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