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