2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * This class represent one XMLDB structure
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
26 defined('MOODLE_INTERNAL') ||
die();
29 class xmldb_structure
extends xmldb_object
{
37 /** @var array tables */
41 * Creates one new xmldb_structure
44 public function __construct($name) {
45 parent
::__construct($name);
47 $this->version
= null;
48 $this->tables
= array();
52 * Returns the path of the structure
55 public function getPath() {
60 * Returns the version of the structure
63 public function getVersion() {
64 return $this->version
;
68 * Returns one xmldb_table
69 * @param string $tablename
72 public function getTable($tablename) {
73 $i = $this->findTableInArray($tablename);
75 return $this->tables
[$i];
81 * Returns the position of one table in the array.
82 * @param string $tablename
85 public function findTableInArray($tablename) {
86 foreach ($this->tables
as $i => $table) {
87 if ($tablename == $table->getName()) {
95 * This function will reorder the array of tables
96 * @return bool success
98 public function orderTables() {
99 $result = $this->orderElements($this->tables
);
101 $this->setTables($result);
109 * Returns the tables of the structure
112 public function getTables() {
113 return $this->tables
;
117 * Set the structure version
118 * @param string version
120 public function setVersion($version) {
121 $this->version
= $version;
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
130 public function addTable($table, $after=null) {
132 // Calculate the previous and next tables
139 $prevtable = $this->tables
[key($this->tables
)];
142 $prevtable = $this->getTable($after);
144 if ($prevtable && $prevtable->getNext()) {
145 $nexttable = $this->getTable($prevtable->getNext());
148 // Set current table previous and next attributes
150 $table->setPrevious($prevtable->getName());
151 $prevtable->setNext($table->getName());
154 $table->setNext($nexttable->getName());
155 $nexttable->setPrevious($table->getName());
157 // Some more attributes
158 $table->setLoaded(true);
159 $table->setChanged(true);
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);
172 * Delete one table from the Structure
173 * @param string $tablename
175 public function deleteTable($tablename) {
177 $table = $this->getTable($tablename);
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
185 $prevtable->setNext($table->getNext());
188 $nexttable->setPrevious($table->getPrevious());
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);
204 * @param array $tables
206 public function setTables($tables) {
207 $this->tables
= $tables;
211 * Load data from XML to the structure
212 * @param array $xmlarr
215 public function arr2xmldb_structure($xmlarr) {
221 // Debug the structure
222 // traverse_xmlize($xmlarr); //Debug
223 // print_object ($GLOBALS['traverse_array']); //Debug
224 // $GLOBALS['traverse_array']=""; //Debug
226 // Process structure attributes (path, comment and version)
227 if (isset($xmlarr['XMLDB']['@']['PATH'])) {
228 $this->path
= trim($xmlarr['XMLDB']['@']['PATH']);
230 $this->errormsg
= 'Missing PATH attribute';
231 $this->debug($this->errormsg
);
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') {
240 if (strpos($this->path
, $needle) === 0) {
241 $normalisedpath = substr_replace($this->path
, "$CFG->admin/", 0, strlen($needle));
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
);
251 if (isset($xmlarr['XMLDB']['@']['VERSION'])) {
252 $this->version
= trim($xmlarr['XMLDB']['@']['VERSION']);
254 $this->errormsg
= 'Missing VERSION attribute';
255 $this->debug($this->errormsg
);
258 if (isset($xmlarr['XMLDB']['@']['COMMENT'])) {
259 $this->comment
= trim($xmlarr['XMLDB']['@']['COMMENT']);
260 } else if (!empty($CFG->xmldbdisablecommentchecking
)) {
263 $this->errormsg
= 'Missing COMMENT attribute';
264 $this->debug($this->errormsg
);
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
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
);
285 $this->errormsg
= 'Missing TABLES section';
286 $this->debug($this->errormsg
);
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
);
298 // Compute prev/next.
299 $this->fixPrevNext($this->tables
);
301 if ($result && !$this->orderTables($this->tables
)) {
302 $this->errormsg
= 'Error ordering the tables';
303 $this->debug($this->errormsg
);
308 // Set some attributes
310 $this->loaded
= true;
312 $this->calculateHash();
317 * This function calculate and set the hash of one xmldb_structure
318 * @param bool $recursive
320 public function calculateHash($recursive = false) {
321 if (!$this->loaded
) {
324 $key = $this->name
. $this->path
. $this->comment
;
326 foreach ($this->tables
as $tbl) {
327 $table = $this->getTable($tbl->getName());
329 $table->calculateHash($recursive);
331 $key .= $table->getHash();
334 $this->hash
= md5($key);
339 * This function will output the XML text for one structure
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
) . '"'."\n";
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";
356 $o.= ' <TABLES>' . "\n";
357 foreach ($this->tables
as $table) {
358 $o.= $table->xmlOutput();
360 $o.= ' </TABLES>' . "\n";
362 $o.= '</XMLDB>' . "\n";
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
374 public function getTableUses($tablename) {
378 // Check if some foreign key in the whole structure is using it
379 // (by comparing the reftable with the tablename)
381 foreach ($this->tables
as $table) {
382 $keys = $table->getKeys();
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();
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
411 public function getFieldUses($tablename, $fieldname) {
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();
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();
434 // Check if some foreign key in the whole structure is using it
435 // By comparing the reftable and refields with the field)
437 foreach ($this->tables
as $table) {
438 $keys = $table->getKeys();
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();
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
470 public function getKeyUses($tablename, $keyname) {
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();
482 foreach ($allkeys as $key) {
483 if ($key->getType() != XMLDB_KEY_FOREIGN
) {
486 if ($key->getRefTable() == $tablename &&
487 implode(',', $key->getRefFields()) == implode(',', $mykey->getFields())) {
488 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
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
511 public function getIndexUses($tablename, $indexname) {
515 // Nothing to check, because indexes haven't uses! Leave it here
516 // for future checks...
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
532 public function getAllErrors() {
535 // First the structure itself
536 if ($this->getError()) {
537 $errors[] = $this->getError();
539 // Delegate to tables
541 foreach ($this->tables
as $table) {
542 if ($tableerrors = $table->getAllErrors()) {
546 // Add them to the errors array
548 $errors = array_merge($errors, $tableerrors);
552 if (count($errors)) {