xref: /plugin/struct/meta/CSVPageImporter.php (revision 0ceefd5c55d49dcf9a9f59c8e57e60eaeea1c187)
1<?php
2
3namespace dokuwiki\plugin\struct\meta;
4use dokuwiki\plugin\struct\types\Page;
5
6class CSVPageImporter extends CSVImporter {
7
8    protected $importedPids = array();
9
10    /** @var bool[]  */
11    protected $createPage = [];
12
13    /**
14     * Chceck if schema is page schema
15     *
16     * @throws StructException
17     * @param string $table
18     * @param string $file
19     */
20//    public function __construct($table, $file) {
21//        parent::__construct($table, $file);
22//
23//        if($this->schema->isLookup()) throw new StructException($table.' is not a page schema');
24//    }
25
26    /**
27     * Import page schema only when the pid header is present.
28     */
29    protected function readHeaders() {
30
31        //add pid to struct
32        $pageType = new Page(null, 'pid');
33        $this->columns[] = new Column(0, $pageType, 0, true, $this->schema->getTable());
34
35        parent::readHeaders();
36
37        if(!in_array('pid', $this->header)) throw new StructException('There is no "pid" header in the CSV. Schema not imported.');
38    }
39
40    /**
41     * Creates the insert string for the single value table
42     *
43     * @return string
44     */
45    protected function getSQLforAllValues() {
46        $colnames = array();
47        foreach($this->columns as $i => $col) {
48            $colnames[] = 'col' . $col->getColref();
49        }
50        //replace first column with pid
51        $colnames[0] = 'pid';
52        //insert rev at the end
53        $colnames[] = 'rev';
54
55        $placeholds = join(', ', array_fill(0, count($colnames), '?'));
56        $colnames = join(', ', $colnames);
57        $table = $this->schema->getTable();
58
59        return "INSERT INTO data_$table ($colnames, latest) VALUES ($placeholds, 1)";
60    }
61
62    /**
63     * Add the revision.
64     *
65     * @param string[] $values
66     * @param          $line
67     * @param string   $single
68     * @param string   $multi
69     */
70    protected function saveLine($values, $line, $single, $multi) {
71        //create new page revision
72        $pid = cleanID($values[0]);
73        if ($this->createPage[$pid]) {
74            $this->createPage($pid, $line);
75        }
76        $helper = plugin_load('helper', 'struct');
77        $revision = $helper->createPageRevision($pid, 'CSV data imported');
78        p_get_metadata($pid); // reparse the metadata of the page top update the titles/rev/lasteditor table
79
80        // make sure this schema is assigned
81        /** @noinspection PhpUndefinedVariableInspection */
82        Assignments::getInstance()->assignPageSchema(
83            $pid,
84            $this->schema->getTable()
85        );
86
87        //add page revision to values
88        $values[] = $revision;
89
90        parent::saveLine($values, $line, $single, $multi);
91    }
92
93    /**
94     * Create a page from a namespace template and replace column-label-placeholders
95     *
96     * This is intended to use the same placeholders as bureaucracy in their most basic version
97     * (i.e. without default values, formatting, etc. )
98     *
99     * @param string $pid
100     * @param array  $line
101     */
102    protected function createPage($pid, $line)
103    {
104        $text = pageTemplate($pid);
105        if (trim($text) === '') {
106            $pageParts = explode(':', $pid);
107            $pagename = end($pageParts);
108            $text = "====== $pagename ======\n";
109        }
110        $keys = array_reduce($this->columns,
111            function ($keys, Column $col) {
112                if (!in_array($col->getLabel(), $keys, true)) {
113                    return $keys;
114                }
115                $index = array_search($col->getLabel(), $keys, true);
116                $keys[$index] = $col->getFullQualifiedLabel();
117                return $keys;
118            },
119            $this->header
120        );
121
122        $keysAt = array_map(function ($key) { return "@@$key@@";}, $keys);
123        $keysHash = array_map(function ($key) { return "##$key##";}, $keys);
124        $flatValues = array_map(
125            function($value) {
126                if (is_array($value)) {
127                    return implode(', ', $value);
128                }
129                return $value;
130            }, $line);
131        $text = $this->evaluateIfNotEmptyTags($text, $keys, $flatValues);
132        $text = str_replace($keysAt, $flatValues, $text);
133        /** @noinspection CascadeStringReplacementInspection */
134        $text = str_replace($keysHash, $flatValues, $text);
135        saveWikiText($pid, $text, 'Created by struct csv import');
136    }
137
138    /**
139     * Replace conditional <ifnotempty fieldname></ifnotempty> tags
140     *
141     * @param string   $text   The template
142     * @param string[] $keys   The array of qualified headers
143     * @param string[] $values The flat array of corresponding values
144     *
145     * @return string The template with the tags replaced
146     */
147    protected function evaluateIfNotEmptyTags($text, $keys, $values)
148    {
149        return preg_replace_callback(
150            '/<ifnotempty (.+?)>([^<]*?)<\/ifnotempty>/',
151            function ($matches) use ($keys, $values)
152            {
153                list (,$blockKey, $textIfNotEmpty) = $matches;
154                $index = array_search($blockKey, $keys, true);
155                if ($index === false) {
156                    msg('Import error: Key "' . hsc($blockKey) . '" not found!', -1);
157                    return '';
158                }
159                if (trim($values[$index]) === '') {
160                    return '';
161                }
162                return $textIfNotEmpty;
163            },
164            $text
165        );
166    }
167
168    /**
169     * In the paga schemas primary key is a touple of (pid, rev)
170     *
171     * @param string[] $values
172     * @param string   $single
173     * @return array(pid, rev)
174     */
175    protected function insertIntoSingle($values, $single) {
176        $pid = $values[0];
177        $rev = $values[count($values) - 1];
178
179        //update latest
180        $table = $this->schema->getTable();
181        $this->sqlite->query("UPDATE data_$table SET latest = 0 WHERE latest = 1 AND pid = ?", array($pid));
182
183        //insert into table
184        parent::insertIntoSingle($values, $single);
185
186        //primary key is touple of (pid, rev)
187        return array($pid, $rev);
188    }
189
190    /**
191     * Add pid and rev to insert query parameters
192     *
193     * @param string $multi
194     * @param string $pk
195     * @param string $column
196     * @param string $row
197     * @param string $value
198     */
199    protected function insertIntoMulti($multi, $pk, $column, $row, $value) {
200        list($pid, $rev) = $pk;
201
202        //update latest
203        $table = $this->schema->getTable();
204        $this->sqlite->query("UPDATE multi_$table SET latest = 0 WHERE latest = 1 AND pid = ?", array($pid));
205
206        $this->sqlite->query($multi, array($pid, $rev, $column->getColref(), $row + 1, $value));
207    }
208
209    /**
210     * In page schemas we use REPLACE instead of INSERT to prevent ambiguity
211     *
212     * @return string
213     */
214    protected function getSQLforMultiValue() {
215        $table = $this->schema->getTable();
216        /** @noinspection SqlResolve */
217        return "INSERT INTO multi_$table (pid, rev, colref, row, value, latest) VALUES (?,?,?,?,?,1)";
218    }
219
220    /**
221     * Check if page id realy exists
222     *
223     * @param Column $col
224     * @param mixed  $rawvalue
225     * @return bool
226     */
227    protected function validateValue(Column $col, &$rawvalue) {
228        //check if page id exists and schema is bounded to the page
229        if($col->getLabel() == 'pid') {
230            $pid = cleanID($rawvalue);
231            if (isset($this->importedPids[$pid])) {
232                $this->errors[] = 'Page "'.$pid.'" already imported. Skipping the row.';
233                return false;
234            }
235            if(page_exists($pid)) {
236                $this->importedPids[$pid] = true;
237                return true;
238            }
239            global $INPUT;
240            if ($INPUT->bool('createPage')) {
241                $this->createPage[$pid] = true;
242                return true;
243            }
244            $this->errors[] = 'Page "'.$pid.'" does not exists. Skipping the row.';
245            return false;
246        }
247
248        return parent::validateValue($col, $rawvalue);
249    }
250}
251