See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This class represent one XMLDB structure 19 * 20 * @package core_xmldb 21 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com 22 * 2001-3001 Eloy Lafuente (stronk7) http://contiento.com 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 29 class xmldb_structure extends xmldb_object { 30 31 /** @var string */ 32 protected $path; 33 34 /** @var string */ 35 protected $version; 36 37 /** @var array tables */ 38 protected $tables; 39 40 /** 41 * Creates one new xmldb_structure 42 * @param string $name 43 */ 44 public function __construct($name) { 45 parent::__construct($name); 46 $this->path = null; 47 $this->version = null; 48 $this->tables = array(); 49 } 50 51 /** 52 * Returns the path of the structure 53 * @return string 54 */ 55 public function getPath() { 56 return $this->path; 57 } 58 59 /** 60 * Returns the version of the structure 61 * @return string 62 */ 63 public function getVersion() { 64 return $this->version; 65 } 66 67 /** 68 * Returns one xmldb_table 69 * @param string $tablename 70 * @return xmldb_table 71 */ 72 public function getTable($tablename) { 73 $i = $this->findTableInArray($tablename); 74 if ($i !== null) { 75 return $this->tables[$i]; 76 } 77 return null; 78 } 79 80 /** 81 * Returns the position of one table in the array. 82 * @param string $tablename 83 * @return mixed 84 */ 85 public function findTableInArray($tablename) { 86 foreach ($this->tables as $i => $table) { 87 if ($tablename == $table->getName()) { 88 return $i; 89 } 90 } 91 return null; 92 } 93 94 /** 95 * This function will reorder the array of tables 96 * @return bool success 97 */ 98 public function orderTables() { 99 $result = $this->orderElements($this->tables); 100 if ($result) { 101 $this->setTables($result); 102 return true; 103 } else { 104 return false; 105 } 106 } 107 108 /** 109 * Returns the tables of the structure 110 * @return array 111 */ 112 public function getTables() { 113 return $this->tables; 114 } 115 116 /** 117 * Set the structure version 118 * @param string version 119 */ 120 public function setVersion($version) { 121 $this->version = $version; 122 } 123 124 /** 125 * Add one table to the structure, allowing to specify the desired order 126 * If it's not specified, then the table is added at the end. 127 * @param xmldb_table $table 128 * @param mixed $after 129 */ 130 public function addTable($table, $after=null) { 131 132 // Calculate the previous and next tables 133 $prevtable = null; 134 $nexttable = null; 135 136 if (!$after) { 137 if ($this->tables) { 138 end($this->tables); 139 $prevtable = $this->tables[key($this->tables)]; 140 } 141 } else { 142 $prevtable = $this->getTable($after); 143 } 144 if ($prevtable && $prevtable->getNext()) { 145 $nexttable = $this->getTable($prevtable->getNext()); 146 } 147 148 // Set current table previous and next attributes 149 if ($prevtable) { 150 $table->setPrevious($prevtable->getName()); 151 $prevtable->setNext($table->getName()); 152 } 153 if ($nexttable) { 154 $table->setNext($nexttable->getName()); 155 $nexttable->setPrevious($table->getName()); 156 } 157 // Some more attributes 158 $table->setLoaded(true); 159 $table->setChanged(true); 160 // Add the new table 161 $this->tables[] = $table; 162 // Reorder the whole structure 163 $this->orderTables($this->tables); 164 // Recalculate the hash 165 $this->calculateHash(true); 166 // We have one new table, so the structure has changed 167 $this->setVersion(userdate(time(), '%Y%m%d', 99, false)); 168 $this->setChanged(true); 169 } 170 171 /** 172 * Delete one table from the Structure 173 * @param string $tablename 174 */ 175 public function deleteTable($tablename) { 176 177 $table = $this->getTable($tablename); 178 if ($table) { 179 $i = $this->findTableInArray($tablename); 180 // Look for prev and next table 181 $prevtable = $this->getTable($table->getPrevious()); 182 $nexttable = $this->getTable($table->getNext()); 183 // Change their previous and next attributes 184 if ($prevtable) { 185 $prevtable->setNext($table->getNext()); 186 } 187 if ($nexttable) { 188 $nexttable->setPrevious($table->getPrevious()); 189 } 190 // Delete the table 191 unset($this->tables[$i]); 192 // Reorder the tables 193 $this->orderTables($this->tables); 194 // Recalculate the hash 195 $this->calculateHash(true); 196 // We have one deleted table, so the structure has changed 197 $this->setVersion(userdate(time(), '%Y%m%d', 99, false)); 198 $this->setChanged(true); 199 } 200 } 201 202 /** 203 * Set the tables 204 * @param array $tables 205 */ 206 public function setTables($tables) { 207 $this->tables = $tables; 208 } 209 210 /** 211 * Load data from XML to the structure 212 * @param array $xmlarr 213 * @return bool 214 */ 215 public function arr2xmldb_structure($xmlarr) { 216 217 global $CFG; 218 219 $result = true; 220 221 // Debug the structure 222 // traverse_xmlize($xmlarr); //Debug 223 // print_object ($GLOBALS['traverse_array']); //Debug 224 // $GLOBALS['traverse_array']=""; //Debug 225 226 // Process structure attributes (path, comment and version) 227 if (isset($xmlarr['XMLDB']['@']['PATH'])) { 228 $this->path = trim($xmlarr['XMLDB']['@']['PATH']); 229 } else { 230 $this->errormsg = 'Missing PATH attribute'; 231 $this->debug($this->errormsg); 232 $result = false; 233 } 234 // Normalize paths to compare them. 235 $filepath = realpath($this->name); // File path comes in name. 236 $filename = basename($filepath); 237 $normalisedpath = $this->path; 238 if ($CFG->admin !== 'admin') { 239 $needle = 'admin/'; 240 if (strpos($this->path, $needle) === 0) { 241 $normalisedpath = substr_replace($this->path, "$CFG->admin/", 0, strlen($needle)); 242 } 243 } 244 $structurepath = realpath($CFG->dirroot . DIRECTORY_SEPARATOR . $normalisedpath . DIRECTORY_SEPARATOR . $filename); 245 if ($filepath !== $structurepath) { 246 $relativepath = dirname(str_replace(realpath($CFG->dirroot) . DIRECTORY_SEPARATOR, '', $filepath)); 247 $this->errormsg = 'PATH attribute does not match file directory: ' . $relativepath; 248 $this->debug($this->errormsg); 249 $result = false; 250 } 251 if (isset($xmlarr['XMLDB']['@']['VERSION'])) { 252 $this->version = trim($xmlarr['XMLDB']['@']['VERSION']); 253 } else { 254 $this->errormsg = 'Missing VERSION attribute'; 255 $this->debug($this->errormsg); 256 $result = false; 257 } 258 if (isset($xmlarr['XMLDB']['@']['COMMENT'])) { 259 $this->comment = trim($xmlarr['XMLDB']['@']['COMMENT']); 260 } else if (!empty($CFG->xmldbdisablecommentchecking)) { 261 $this->comment = ''; 262 } else { 263 $this->errormsg = 'Missing COMMENT attribute'; 264 $this->debug($this->errormsg); 265 $result = false; 266 } 267 268 // Iterate over tables 269 if (isset($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'])) { 270 foreach ($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'] as $xmltable) { 271 if (!$result) { //Skip on error 272 continue; 273 } 274 $name = trim($xmltable['@']['NAME']); 275 $table = new xmldb_table($name); 276 $table->arr2xmldb_table($xmltable); 277 $this->tables[] = $table; 278 if (!$table->isLoaded()) { 279 $this->errormsg = 'Problem loading table ' . $name; 280 $this->debug($this->errormsg); 281 $result = false; 282 } 283 } 284 } else { 285 $this->errormsg = 'Missing TABLES section'; 286 $this->debug($this->errormsg); 287 $result = false; 288 } 289 290 // Perform some general checks over tables 291 if ($result && $this->tables) { 292 // Check tables names are ok (lowercase, a-z _-) 293 if (!$this->checkNameValues($this->tables)) { 294 $this->errormsg = 'Some TABLES name values are incorrect'; 295 $this->debug($this->errormsg); 296 $result = false; 297 } 298 // Compute prev/next. 299 $this->fixPrevNext($this->tables); 300 // Order tables 301 if ($result && !$this->orderTables($this->tables)) { 302 $this->errormsg = 'Error ordering the tables'; 303 $this->debug($this->errormsg); 304 $result = false; 305 } 306 } 307 308 // Set some attributes 309 if ($result) { 310 $this->loaded = true; 311 } 312 $this->calculateHash(); 313 return $result; 314 } 315 316 /** 317 * This function calculate and set the hash of one xmldb_structure 318 * @param bool $recursive 319 */ 320 public function calculateHash($recursive = false) { 321 if (!$this->loaded) { 322 $this->hash = null; 323 } else { 324 $key = $this->name . $this->path . $this->comment; 325 if ($this->tables) { 326 foreach ($this->tables as $tbl) { 327 $table = $this->getTable($tbl->getName()); 328 if ($recursive) { 329 $table->calculateHash($recursive); 330 } 331 $key .= $table->getHash(); 332 } 333 } 334 $this->hash = md5($key); 335 } 336 } 337 338 /** 339 * This function will output the XML text for one structure 340 * @return string 341 */ 342 public function xmlOutput() { 343 $o = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n"; 344 $o.= '<XMLDB PATH="' . $this->path . '"'; 345 $o.= ' VERSION="' . $this->version . '"'; 346 if ($this->comment) { 347 $o.= ' COMMENT="' . htmlspecialchars($this->comment, ENT_COMPAT) . '"'."\n"; 348 } 349 $rel = array_fill(0, count(explode('/', $this->path)), '..'); 350 $rel = implode('/', $rel); 351 $o.= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n"; 352 $o.= ' xsi:noNamespaceSchemaLocation="'.$rel.'/lib/xmldb/xmldb.xsd"'."\n"; 353 $o.= '>' . "\n"; 354 // Now the tables 355 if ($this->tables) { 356 $o.= ' <TABLES>' . "\n"; 357 foreach ($this->tables as $table) { 358 $o.= $table->xmlOutput(); 359 } 360 $o.= ' </TABLES>' . "\n"; 361 } 362 $o.= '</XMLDB>' . "\n"; 363 364 return $o; 365 } 366 367 /** 368 * This function returns the number of uses of one table inside 369 * a whole XMLDStructure. Useful to detect if the table must be 370 * locked. Return false if no uses are found. 371 * @param string $tablename 372 * @return mixed 373 */ 374 public function getTableUses($tablename) { 375 376 $uses = array(); 377 378 // Check if some foreign key in the whole structure is using it 379 // (by comparing the reftable with the tablename) 380 if ($this->tables) { 381 foreach ($this->tables as $table) { 382 $keys = $table->getKeys(); 383 if ($keys) { 384 foreach ($keys as $key) { 385 if ($key->getType() == XMLDB_KEY_FOREIGN) { 386 if ($tablename == $key->getRefTable()) { 387 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName(); 388 } 389 } 390 } 391 } 392 } 393 } 394 395 // Return result 396 if (!empty($uses)) { 397 return $uses; 398 } else { 399 return false; 400 } 401 } 402 403 /** 404 * This function returns the number of uses of one field inside 405 * a whole xmldb_structure. Useful to detect if the field must be 406 * locked. Return false if no uses are found. 407 * @param string $tablename 408 * @param string $fieldname 409 * @return mixed 410 */ 411 public function getFieldUses($tablename, $fieldname) { 412 413 $uses = array(); 414 415 // Check if any key in the table is using it 416 $table = $this->getTable($tablename); 417 if ($keys = $table->getKeys()) { 418 foreach ($keys as $key) { 419 if (in_array($fieldname, $key->getFields()) || 420 in_array($fieldname, $key->getRefFields())) { 421 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName(); 422 } 423 } 424 } 425 // Check if any index in the table is using it 426 $table = $this->getTable($tablename); 427 if ($indexes = $table->getIndexes()) { 428 foreach ($indexes as $index) { 429 if (in_array($fieldname, $index->getFields())) { 430 $uses[] = 'table ' . $table->getName() . ' index ' . $index->getName(); 431 } 432 } 433 } 434 // Check if some foreign key in the whole structure is using it 435 // By comparing the reftable and refields with the field) 436 if ($this->tables) { 437 foreach ($this->tables as $table) { 438 $keys = $table->getKeys(); 439 if ($keys) { 440 foreach ($keys as $key) { 441 if ($key->getType() == XMLDB_KEY_FOREIGN) { 442 if ($tablename == $key->getRefTable()) { 443 $reffieds = $key->getRefFields(); 444 if (in_array($fieldname, $key->getRefFields())) { 445 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName(); 446 } 447 } 448 } 449 } 450 } 451 } 452 } 453 454 // Return result 455 if (!empty($uses)) { 456 return $uses; 457 } else { 458 return false; 459 } 460 } 461 462 /** 463 * This function returns the number of uses of one key inside 464 * a whole xmldb_structure. Useful to detect if the key must be 465 * locked. Return false if no uses are found. 466 * @param string $tablename 467 * @param string $keyname 468 * @return mixed 469 */ 470 public function getKeyUses($tablename, $keyname) { 471 472 $uses = array(); 473 474 // Check if some foreign key in the whole structure is using it 475 // (by comparing the reftable and reffields with the fields in the key) 476 $mytable = $this->getTable($tablename); 477 $mykey = $mytable->getKey($keyname); 478 if ($this->tables && $mykey) { 479 foreach ($this->tables as $table) { 480 $allkeys = $table->getKeys(); 481 if ($allkeys) { 482 foreach ($allkeys as $key) { 483 if ($key->getType() != XMLDB_KEY_FOREIGN) { 484 continue; 485 } 486 if ($key->getRefTable() == $tablename && 487 implode(',', $key->getRefFields()) == implode(',', $mykey->getFields())) { 488 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName(); 489 } 490 } 491 } 492 } 493 } 494 495 // Return result 496 if (!empty($uses)) { 497 return $uses; 498 } else { 499 return false; 500 } 501 } 502 503 /** 504 * This function returns the number of uses of one index inside 505 * a whole xmldb_structure. Useful to detect if the index must be 506 * locked. Return false if no uses are found. 507 * @param string $tablename 508 * @param string $indexname 509 * @return mixed 510 */ 511 public function getIndexUses($tablename, $indexname) { 512 513 $uses = array(); 514 515 // Nothing to check, because indexes haven't uses! Leave it here 516 // for future checks... 517 518 // Return result 519 if (!empty($uses)) { 520 return $uses; 521 } else { 522 return false; 523 } 524 } 525 526 /** 527 * This function will return all the errors found in one structure 528 * looking recursively inside each table. Returns 529 * an array of errors or false 530 * @return mixed 531 */ 532 public function getAllErrors() { 533 534 $errors = array(); 535 // First the structure itself 536 if ($this->getError()) { 537 $errors[] = $this->getError(); 538 } 539 // Delegate to tables 540 if ($this->tables) { 541 foreach ($this->tables as $table) { 542 if ($tableerrors = $table->getAllErrors()) { 543 544 } 545 } 546 // Add them to the errors array 547 if ($tableerrors) { 548 $errors = array_merge($errors, $tableerrors); 549 } 550 } 551 // Return decision 552 if (count($errors)) { 553 return $errors; 554 } else { 555 return false; 556 } 557 } 558 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body