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