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