From 935a9fa3748d39dfb949a47e581195596df5c12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gro=C3=9Fe?= Date: Tue, 11 Jul 2017 15:01:43 +0200 Subject: [PATCH 01/16] Add API-method to get data of aggregation --- remote.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/remote.php b/remote.php index 21282f7..be3b689 100644 --- a/remote.php +++ b/remote.php @@ -7,6 +7,8 @@ */ // must be run within Dokuwiki +use dokuwiki\plugin\struct\meta\ConfigParser; +use dokuwiki\plugin\struct\meta\SearchConfig; use dokuwiki\plugin\struct\meta\StructException; if(!defined('DOKU_INC')) die(); @@ -116,4 +118,44 @@ class remote_plugin_struct extends DokuWiki_Remote_Plugin { throw new RemoteException($e->getMessage(), 0, $e); } } + + /** + * Get the data that would be shown in an aggregation + * + * @param array $schemas array of strings with the schema-names + * @param array $cols array of strings with the columns + * @param array $filter array of arrays with ['logic'=> 'and'|'or', 'condition' => 'your condition'] + * @param string $sort string indicating the column to sort by + * + * @return array array of rows, each row is an array of the column values + * @throws RemoteException + */ + public function getAggregationData(array $schemas, array $cols, array $filter = [], $sort = '') { + $schemaLine = 'schema: ' . implode(', ', $schemas); + $columnLine = 'cols: ' . implode(', ', $cols); + $filterLines = array_map(function ($filter) { + return 'filter' . $filter['logic'] . ': ' . $filter['condition']; + }, $filter); + $sortLine = 'sort: ' . $sort; + // schemas, cols, REV?, filter, order + + try { + $parser = new ConfigParser(array_merge([$schemaLine, $columnLine, $sortLine], $filterLines)); + $config = $parser->getConfig(); + $search = new SearchConfig($config); + $results = $search->execute(); + $data = []; + /** @var \dokuwiki\plugin\struct\meta\Value[] $rowValues */ + foreach ($results as $rowValues) { + $row = []; + foreach ($rowValues as $value) { + $row[$value->getColumn()->getFullQualifiedLabel()] = $value->getDisplayValue(); + } + $data[] = $row; + } + return $data; + } catch (StructException $e) { + throw new RemoteException($e->getMessage(), 0, $e); + } + } } From 42d864a2d9d8b0fbc7f04d9759d8635bb65b5d52 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Wed, 12 Jul 2017 23:51:35 +0200 Subject: [PATCH 02/16] Version upped --- plugin.info.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.info.txt b/plugin.info.txt index ca3a086..3464cee 100644 --- a/plugin.info.txt +++ b/plugin.info.txt @@ -1,7 +1,7 @@ base struct author Andreas Gohr, Michael Große email dokuwiki@cosmocode.de -date 2017-06-15 +date 2017-07-11 name struct plugin desc Add and query additional structured page data url https://www.dokuwiki.org/plugin:struct From 57775e30117bbc976c01a11e3ac8a5b8bd694e38 Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Fri, 15 Sep 2017 11:39:20 +0200 Subject: [PATCH 03/16] chane comparator for cloud-tags --- meta/AggregationCloud.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/AggregationCloud.php b/meta/AggregationCloud.php index 8919b80..66f1029 100644 --- a/meta/AggregationCloud.php +++ b/meta/AggregationCloud.php @@ -131,7 +131,7 @@ class AggregationCloud { if (is_array($tagValue)) { $tagValue = $tagValue[0]; } - $key = $value->getColumn()->getFullQualifiedLabel() . '*~'; + $key = $value->getColumn()->getFullQualifiedLabel() . '='; $filter = SearchConfigParameters::$PARAM_FILTER . "[$key]=" . urlencode($tagValue); $this->renderer->listitem_open(1); From 43d764065a6970f3018529d8b7079403e49a37d6 Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Mon, 18 Sep 2017 09:52:48 +0200 Subject: [PATCH 04/16] properly encode comperor for cloud tag --- meta/AggregationCloud.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/AggregationCloud.php b/meta/AggregationCloud.php index 66f1029..f8707e8 100644 --- a/meta/AggregationCloud.php +++ b/meta/AggregationCloud.php @@ -132,7 +132,7 @@ class AggregationCloud { $tagValue = $tagValue[0]; } $key = $value->getColumn()->getFullQualifiedLabel() . '='; - $filter = SearchConfigParameters::$PARAM_FILTER . "[$key]=" . urlencode($tagValue); + $filter = SearchConfigParameters::$PARAM_FILTER . '[' . urlencode($key) . ']=' . urlencode($tagValue); $this->renderer->listitem_open(1); $this->renderer->listcontent_open(); From 3215aebf1c5bffc3ad6043b2d007f43bc3463551 Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Mon, 18 Sep 2017 13:12:02 +0200 Subject: [PATCH 05/16] fix row numbers resetting with pagination (#332) * create unit test that fails * implement the feature fixes #331 --- _test/SearchConfigParameter.test.php | 50 ++++++++++++++++++++++++++++ meta/AggregationTable.php | 3 +- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/_test/SearchConfigParameter.test.php b/_test/SearchConfigParameter.test.php index ad90ea7..304574a 100644 --- a/_test/SearchConfigParameter.test.php +++ b/_test/SearchConfigParameter.test.php @@ -203,4 +203,54 @@ class SearchConfigParameter_struct_test extends StructTest { $this->assertArrayNotHasKey('sort', $conf); $this->assertArrayNotHasKey(meta\SearchConfigParameters::$PARAM_SORT, $param); } + + public function test_pagination() { + global $INPUT; + + $data = array( + 'schemas' => array( + array('schema2', 'alias2'), + ), + 'cols' => array( + 'afirst' + ), + 'rownumbers' => '1', + 'limit' => '5', + ); + + $R = new \Doku_Renderer_xhtml(); + // init with offset + $INPUT->set(meta\SearchConfigParameters::$PARAM_OFFSET, 5); + //$params[meta\SearchConfigParameters::$PARAM_OFFSET] = 25; + $searchConfig = new meta\SearchConfig($data); + $aggregationTable = new meta\AggregationTable('test_pagination', 'xhtml', $R, $searchConfig); + $aggregationTable->render(); + $expect = '
+ + + + + + + + + + + + + + + + + + + + + + +
#afirst
6page14 first data
7page15 first data
8page16 first data
9page17 first data
10page18 first data
+
'; + + $this->assertEquals($expect, $R->doc); + } } diff --git a/meta/AggregationTable.php b/meta/AggregationTable.php index 8ef3d7d..d8d6400 100644 --- a/meta/AggregationTable.php +++ b/meta/AggregationTable.php @@ -360,7 +360,8 @@ class AggregationTable { // row number column if($this->data['rownumbers']) { $this->renderer->tablecell_open(); - $this->renderer->cdata($rownum + 1); + $searchConfigConf = $this->searchConfig->getConf(); + $this->renderer->cdata($rownum + $searchConfigConf['offset'] + 1); $this->renderer->tablecell_close(); } From 48801f6d7d7ad3c56a635066943b271f49b9264e Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Mon, 18 Sep 2017 14:11:18 +0200 Subject: [PATCH 06/16] display CSV import form for page schemas --- admin/schemas.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/admin/schemas.php b/admin/schemas.php index 4fc37b6..d41d935 100644 --- a/admin/schemas.php +++ b/admin/schemas.php @@ -181,13 +181,11 @@ class admin_plugin_struct_schemas extends DokuWiki_Admin_Plugin { $form->addButton('exportcsv', $this->getLang('btn_export')); $form->addFieldsetClose(); - if($schema->isLookup()) { - $form->addFieldsetOpen($this->getLang('admin_csvimport')); - $form->addElement(new \dokuwiki\Form\InputElement('file', 'csvfile')); - $form->addButton('importcsv', $this->getLang('btn_import')); - $form->addHTML('

