Merge branch 'MDL-33441' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / xmldb / xmldb_structure.php
blob41e48bb12d8219766d97c184871a341ab73c4236
1 <?php
3 ///////////////////////////////////////////////////////////////////////////
4 // //
5 // NOTICE OF COPYRIGHT //
6 // //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment //
8 // http://moodle.com //
9 // //
10 // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
11 // (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
12 // //
13 // This program is free software; you can redistribute it and/or modify //
14 // it under the terms of the GNU General Public License as published by //
15 // the Free Software Foundation; either version 2 of the License, or //
16 // (at your option) any later version. //
17 // //
18 // This program is distributed in the hope that it will be useful, //
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
21 // GNU General Public License for more details: //
22 // //
23 // http://www.gnu.org/copyleft/gpl.html //
24 // //
25 ///////////////////////////////////////////////////////////////////////////
27 /// This class represent one XMLDB structure
29 class xmldb_structure extends xmldb_object {
31 var $path;
32 var $version;
33 var $tables;
35 /**
36 * Creates one new xmldb_structure
38 function __construct($name) {
39 parent::__construct($name);
40 $this->path = NULL;
41 $this->version = NULL;
42 $this->tables = array();
45 /**
46 * Returns the path of the structure
48 function getPath() {
49 return $this->path;
52 /**
53 * Returns the version of the structure
55 function getVersion() {
56 return $this->version;
59 /**
60 * Returns one xmldb_table
62 function &getTable($tablename) {
63 $i = $this->findTableInArray($tablename);
64 if ($i !== NULL) {
65 return $this->tables[$i];
67 $null = NULL;
68 return $null;
71 /**
72 * Returns the position of one table in the array.
74 function &findTableInArray($tablename) {
75 foreach ($this->tables as $i => $table) {
76 if ($tablename == $table->getName()) {
77 return $i;
80 $null = NULL;
81 return $null;
84 /**
85 * This function will reorder the array of tables
87 function orderTables() {
88 $result = $this->orderElements($this->tables);
89 if ($result) {
90 $this->setTables($result);
91 return true;
92 } else {
93 return false;
97 /**
98 * Returns the tables of the structure
100 function &getTables() {
101 return $this->tables;
105 * Set the structure version
107 function setVersion($version) {
108 $this->version = $version;
112 * Add one table to the structure, allowing to specify the desired order
113 * If it's not specified, then the table is added at the end.
115 function addTable(&$table, $after=NULL) {
117 /// Calculate the previous and next tables
118 $prevtable = NULL;
119 $nexttable = NULL;
121 if (!$after) {
122 $alltables =& $this->getTables();
123 if ($alltables) {
124 end($alltables);
125 $prevtable =& $alltables[key($alltables)];
127 } else {
128 $prevtable =& $this->getTable($after);
130 if ($prevtable && $prevtable->getNext()) {
131 $nexttable =& $this->getTable($prevtable->getNext());
134 /// Set current table previous and next attributes
135 if ($prevtable) {
136 $table->setPrevious($prevtable->getName());
137 $prevtable->setNext($table->getName());
139 if ($nexttable) {
140 $table->setNext($nexttable->getName());
141 $nexttable->setPrevious($table->getName());
143 /// Some more attributes
144 $table->setLoaded(true);
145 $table->setChanged(true);
146 /// Add the new table
147 $this->tables[] =& $table;
148 /// Reorder the whole structure
149 $this->orderTables($this->tables);
150 /// Recalculate the hash
151 $this->calculateHash(true);
152 /// We have one new table, so the structure has changed
153 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
154 $this->setChanged(true);
158 * Delete one table from the Structure
160 function deleteTable($tablename) {
162 $table =& $this->getTable($tablename);
163 if ($table) {
164 $i = $this->findTableInArray($tablename);
165 /// Look for prev and next table
166 $prevtable =& $this->getTable($table->getPrevious());
167 $nexttable =& $this->getTable($table->getNext());
168 /// Change their previous and next attributes
169 if ($prevtable) {
170 $prevtable->setNext($table->getNext());
172 if ($nexttable) {
173 $nexttable->setPrevious($table->getPrevious());
175 /// Delete the table
176 unset($this->tables[$i]);
177 /// Reorder the tables
178 $this->orderTables($this->tables);
179 /// Recalculate the hash
180 $this->calculateHash(true);
181 /// We have one deleted table, so the structure has changed
182 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
183 $this->setChanged(true);
188 * Set the tables
190 function setTables(&$tables) {
191 $this->tables = $tables;
195 * Load data from XML to the structure
197 function arr2xmldb_structure($xmlarr) {
199 global $CFG;
201 $result = true;
203 /// Debug the structure
204 /// traverse_xmlize($xmlarr); //Debug
205 /// print_object ($GLOBALS['traverse_array']); //Debug
206 /// $GLOBALS['traverse_array']=""; //Debug
208 /// Process structure attributes (path, comment and version)
209 if (isset($xmlarr['XMLDB']['@']['PATH'])) {
210 $this->path = trim($xmlarr['XMLDB']['@']['PATH']);
211 } else {
212 $this->errormsg = 'Missing PATH attribute';
213 $this->debug($this->errormsg);
214 $result = false;
216 if (isset($xmlarr['XMLDB']['@']['VERSION'])) {
217 $this->version = trim($xmlarr['XMLDB']['@']['VERSION']);
218 } else {
219 $this->errormsg = 'Missing VERSION attribute';
220 $this->debug($this->errormsg);
221 $result = false;
223 if (isset($xmlarr['XMLDB']['@']['COMMENT'])) {
224 $this->comment = trim($xmlarr['XMLDB']['@']['COMMENT']);
225 } else if (!empty($CFG->xmldbdisablecommentchecking)) {
226 $this->comment = '';
227 } else {
228 $this->errormsg = 'Missing COMMENT attribute';
229 $this->debug($this->errormsg);
230 $result = false;
233 /// Iterate over tables
234 if (isset($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'])) {
235 foreach ($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'] as $xmltable) {
236 if (!$result) { //Skip on error
237 continue;
239 $name = trim($xmltable['@']['NAME']);
240 $table = new xmldb_table($name);
241 $table->arr2xmldb_table($xmltable);
242 $this->tables[] = $table;
243 if (!$table->isLoaded()) {
244 $this->errormsg = 'Problem loading table ' . $name;
245 $this->debug($this->errormsg);
246 $result = false;
249 } else {
250 $this->errormsg = 'Missing TABLES section';
251 $this->debug($this->errormsg);
252 $result = false;
255 /// Perform some general checks over tables
256 if ($result && $this->tables) {
257 /// Check tables names are ok (lowercase, a-z _-)
258 if (!$this->checkNameValues($this->tables)) {
259 $this->errormsg = 'Some TABLES name values are incorrect';
260 $this->debug($this->errormsg);
261 $result = false;
263 /// Check previous & next are ok (duplicates and existing tables)
264 $this->fixPrevNext($this->tables);
265 if ($result && !$this->checkPreviousNextValues($this->tables)) {
266 $this->errormsg = 'Some TABLES previous/next values are incorrect';
267 $this->debug($this->errormsg);
268 $result = false;
270 /// Order tables
271 if ($result && !$this->orderTables($this->tables)) {
272 $this->errormsg = 'Error ordering the tables';
273 $this->debug($this->errormsg);
274 $result = false;
278 /// Set some attributes
279 if ($result) {
280 $this->loaded = true;
282 $this->calculateHash();
283 return $result;
287 * This function calculate and set the hash of one xmldb_structure
289 function calculateHash($recursive = false) {
290 if (!$this->loaded) {
291 $this->hash = NULL;
292 } else {
293 $key = $this->name . $this->path . $this->comment;
294 if ($this->tables) {
295 foreach ($this->tables as $tbl) {
296 $table =& $this->getTable($tbl->getName());
297 if ($recursive) {
298 $table->calculateHash($recursive);
300 $key .= $table->getHash();
303 $this->hash = md5($key);
308 * This function will output the XML text for one structure
310 function xmlOutput() {
311 $o = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
312 $o.= '<XMLDB PATH="' . $this->path . '"';
313 $o.= ' VERSION="' . $this->version . '"';
314 if ($this->comment) {
315 $o.= ' COMMENT="' . htmlspecialchars($this->comment) . '"'."\n";
317 $rel = array_fill(0, count(explode('/', $this->path)), '..');
318 $rel = implode('/', $rel);
319 $o.= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n";
320 $o.= ' xsi:noNamespaceSchemaLocation="'.$rel.'/lib/xmldb/xmldb.xsd"'."\n";
321 $o.= '>' . "\n";
322 /// Now the tables
323 if ($this->tables) {
324 $o.= ' <TABLES>' . "\n";
325 foreach ($this->tables as $table) {
326 $o.= $table->xmlOutput();
328 $o.= ' </TABLES>' . "\n";
330 $o.= '</XMLDB>';
332 return $o;
336 * This function returns the number of uses of one table inside
337 * a whole XMLDStructure. Useful to detect if the table must be
338 * locked. Return false if no uses are found.
340 function getTableUses($tablename) {
342 $uses = array();
344 /// Check if some foreign key in the whole structure is using it
345 /// (by comparing the reftable with the tablename)
346 $alltables = $this->getTables();
347 if ($alltables) {
348 foreach ($alltables as $table) {
349 $keys = $table->getKeys();
350 if ($keys) {
351 foreach ($keys as $key) {
352 if ($key->getType() == XMLDB_KEY_FOREIGN) {
353 if ($tablename == $key->getRefTable()) {
354 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
362 /// Return result
363 if (!empty($uses)) {
364 return $uses;
365 } else {
366 return false;
371 * This function returns the number of uses of one field inside
372 * a whole xmldb_structure. Useful to detect if the field must be
373 * locked. Return false if no uses are found.
375 function getFieldUses($tablename, $fieldname) {
377 $uses = array();
379 /// Check if any key in the table is using it
380 $table = $this->getTable($tablename);
381 if ($keys = $table->getKeys()) {
382 foreach ($keys as $key) {
383 if (in_array($fieldname, $key->getFields()) ||
384 in_array($fieldname, $key->getRefFields())) {
385 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
389 /// Check if any index in the table is using it
390 $table = $this->getTable($tablename);
391 if ($indexes = $table->getIndexes()) {
392 foreach ($indexes as $index) {
393 if (in_array($fieldname, $index->getFields())) {
394 $uses[] = 'table ' . $table->getName() . ' index ' . $index->getName();
398 /// Check if some foreign key in the whole structure is using it
399 /// By comparing the reftable and refields with the field)
400 $alltables = $this->getTables();
401 if ($alltables) {
402 foreach ($alltables as $table) {
403 $keys = $table->getKeys();
404 if ($keys) {
405 foreach ($keys as $key) {
406 if ($key->getType() == XMLDB_KEY_FOREIGN) {
407 if ($tablename == $key->getRefTable()) {
408 $reffieds = $key->getRefFields();
409 if (in_array($fieldname, $key->getRefFields())) {
410 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
419 /// Return result
420 if (!empty($uses)) {
421 return $uses;
422 } else {
423 return false;
428 * This function returns the number of uses of one key inside
429 * a whole xmldb_structure. Useful to detect if the key must be
430 * locked. Return false if no uses are found.
432 function getKeyUses($tablename, $keyname) {
434 $uses = array();
436 /// Check if some foreign key in the whole structure is using it
437 /// (by comparing the reftable and reffields with the fields in the key)
438 $mytable = $this->getTable($tablename);
439 $mykey = $mytable->getKey($keyname);
440 $alltables = $this->getTables();
441 if ($alltables && $mykey) {
442 foreach ($alltables as $table) {
443 $allkeys = $table->getKeys();
444 if ($allkeys) {
445 foreach ($allkeys as $key) {
446 if ($key->getType() != XMLDB_KEY_FOREIGN) {
447 continue;
449 if ($key->getRefTable() == $tablename &&
450 implode(',', $key->getRefFields()) == implode(',', $mykey->getFields())) {
451 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
458 /// Return result
459 if (!empty($uses)) {
460 return $uses;
461 } else {
462 return false;
467 * This function returns the number of uses of one index inside
468 * a whole xmldb_structure. Useful to detect if the index must be
469 * locked. Return false if no uses are found.
471 function getIndexUses($tablename, $indexname) {
473 $uses = array();
475 /// Nothing to check, beause indexes haven't uses! Leave it here
476 /// for future checks...
478 /// Return result
479 if (!empty($uses)) {
480 return $uses;
481 } else {
482 return false;
487 * This function will return all the errors found in one structure
488 * looking recursively inside each table. Returns
489 * an array of errors or false
491 function getAllErrors() {
493 $errors = array();
494 /// First the structure itself
495 if ($this->getError()) {
496 $errors[] = $this->getError();
498 /// Delegate to tables
499 if ($tables = $this->getTables()) {
500 foreach ($tables as $table) {
501 if ($tableerrors = $table->getAllErrors()) {
505 /// Add them to the errors array
506 if ($tableerrors) {
507 $errors = array_merge($errors, $tableerrors);
510 /// Return decision
511 if (count($errors)) {
512 return $errors;
513 } else {
514 return false;