1<?php 2 3/** 4 * DokuWiki Plugin struct (Action Component) 5 * 6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7 * @author Andreas Gohr, Michael Große <dokuwiki@cosmocode.de> 8 */ 9 10use dokuwiki\plugin\struct\meta\Assignments; 11use dokuwiki\plugin\struct\meta\Column; 12use dokuwiki\plugin\struct\meta\Schema; 13use dokuwiki\plugin\struct\types\Lookup; 14use dokuwiki\plugin\struct\types\Media; 15use dokuwiki\plugin\struct\types\Page; 16 17class action_plugin_struct_move extends DokuWiki_Action_Plugin 18{ 19 20 /** @var helper_plugin_sqlite */ 21 protected $db = null; 22 23 /** 24 * Registers a callback function for a given event 25 * 26 * @param Doku_Event_Handler $controller DokuWiki's event controller object 27 * @return void 28 */ 29 public function register(Doku_Event_Handler $controller) 30 { 31 $controller->register_hook('PLUGIN_MOVE_PAGE_RENAME', 'AFTER', $this, 'handleMove', true); 32 $controller->register_hook('PLUGIN_MOVE_MEDIA_RENAME', 'AFTER', $this, 'handleMove', false); 33 } 34 35 /** 36 * Renames all occurances of a page ID in the database 37 * 38 * @param Doku_Event $event event object by reference 39 * @param bool $ispage is this a page move operation? 40 * @return bool 41 */ 42 public function handleMove(Doku_Event $event, $ispage) 43 { 44 /** @var helper_plugin_struct_db $hlp */ 45 $hlp = plugin_load('helper', 'struct_db'); 46 $this->db = $hlp->getDB(false); 47 if (!$this->db) return false; 48 $old = $event->data['src_id']; 49 $new = $event->data['dst_id']; 50 51 // prepare work 52 $this->db->query('BEGIN TRANSACTION'); 53 54 // general update of our meta tables 55 if ($ispage) { 56 $this->updateDataTablePIDs($old, $new); 57 $this->updateAssignments($old, $new); 58 $this->updateTitles($old, $new); 59 } 60 61 // apply updates to all columns in all schemas depending on type 62 $schemas = Schema::getAll(); 63 foreach ($schemas as $table) { 64 $schema = new Schema($table); 65 foreach ($schema->getColumns() as $col) { 66 if ($ispage) { 67 switch (get_class($col->getType())) { 68 case Page::class: 69 $this->updateColumnID($schema, $col, $old, $new, true); 70 break; 71 case Lookup::class: 72 $this->updateColumnLookup($schema, $col, $old, $new); 73 break; 74 } 75 } else { 76 switch (get_class($col->getType())) { 77 case Media::class: 78 $this->updateColumnID($schema, $col, $old, $new); 79 break; 80 } 81 } 82 } 83 } 84 85 // execute everything 86 $ok = $this->db->query('COMMIT TRANSACTION'); 87 if (!$ok) { 88 $this->db->query('ROLLBACK TRANSACTION'); 89 return false; 90 } 91 92 return true; 93 } 94 95 /** 96 * Update the pid column of ALL data tables 97 * 98 * (we don't trust the assigments are still there) 99 * 100 * @param string $old old page id 101 * @param string $new new page id 102 */ 103 protected function updateDataTablePIDs($old, $new) 104 { 105 foreach (Schema::getAll('page') as $tbl) { 106 /** @noinspection SqlResolve */ 107 $sql = "UPDATE data_$tbl SET pid = ? WHERE pid = ?"; 108 $this->db->query($sql, array($new, $old)); 109 110 /** @noinspection SqlResolve */ 111 $sql = "UPDATE multi_$tbl SET pid = ? WHERE pid = ?"; 112 $this->db->query($sql, array($new, $old)); 113 } 114 } 115 116 /** 117 * Update the page-schema assignments 118 * 119 * @param string $old old page id 120 * @param string $new new page id 121 */ 122 protected function updateAssignments($old, $new) 123 { 124 // assignments 125 $sql = "UPDATE schema_assignments SET pid = ? WHERE pid = ?"; 126 $this->db->query($sql, array($new, $old)); 127 // make sure assignments still match patterns; 128 $assignments = Assignments::getInstance(); 129 $assignments->reevaluatePageAssignments($new); 130 } 131 132 /** 133 * Update the Title information for the moved page 134 * 135 * @param string $old old page id 136 * @param string $new new page id 137 */ 138 protected function updateTitles($old, $new) 139 { 140 $sql = "UPDATE titles SET pid = ? WHERE pid = ?"; 141 $this->db->query($sql, array($new, $old)); 142 } 143 144 /** 145 * Update the ID in a given column 146 * 147 * @param Schema $schema 148 * @param Column $col 149 * @param string $old old page id 150 * @param string $new new page id 151 * @param bool $hashes could the ID have a hash part? (for Page type) 152 */ 153 protected function updateColumnID(Schema $schema, Column $col, $old, $new, $hashes = false) 154 { 155 $colref = $col->getColref(); 156 $table = $schema->getTable(); 157 158 if ($col->isMulti()) { 159 /** @noinspection SqlResolve */ 160 $sql = "UPDATE multi_$table 161 SET value = REPLACE(value, ?, ?) 162 WHERE value LIKE ? 163 AND colref = $colref 164 AND latest = 1"; 165 } else { 166 /** @noinspection SqlResolve */ 167 $sql = "UPDATE data_$table 168 SET col$colref = REPLACE(col$colref, ?, ?) 169 WHERE col$colref LIKE ? 170 AND latest = 1"; 171 } 172 $this->db->query($sql, $old, $new, $old); // exact match 173 if ($hashes) { 174 $this->db->query($sql, $old, $new, "$old#%"); // match with hashes 175 } 176 if (get_class($col->getType()) === Lookup::class) { 177 $this->db->query($sql, $old, $new, "[\"$old\",%]"); // match JSON string 178 if ($hashes) { 179 $this->db->query($sql, $old, $new, "[\"$old#%\",%]"); // match JSON string with hash 180 } 181 } 182 } 183 184 /** 185 * Update a Lookup type column 186 * 187 * Lookups contain a page id when the referenced schema is a data schema 188 * 189 * @param Schema $schema 190 * @param Column $col 191 * @param string $old old page id 192 * @param string $new new page id 193 */ 194 protected function updateColumnLookup(Schema $schema, Column $col, $old, $new) 195 { 196 $tconf = $col->getType()->getConfig(); 197 $ref = new Schema($tconf['schema']); 198 if (!$ref->getId()) return; // this schema does not exist 199 if (!$ref->getTimeStamp()) return; // a lookup is referenced, nothing to do 200 $this->updateColumnID($schema, $col, $old, $new); 201 } 202} 203