' . $this->getLang('admin_csvhelp') . '

'); - $form->addFieldsetClose(); - } + $form->addFieldsetOpen($this->getLang('admin_csvimport')); + $form->addElement(new \dokuwiki\Form\InputElement('file', 'csvfile')); + $form->addButton('importcsv', $this->getLang('btn_import')); + $form->addHTML('

' . $this->getLang('admin_csvhelp') . '

'); + $form->addFieldsetClose(); return $form->toHTML(); } From 1fc2361fe236472e7585162e283a99db231aaf7e Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Tue, 19 Sep 2017 12:09:14 +0200 Subject: [PATCH 07/16] implement CSV import for page schemas --- admin/schemas.php | 11 +++- meta/CSVImporter.php | 105 ++++++++++++++++++++++++++--- meta/CSVLookupImporter.php | 20 ++++++ meta/CSVPageImporter.php | 131 +++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 meta/CSVLookupImporter.php create mode 100644 meta/CSVPageImporter.php diff --git a/admin/schemas.php b/admin/schemas.php index d41d935..de66457 100644 --- a/admin/schemas.php +++ b/admin/schemas.php @@ -8,7 +8,8 @@ use dokuwiki\Form\Form; use dokuwiki\plugin\struct\meta\CSVExporter; -use dokuwiki\plugin\struct\meta\CSVImporter; +use dokuwiki\plugin\struct\meta\CSVLookupImporter; +use dokuwiki\plugin\struct\meta\CSVPageImporter; use dokuwiki\plugin\struct\meta\Schema; use dokuwiki\plugin\struct\meta\SchemaBuilder; use dokuwiki\plugin\struct\meta\SchemaEditor; @@ -80,7 +81,13 @@ class admin_plugin_struct_schemas extends DokuWiki_Admin_Plugin { if($table && $INPUT->bool('importcsv')) { if(isset($_FILES['csvfile']['tmp_name'])) { try { - new CSVImporter($table, $_FILES['csvfile']['tmp_name']); + if ($INPUT->bool('lookup')) { + $csvImporter = new CSVLookupImporter($table, $_FILES['csvfile']['tmp_name']); + } else { + $csvImporter = new CSVPageImporter($table, $_FILES['csvfile']['tmp_name']); + } + $csvImporter->import(); + msg($this->getLang('admin_csvdone'), 1); } catch(StructException $e) { msg(hsc($e->getMessage()), -1); diff --git a/meta/CSVImporter.php b/meta/CSVImporter.php index 9e3f4d5..e49c497 100644 --- a/meta/CSVImporter.php +++ b/meta/CSVImporter.php @@ -9,7 +9,7 @@ namespace dokuwiki\plugin\struct\meta; * * @package dokuwiki\plugin\struct\meta */ -class CSVImporter { +abstract class CSVImporter { /** @var Schema */ protected $schema; @@ -26,6 +26,12 @@ class CSVImporter { /** @var int current line number */ protected $line = 0; + /** @var list of headers */ + protected $header; + + /** @var array list of validation errors */ + protected $errors; + /** * CSVImporter constructor. * @@ -40,12 +46,17 @@ class CSVImporter { $this->schema = new Schema($table); if(!$this->schema->getId()) throw new StructException('Schema does not exist'); - if(!$this->schema->isLookup()) throw new StructException('CSV import is only valid for Lookup Schemas'); - /** @var \helper_plugin_struct_db $db */ $db = plugin_load('helper', 'struct_db'); $this->sqlite = $db->getDB(true); + } + /** + * Import the data from file. + * + * @throws StructException + */ + public function import() { // Do the import $this->readHeaders(); $this->importCSV(); @@ -53,6 +64,8 @@ class CSVImporter { /** * Read the CSV headers and match it with the Schema columns + * + * @return array headers of file */ protected function readHeaders() { $header = fgetcsv($this->fh); @@ -69,6 +82,8 @@ class CSVImporter { if(!$this->columns) { throw new StructException('None of the CSV headers matched any of the schema\'s fields'); } + + $this->header = $header; } /** @@ -118,18 +133,39 @@ class CSVImporter { } /** - * Imports one line into the schema + * The errors that occured during validation * - * @param string[] $line the parsed CSV line - * @param string $single SQL for single table - * @param string $multi SQL for multi table + * @return string[] already translated error messages */ - protected function importLine($line, $single, $multi) { + public function getErrors() { + return $this->errors; + } + + /** + * Validate a single value + * + * @param Column $col the column of that value + * @param mixed &$rawvalue the value, will be fixed according to the type + * @return bool true if the data validates, otherwise false + */ + protected function validateValue(Column $col, &$rawvalue) { + //by default no validation + return true; + } + + /** + * Read and validate CSV parsed line + * + * @param &$line + */ + protected function readLine(&$line) { // prepare values for single value table $values = array(); foreach($this->columns as $i => $column) { if(!isset($line[$i])) throw new StructException('Missing field at CSV line %d', $this->line); + if(!$this->validateValue($column, $line[$i])) return false; + if($column->isMulti()) { // multi values get split on comma $line[$i] = array_map('trim', explode(',', $line[$i])); @@ -138,20 +174,69 @@ class CSVImporter { $values[] = $line[$i]; } } + //if no ok don't import + return $values; + } - // insert into single value table (and create pid) + /** + * @param string[] $values + * @param string $single SQL for single table + * + * @return string pid + */ + protected function insertIntoSingle($values, $single) { $this->sqlite->query($single, $values); $res = $this->sqlite->query('SELECT last_insert_rowid()'); $pid = $this->sqlite->res2single($res); $this->sqlite->res_close($res); + return $pid; + } + + /** + * @param string $multi SQL for multi table + * @param $pid string + * @param $column string + * @param $row string + * @param $value string + */ + protected function insertIntoMulti($multi, $pid, $column, $row, $value) { + $this->sqlite->query($multi, array($pid, $column->getColref(), $row + 1, $value)); + } + + /** + * @param string[] $values parsed line values + * @param string $single SQL for single table + * @param string $multi SQL for multi table + */ + protected function saveLine($values, $line, $single, $multi) { + // insert into single value table (and create pid) + $pid = $this->insertIntoSingle($values, $single); + // insert all the multi values foreach($this->columns as $i => $column) { if(!$column->isMulti()) continue; foreach($line[$i] as $row => $value) { - $this->sqlite->query($multi, array($pid, $column->getColref(), $row + 1, $value)); + $this->insertIntoMulti($multi, $pid, $column, $row, $value); } } } + /** + * Imports one line into the schema + * + * @param string[] $line the parsed CSV line + * @param string $single SQL for single table + * @param string $multi SQL for multi table + */ + protected function importLine($line, $single, $multi) { + //read values, false if no validation + $values = $this->readLine($line); + + if($values) { + $this->saveLine($values, $line, $single, $multi); + } else foreach($this->errors as $error) { + msg($error, -1); + } + } } diff --git a/meta/CSVLookupImporter.php b/meta/CSVLookupImporter.php new file mode 100644 index 0000000..20c86e2 --- /dev/null +++ b/meta/CSVLookupImporter.php @@ -0,0 +1,20 @@ +schema->isLookup()) throw new StructException($table.' is not lookup schema'); + } + +} diff --git a/meta/CSVPageImporter.php b/meta/CSVPageImporter.php new file mode 100644 index 0000000..24978d5 --- /dev/null +++ b/meta/CSVPageImporter.php @@ -0,0 +1,131 @@ +schema->isLookup()) throw new StructException($table.' is not a page schema'); + } + + /** + * Import page schema only when the pid header is present. + */ + protected function readHeaders() { + + //add pid to struct + $pageType = new Page(null, 'pid'); + $this->columns[] = new Column(0, $pageType); + + parent::readHeaders(); + + if(!in_array('pid', $this->header)) throw new StructException('There is no "pid" header in the CSV. Schema not imported.'); + } + + /** + * Creates the insert string for the single value table + * + * @return string + */ + protected function getSQLforAllValues() { + $colnames = array(); + foreach($this->columns as $i => $col) { + $colnames[] = 'col' . $col->getColref(); + } + //replace first column with pid + $colnames[0] = 'pid'; + //insert rev at the end + $colnames[] = 'rev'; + + $placeholds = join(', ', array_fill(0, count($colnames), '?')); + $colnames = join(', ', $colnames); + $table = $this->schema->getTable(); + + //replace previous data + return "REPLACE INTO data_$table ($colnames, latest) VALUES ($placeholds, 1)"; + } + + /** + * Add the revision. + * + * @param string[] $values + * @param $line + * @param string $single + * @param string $multi + */ + protected function saveLine($values, $line, $single, $multi) { + //read the lastest revision of inserted page + $values[] = @filemtime(wikiFN($values[0])); + parent::saveLine($values, $line, $single, $multi); + } + + /** + * In the paga schemas primary key is a touple of (pid, rev) + * + * @param string[] $values + * @param string $single + * @return array(pid, rev) + */ + protected function insertIntoSingle($values, $single) { + parent::insertIntoSingle($values, $single); + $pid = $values[0]; + $rev = $values[count($values) - 1]; + //primary key is touple of (pid, rev) + return array($pid, $rev); + } + + /** + * Add pid and rev to insert query parameters + * + * @param string $multi + * @param string $pk + * @param string $column + * @param string $row + * @param string $value + */ + protected function insertIntoMulti($multi, $pk, $column, $row, $value) { + list($pid, $rev) = $pk; + $this->sqlite->query($multi, array($pid, $rev, $column->getColref(), $row + 1, $value)); + } + + /** + * In page schemas we use REPLACE instead of INSERT to prevent ambiguity + * + * @return string + */ + protected function getSQLforMultiValue() { + $table = $this->schema->getTable(); + /** @noinspection SqlResolve */ + return "REPLACE INTO multi_$table (pid, rev, colref, row, value, latest) VALUES (?,?,?,?,?,1)"; + } + + /** + * Check if page id realy exists + * + * @param Column $col + * @param mixed $rawvalue + * @return bool + */ + protected function validateValue(Column $col, &$rawvalue) { + //check if page id exists + if($col->getLabel() == 'pid') { + $rawvalue = cleanID($rawvalue); + if(page_exists($rawvalue)) { + return true; + } + $this->errors[] = 'Page "'.$rawvalue.'" does not exists. Skipping the row.'; + return false; + } + + return parent::validateValue($col, $rawvalue); + } +} From 9b4977e0bced4f20e020c234b5e7df43c873613a Mon Sep 17 00:00:00 2001 From: Malte Lembeck Date: Tue, 19 Sep 2017 15:25:10 +0200 Subject: [PATCH 08/16] translation update --- lang/de/lang.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lang/de/lang.php b/lang/de/lang.php index 0e9116f..bb89d4f 100644 --- a/lang/de/lang.php +++ b/lang/de/lang.php @@ -4,18 +4,31 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Andreas Gohr + * @author Malte Lembeck */ $lang['menu'] = 'Struct Schema Editor'; $lang['menu_assignments'] = 'Struct Schema Zuweisungen'; $lang['headline'] = 'Strukturierte Daten'; -$lang['edithl'] = 'Schema %s bearbeiten'; $lang['create'] = 'Neues Schema anlegen'; $lang['schemaname'] = 'Schema-Name:'; $lang['save'] = 'Speichern'; $lang['createhint'] = 'Achtung: Schemas können später nicht umbenannt werden'; $lang['pagelabel'] = 'Seite'; +$lang['rowlabel'] = 'Reihe #'; +$lang['revisionlabel'] = 'Zuletzt geändert'; +$lang['userlabel'] = 'letzter Bearbeitender'; $lang['summary'] = 'Struct-Daten geändert'; $lang['export'] = 'Schema als JSON exportieren'; $lang['btn_export'] = 'Exportieren'; - +$lang['btn_import'] = 'Importieren'; +$lang['btn_delete'] = 'Löschen'; $lang['js']['confirmAssignmentsDelete'] = 'Wollen Sie wirklich die Zuweisung von Schma "{0}" zu Seite/Namensraum "{1}" löschen?'; +$lang['tab_export'] = 'Importieren/Exportieren'; +$lang['tab_delete'] = 'Löschen'; +$lang['editor_sort'] = 'Sortieren'; +$lang['editor_conf'] = 'Konfiguration'; +$lang['editor_type'] = 'Eingeben'; +$lang['editor_enabled'] = 'Aktiviert'; +$lang['assign_add'] = 'Hinzufügen'; +$lang['assign_del'] = 'Löschen'; +$lang['multidropdown'] = 'Halte STRG oder CMD um mehrere Werte auszuwählen.'; From e53295c50fccd6c704766be3b2ac38c83bde3cba Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Tue, 19 Sep 2017 15:49:29 +0200 Subject: [PATCH 09/16] create new page revision for every csv data import --- meta/CSVPageImporter.php | 59 +++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/meta/CSVPageImporter.php b/meta/CSVPageImporter.php index 24978d5..0a9d827 100644 --- a/meta/CSVPageImporter.php +++ b/meta/CSVPageImporter.php @@ -4,6 +4,9 @@ namespace dokuwiki\plugin\struct\meta; use dokuwiki\plugin\struct\types\Page; class CSVPageImporter extends CSVImporter { + + protected $importedPids = array(); + /** * Chceck if schema is page schema * @@ -50,8 +53,7 @@ class CSVPageImporter extends CSVImporter { $colnames = join(', ', $colnames); $table = $this->schema->getTable(); - //replace previous data - return "REPLACE INTO data_$table ($colnames, latest) VALUES ($placeholds, 1)"; + return "INSERT INTO data_$table ($colnames, latest) VALUES ($placeholds, 1)"; } /** @@ -63,8 +65,22 @@ class CSVPageImporter extends CSVImporter { * @param string $multi */ protected function saveLine($values, $line, $single, $multi) { - //read the lastest revision of inserted page - $values[] = @filemtime(wikiFN($values[0])); + //create new page revision + $pid = $values[0]; + $helper = plugin_load('helper', 'struct'); + $revision = $helper->createPageRevision($pid, 'CSV data imported'); + p_get_metadata($pid); // reparse the metadata of the page top update the titles/rev/lasteditor table + + // make sure this schema is assigned + /** @noinspection PhpUndefinedVariableInspection */ + Assignments::getInstance()->assignPageSchema( + $pid, + $this->schema->getTable() + ); + + //add page revision to values + $values[] = $revision; + parent::saveLine($values, $line, $single, $multi); } @@ -76,9 +92,16 @@ class CSVPageImporter extends CSVImporter { * @return array(pid, rev) */ protected function insertIntoSingle($values, $single) { - parent::insertIntoSingle($values, $single); $pid = $values[0]; $rev = $values[count($values) - 1]; + + //update latest + $table = $this->schema->getTable(); + $this->sqlite->query("UPDATE data_$table SET latest = 0 WHERE latest = 1 AND pid = ?", array($pid)); + + //insert into table + parent::insertIntoSingle($values, $single); + //primary key is touple of (pid, rev) return array($pid, $rev); } @@ -94,6 +117,11 @@ class CSVPageImporter extends CSVImporter { */ protected function insertIntoMulti($multi, $pk, $column, $row, $value) { list($pid, $rev) = $pk; + + //update latest + $table = $this->schema->getTable(); + $this->sqlite->query("UPDATE multi_$table SET latest = 0 WHERE latest = 1 AND pid = ?", array($pid)); + $this->sqlite->query($multi, array($pid, $rev, $column->getColref(), $row + 1, $value)); } @@ -105,7 +133,7 @@ class CSVPageImporter extends CSVImporter { protected function getSQLforMultiValue() { $table = $this->schema->getTable(); /** @noinspection SqlResolve */ - return "REPLACE INTO multi_$table (pid, rev, colref, row, value, latest) VALUES (?,?,?,?,?,1)"; + return "INSERT INTO multi_$table (pid, rev, colref, row, value, latest) VALUES (?,?,?,?,?,1)"; } /** @@ -116,13 +144,24 @@ class CSVPageImporter extends CSVImporter { * @return bool */ protected function validateValue(Column $col, &$rawvalue) { - //check if page id exists + //check if page id exists and schema is bounded to the page if($col->getLabel() == 'pid') { - $rawvalue = cleanID($rawvalue); - if(page_exists($rawvalue)) { + $pid = cleanID($rawvalue); + if (isset($this->importedPids[$pid])) { + $this->errors[] = 'Page "'.$pid.'" already imported. Skipping the row.'; + return false; + } + if(page_exists($pid)) { + //check if schema is assigned to page + $tables = Assignments::getInstance()->getPageAssignments($pid, true); + if (!in_array($this->schema->getTable(), $tables)) { + $this->errors[] = 'Schema not assigned to page "'.$pid.'"'; + return false; + } + $this->importedPids[$pid] = true; return true; } - $this->errors[] = 'Page "'.$rawvalue.'" does not exists. Skipping the row.'; + $this->errors[] = 'Page "'.$pid.'" does not exists. Skipping the row.'; return false; } From 424167cba63c28d9a5fe6c9e9fed819bb2eb977f Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Tue, 19 Sep 2017 16:05:37 +0200 Subject: [PATCH 10/16] remove pid schema assignment check --- meta/CSVPageImporter.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/meta/CSVPageImporter.php b/meta/CSVPageImporter.php index 0a9d827..56e94b5 100644 --- a/meta/CSVPageImporter.php +++ b/meta/CSVPageImporter.php @@ -152,12 +152,6 @@ class CSVPageImporter extends CSVImporter { return false; } if(page_exists($pid)) { - //check if schema is assigned to page - $tables = Assignments::getInstance()->getPageAssignments($pid, true); - if (!in_array($this->schema->getTable(), $tables)) { - $this->errors[] = 'Schema not assigned to page "'.$pid.'"'; - return false; - } $this->importedPids[$pid] = true; return true; } From 69c07c1ba55c24c3df9e61701ad4f72c7948c97e Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Tue, 19 Sep 2017 16:23:47 +0200 Subject: [PATCH 11/16] doc update --- meta/CSVImporter.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/meta/CSVImporter.php b/meta/CSVImporter.php index e49c497..4ab89e5 100644 --- a/meta/CSVImporter.php +++ b/meta/CSVImporter.php @@ -179,10 +179,12 @@ abstract class CSVImporter { } /** + * INSERT $values into data_* table + * * @param string[] $values * @param string $single SQL for single table * - * @return string pid + * @return string last_insert_rowid() */ protected function insertIntoSingle($values, $single) { $this->sqlite->query($single, $values); @@ -194,6 +196,8 @@ abstract class CSVImporter { } /** + * INSERT one row into multi_* table + * * @param string $multi SQL for multi table * @param $pid string * @param $column string @@ -205,6 +209,8 @@ abstract class CSVImporter { } /** + * Save one CSV line into database + * * @param string[] $values parsed line values * @param string $single SQL for single table * @param string $multi SQL for multi table From 0e489a46bcc1d9f34fc792df74c5ec207cdf7b7a Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Tue, 19 Sep 2017 16:41:17 +0200 Subject: [PATCH 12/16] make one SQL transaction per imported line to eliminate potential inconsistencies --- meta/CSVImporter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meta/CSVImporter.php b/meta/CSVImporter.php index 4ab89e5..9e96941 100644 --- a/meta/CSVImporter.php +++ b/meta/CSVImporter.php @@ -124,12 +124,12 @@ abstract class CSVImporter { $single = $this->getSQLforAllValues(); $multi = $this->getSQLforMultiValue(); - $this->sqlite->query('BEGIN TRANSACTION'); while(($data = fgetcsv($this->fh)) !== false) { + $this->sqlite->query('BEGIN TRANSACTION'); $this->line++; $this->importLine($data, $single, $multi); + $this->sqlite->query('COMMIT TRANSACTION'); } - $this->sqlite->query('COMMIT TRANSACTION'); } /** From eaab469c00cfe321e34b8028f8f0004daa52b91e Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Fri, 22 Sep 2017 13:42:40 +0200 Subject: [PATCH 13/16] fixes #337 --- action/bureaucracy.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/action/bureaucracy.php b/action/bureaucracy.php index f3d3cf0..0a31ec1 100644 --- a/action/bureaucracy.php +++ b/action/bureaucracy.php @@ -112,7 +112,8 @@ class action_plugin_struct_bureaucracy extends DokuWiki_Action_Plugin { ); // trigger meta data rendering to set page title - p_get_metadata($id); + // expire the cache in order to correctly render the struct header on the first page visit + p_get_metadata($id, array('cache' => 'expire')); } } From 345c38383452f7f13a6e3d0864644c99ce2d80a7 Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Fri, 22 Sep 2017 16:56:55 +0200 Subject: [PATCH 14/16] Replace lookup placeholders by value not by the row id While creating a new page using bureaucracy template action together with struct provided data, we should replace the lookup-field's placeholders by the value selected by the user. It's much more sensible. Fixes #323 --- action/bureaucracy.php | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/action/bureaucracy.php b/action/bureaucracy.php index f3d3cf0..9c72575 100644 --- a/action/bureaucracy.php +++ b/action/bureaucracy.php @@ -12,6 +12,7 @@ if(!defined('DOKU_INC')) die(); use dokuwiki\plugin\struct\meta\AccessTable; use dokuwiki\plugin\struct\meta\Assignments; use dokuwiki\plugin\struct\meta\Schema; +use dokuwiki\plugin\struct\meta\Search; /** * Handles bureaucracy additions @@ -32,6 +33,7 @@ class action_plugin_struct_bureaucracy extends DokuWiki_Action_Plugin { * @return void */ public function register(Doku_Event_Handler $controller) { + $controller->register_hook('PLUGIN_BUREAUCRACY_TEMPLATE_SAVE', 'BEFORE', $this, 'handle_lookup_fields'); $controller->register_hook('PLUGIN_BUREAUCRACY_TEMPLATE_SAVE', 'AFTER', $this, 'handle_save'); $controller->register_hook('PLUGIN_BUREAUCRACY_FIELD_UNKNOWN', 'BEFORE', $this, 'handle_schema'); } @@ -72,6 +74,47 @@ class action_plugin_struct_bureaucracy extends DokuWiki_Action_Plugin { return true; } + /** + * Replace lookup fields placeholder's values + * + * @param Doku_Event $event event object by reference + * @param mixed $param [the parameters passed as fifth argument to register_hook() when this + * handler was registered] + * @return bool + */ + public function handle_lookup_fields(Doku_Event $event, $param) { + foreach($event->data['fields'] as $field) { + if(!is_a($field, 'helper_plugin_struct_field')) continue; + if($field->column->getType()->getClass() != 'Lookup') continue; + + $pid = $field->getParam('value'); + $config = $field->column->getType()->getConfig(); + + // find proper value + // current Search implementation doesn't allow doing it using SQL + $search = new Search(); + $search->addSchema($config['schema']); + $search->addColumn($config['field']); + $result = $search->execute(); + $pids = $search->getPids(); + $len = count($result); + + $value = ''; + for($i = 0; $i < $len; $i++) { + if ($pids[$i] == $pid) { + $value = $result[$i][0]->getDisplayValue(); + break; + } + } + + //replace previous value + if ($value) { + $event->data['values'][$field->column->getFullQualifiedLabel()] = $value; + } + } + return true; + } + /** * Save the struct data * From 4d7999133cfa8c99aee4dd4e83452bf120d03127 Mon Sep 17 00:00:00 2001 From: Szymon Olewniczak Date: Mon, 25 Sep 2017 14:20:47 +0200 Subject: [PATCH 15/16] Create unit tests for testing lookup replacements This commit provides the basis for further bureaucracy-struct integration testing. Currently we only check if lookup field replacements works correctly. --- _test/Bureaucracy.test.php | 109 ++++++++++++++++++++++ _test/json/bureaucracy.struct.json | 36 +++++++ _test/json/bureaucracy_lookup.struct.json | 58 ++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 _test/Bureaucracy.test.php create mode 100644 _test/json/bureaucracy.struct.json create mode 100644 _test/json/bureaucracy_lookup.struct.json diff --git a/_test/Bureaucracy.test.php b/_test/Bureaucracy.test.php new file mode 100644 index 0000000..6a3207c --- /dev/null +++ b/_test/Bureaucracy.test.php @@ -0,0 +1,109 @@ +loadSchemaJSON('bureaucracy_lookup', '', 0, true); + $this->loadSchemaJSON('bureaucracy'); + + //insert some data to lookup + for($i = 1; $i <= 10; ++$i) { + $data = array( + 'lookup_first' => 'value first ' . $i, + 'lookup_second' => 'value second ' . $i + ); + + $lookupData = AccessTable::byTableName('bureaucracy_lookup', 0); + $lookupData->saveData($data); + $this->lookup[] = $lookupData; + } + } + + public function test_bureaucracy_lookup_replacement_empty() { + //page created by bureaucracy + $id = 'bureaucracy_lookup_replacement_empty'; + //id of template page + $template_id = 'template'; + + //create template + saveWikiText($template_id, 'Value:@@bureaucracy.lookup_select@@', 'summary'); + + //build form + $fields = array(); + + $lookup_field = plugin_load('helper', 'struct_field'); + $lookup_field->opt['label'] = 'bureaucracy.lookup_select'; + //empty value + $lookup_field->opt['value'] = ''; + //left pagename undefined + //$lookup_field->opt['pagename']; + + //$args are ommited in struct_field + $lookup_field->initialize(array()); + $fields[] = $lookup_field; + + //helper_plugin_bureaucracy_actiontemplate + $actiontemplate = plugin_load('helper', 'bureaucracy_actiontemplate'); + $actiontemplate->run($fields, '', array($template_id, $id, '_')); + + $page_content = io_readWikiPage(wikiFN($id), $id); + + $this->assertEquals('Value:', $page_content); + } + + public function test_bureaucracy_lookup_replacement() { + //page created by bureaucracy + $id = 'bureaucracy_lookup_replacement'; + //id of template page + $template_id = 'template'; + //pid of selected value + $lookup_pid = $this->lookup[0]->getPid(); + //selected value + $lookup_value = $this->lookup[0]->getData()['lookup_first']->getValue(); + + //create template + saveWikiText($template_id, 'Value:@@bureaucracy.lookup_select@@', 'summary'); + + //build form + $fields = array(); + + $lookup_field = plugin_load('helper', 'struct_field'); + $lookup_field->opt['label'] = 'bureaucracy.lookup_select'; + $lookup_field->opt['value'] = $lookup_pid; + //left pagename undefined + //$lookup_field->opt['pagename']; + + //$args are ommited in struct_field + $lookup_field->initialize(array()); + $fields[] = $lookup_field; + + //helper_plugin_bureaucracy_actiontemplate + + $actiontemplate = plugin_load('helper', 'bureaucracy_actiontemplate'); + $actiontemplate->run($fields, '', array($template_id, $id, '_')); + + $page_content = io_readWikiPage(wikiFN($id), $id); + + $this->assertEquals('Value:' . $lookup_value, $page_content); + } +} diff --git a/_test/json/bureaucracy.struct.json b/_test/json/bureaucracy.struct.json new file mode 100644 index 0000000..03ce006 --- /dev/null +++ b/_test/json/bureaucracy.struct.json @@ -0,0 +1,36 @@ +{ + "structversion": "2017-07-11", + "schema": "bureaucracy", + "id": "2", + "user": "", + "config": { + "allowed editors": "", + "label": { + "en": "" + } + }, + "columns": [ + { + "colref": 1, + "ismulti": false, + "isenabled": true, + "sort": 10, + "label": "lookup_select", + "class": "Lookup", + "config": { + "visibility": { + "inpage": true, + "ineditor": true + }, + "schema": "bureaucracy_lookup", + "field": "lookup_first", + "label": { + "en": "" + }, + "hint": { + "en": "" + } + } + } + ] +} diff --git a/_test/json/bureaucracy_lookup.struct.json b/_test/json/bureaucracy_lookup.struct.json new file mode 100644 index 0000000..db87f5e --- /dev/null +++ b/_test/json/bureaucracy_lookup.struct.json @@ -0,0 +1,58 @@ +{ + "structversion": "2017-07-11", + "schema": "bureaucracy_lookup", + "id": "1", + "user": "", + "config": { + "allowed editors": "", + "label": { + "en": "" + } + }, + "columns": [ + { + "colref": 1, + "ismulti": false, + "isenabled": true, + "sort": 10, + "label": "lookup_first", + "class": "Text", + "config": { + "visibility": { + "inpage": true, + "ineditor": true + }, + "prefix": "", + "postfix": "", + "label": { + "en": "" + }, + "hint": { + "en": "" + } + } + }, + { + "colref": 2, + "ismulti": false, + "isenabled": true, + "sort": 20, + "label": "lookup_second", + "class": "Text", + "config": { + "visibility": { + "inpage": true, + "ineditor": true + }, + "prefix": "", + "postfix": "", + "label": { + "en": "" + }, + "hint": { + "en": "" + } + } + } + ] +} From 67ddf0d09fba379297c77457d5d1c7b4f6c56a57 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Wed, 11 Oct 2017 19:01:23 +0200 Subject: [PATCH 16/16] do not use a non-guaranteed placeholder --- all.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/all.less b/all.less index 049dd81..af419a7 100644 --- a/all.less +++ b/all.less @@ -1,4 +1,4 @@ -@media only screen and (max-width: @ini_tablet_width) { +@media only screen and (max-width: 800px) { #plugin__struct_output { margin-right: 0; }