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