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