3 ///////////////////////////////////////////////////////////////////////////
5 // NOTICE OF COPYRIGHT //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment //
8 // http://moodle.com //
10 // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
11 // (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
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. //
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: //
23 // http://www.gnu.org/copyleft/gpl.html //
25 ///////////////////////////////////////////////////////////////////////////
27 /// This class represent one XMLDB structure
29 class XMLDBStructure
extends XMLDBObject
{
37 * Creates one new XMLDBStructure
39 function XMLDBStructure($name) {
40 parent
::XMLDBObject($name);
42 $this->version
= NULL;
43 $this->tables
= array();
44 $this->statements
= array();
48 * Returns the path of the structure
55 * Returns the version of the structure
57 function getVersion() {
58 return $this->version
;
62 * Returns one XMLDBTable
64 function &getTable($tablename) {
65 $i = $this->findTableInArray($tablename);
67 return $this->tables
[$i];
74 * Returns the position of one table in the array.
76 function &findTableInArray($tablename) {
77 foreach ($this->tables
as $i => $table) {
78 if ($tablename == $table->getName()) {
87 * Returns the position of one statement in the array.
89 function &findStatementInArray($statementname) {
90 foreach ($this->statements
as $i => $statement) {
91 if ($statementname == $statement->getName()) {
100 * This function will reorder the array of tables
102 function orderTables() {
103 $result = $this->orderElements($this->tables
);
105 $this->setTables($result);
113 * This function will reorder the array of statements
115 function orderStatements() {
116 $result = $this->orderElements($this->statements
);
118 $this->setStatements($result);
126 * Returns the tables of the structure
128 function &getTables() {
129 return $this->tables
;
133 * Returns one XMLDBStatement
135 function &getStatement($statementname) {
136 $i = $this->findStatementInArray($statementname);
138 return $this->statements
[$i];
145 * Returns the statements of the structure
147 function &getStatements() {
148 return $this->statements
;
152 * Set the structure version
154 function setVersion($version) {
155 $this->version
= $version;
159 * Add one table to the structure, allowing to specify the desired order
160 * If it's not specified, then the table is added at the end.
162 function addTable(&$table, $after=NULL) {
164 /// Calculate the previous and next tables
169 $alltables =& $this->getTables();
172 $prevtable =& $alltables[key($alltables)];
175 $prevtable =& $this->getTable($after);
177 if ($prevtable && $prevtable->getNext()) {
178 $nexttable =& $this->getTable($prevtable->getNext());
181 /// Set current table previous and next attributes
183 $table->setPrevious($prevtable->getName());
184 $prevtable->setNext($table->getName());
187 $table->setNext($nexttable->getName());
188 $nexttable->setPrevious($table->getName());
190 /// Some more attributes
191 $table->setLoaded(true);
192 $table->setChanged(true);
193 /// Add the new table
194 $this->tables
[] =& $table;
195 /// Reorder the whole structure
196 $this->orderTables($this->tables
);
197 /// Recalculate the hash
198 $this->calculateHash(true);
199 /// We have one new table, so the structure has changed
200 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
201 $this->setChanged(true);
205 * Add one statement to the structure, allowing to specify the desired order
206 * If it's not specified, then the statement is added at the end.
208 function addStatement(&$statement, $after=NULL) {
210 /// Calculate the previous and next tables
211 $prevstatement = NULL;
212 $nextstatement = NULL;
215 $allstatements =& $this->getStatements();
216 if ($allstatements) {
218 $prevstatement =& $allstatements[key($allstatements)];
221 $prevstatement =& $this->getStatement($after);
223 if ($prevstatement && $prevstatement->getNext()) {
224 $nextstatement =& $this->getStatement($prevstatement->getNext());
227 /// Set current statement previous and next attributes
228 if ($prevstatement) {
229 $statement->setPrevious($prevstatement->getName());
230 $prevstatement->setNext($statement->getName());
232 if ($nextstatement) {
233 $statement->setNext($nextstatement->getName());
234 $nextstatement->setPrevious($statement->getName());
236 /// Some more attributes
237 $statement->setLoaded(true);
238 $statement->setChanged(true);
239 /// Add the new statement
240 $this->statements
[] =& $statement;
241 /// Reorder the whole structure
242 $this->orderStatements($this->statements
);
243 /// Recalculate the hash
244 $this->calculateHash(true);
245 /// We have one new statement, so the structure has changed
246 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
247 $this->setChanged(true);
251 * Delete one table from the Structure
253 function deleteTable($tablename) {
255 $table =& $this->getTable($tablename);
257 $i = $this->findTableInArray($tablename);
260 /// Look for prev and next table
261 $prevtable =& $this->getTable($table->getPrevious());
262 $nexttable =& $this->getTable($table->getNext());
263 /// Change their previous and next attributes
265 $prevtable->setNext($table->getNext());
268 $nexttable->setPrevious($table->getPrevious());
271 unset($this->tables
[$i]);
272 /// Reorder the tables
273 $this->orderTables($this->tables
);
274 /// Recalculate the hash
275 $this->calculateHash(true);
276 /// We have one deleted table, so the structure has changed
277 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
278 $this->setChanged(true);
283 * Delete one statement from the Structure
285 function deleteStatement($statementname) {
287 $statement =& $this->getStatement($statementname);
289 $i = $this->findStatementInArray($statementname);
290 $prevstatement = NULL;
291 $nextstatement = NULL;
292 /// Look for prev and next statement
293 $prevstatement =& $this->getStatement($statement->getPrevious());
294 $nextstatement =& $this->getStatement($statement->getNext());
295 /// Change their previous and next attributes
296 if ($prevstatement) {
297 $prevstatement->setNext($statement->getNext());
299 if ($nextstatement) {
300 $nextstatement->setPrevious($statement->getPrevious());
302 /// Delete the statement
303 unset($this->statements
[$i]);
304 /// Reorder the statements
305 $this->orderStatements($this->statements
);
306 /// Recalculate the hash
307 $this->calculateHash(true);
308 /// We have one deleted statement, so the structure has changed
309 $this->setVersion(userdate(time(), '%Y%m%d', 99, false));
310 $this->setChanged(true);
317 function setTables(&$tables) {
318 $this->tables
= $tables;
324 function setStatements(&$statements) {
325 $this->statements
= $statements;
329 * Load data from XML to the structure
331 function arr2XMLDBStructure($xmlarr) {
337 /// Debug the structure
338 /// traverse_xmlize($xmlarr); //Debug
339 /// print_object ($GLOBALS['traverse_array']); //Debug
340 /// $GLOBALS['traverse_array']=""; //Debug
342 /// Process structure attributes (path, comment and version)
343 if (isset($xmlarr['XMLDB']['@']['PATH'])) {
344 $this->path
= trim($xmlarr['XMLDB']['@']['PATH']);
346 $this->errormsg
= 'Missing PATH attribute';
347 $this->debug($this->errormsg
);
350 if (isset($xmlarr['XMLDB']['@']['VERSION'])) {
351 $this->version
= trim($xmlarr['XMLDB']['@']['VERSION']);
353 $this->errormsg
= 'Missing VERSION attribute';
354 $this->debug($this->errormsg
);
357 if (isset($xmlarr['XMLDB']['@']['COMMENT'])) {
358 $this->comment
= trim($xmlarr['XMLDB']['@']['COMMENT']);
359 } else if (!empty($CFG->xmldbdisablecommentchecking
)) {
362 $this->errormsg
= 'Missing COMMENT attribute';
363 $this->debug($this->errormsg
);
367 /// Iterate over tables
368 if (isset($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'])) {
369 foreach ($xmlarr['XMLDB']['#']['TABLES']['0']['#']['TABLE'] as $xmltable) {
370 if (!$result) { //Skip on error
373 $name = trim($xmltable['@']['NAME']);
374 $table = new XMLDBTable($name);
375 $table->arr2XMLDBTable($xmltable);
376 $this->tables
[] = $table;
377 if (!$table->isLoaded()) {
378 $this->errormsg
= 'Problem loading table ' . $name;
379 $this->debug($this->errormsg
);
384 $this->errormsg
= 'Missing TABLES section';
385 $this->debug($this->errormsg
);
389 /// Perform some general checks over tables
390 if ($result && $this->tables
) {
391 /// Check tables names are ok (lowercase, a-z _-)
392 if (!$this->checkNameValues($this->tables
)) {
393 $this->errormsg
= 'Some TABLES name values are incorrect';
394 $this->debug($this->errormsg
);
397 /// Check previous & next are ok (duplicates and existing tables)
398 $this->fixPrevNext($this->tables
);
399 if ($result && !$this->checkPreviousNextValues($this->tables
)) {
400 $this->errormsg
= 'Some TABLES previous/next values are incorrect';
401 $this->debug($this->errormsg
);
405 if ($result && !$this->orderTables($this->tables
)) {
406 $this->errormsg
= 'Error ordering the tables';
407 $this->debug($this->errormsg
);
412 /// Iterate over statements
413 if (isset($xmlarr['XMLDB']['#']['STATEMENTS']['0']['#']['STATEMENT'])) {
414 foreach ($xmlarr['XMLDB']['#']['STATEMENTS']['0']['#']['STATEMENT'] as $xmlstatement) {
415 if (!$result) { //Skip on error
418 $name = trim($xmlstatement['@']['NAME']);
419 $statement = new XMLDBStatement($name);
420 $statement->arr2XMLDBStatement($xmlstatement);
421 $this->statements
[] = $statement;
422 if (!$statement->isLoaded()) {
423 $this->errormsg
= 'Problem loading statement ' . $name;
424 $this->debug($this->errormsg
);
430 /// Perform some general checks over statements
431 if ($result && $this->statements
) {
432 /// Check statements names are ok (lowercase, a-z _-)
433 if (!$this->checkNameValues($this->statements
)) {
434 $this->errormsg
= 'Some STATEMENTS name values are incorrect';
435 $this->debug($this->errormsg
);
438 /// Check previous & next are ok (duplicates and existing statements)
439 $this->fixPrevNext($this->statements
);
440 if ($result && !$this->checkPreviousNextValues($this->statements
)) {
441 $this->errormsg
= 'Some STATEMENTS previous/next values are incorrect';
442 $this->debug($this->errormsg
);
446 if ($result && !$this->orderStatements($this->statements
)) {
447 $this->errormsg
= 'Error ordering the statements';
448 $this->debug($this->errormsg
);
453 /// Set some attributes
455 $this->loaded
= true;
457 $this->calculateHash();
462 * This function calculate and set the hash of one XMLDBStructure
464 function calculateHash($recursive = false) {
465 if (!$this->loaded
) {
468 $key = $this->name
. $this->path
. $this->comment
;
470 foreach ($this->tables
as $tbl) {
471 $table =& $this->getTable($tbl->getName());
473 $table->calculateHash($recursive);
475 $key .= $table->getHash();
478 if ($this->statements
) {
479 foreach ($this->statements
as $sta) {
480 $statement =& $this->getStatement($sta->getName());
482 $statement->calculateHash($recursive);
484 $key .= $statement->getHash();
487 $this->hash
= md5($key);
492 * This function will output the XML text for one structure
494 function xmlOutput() {
495 $o = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
496 $o.= '<XMLDB PATH="' . $this->path
. '"';
497 $o.= ' VERSION="' . $this->version
. '"';
498 if ($this->comment
) {
499 $o.= ' COMMENT="' . htmlspecialchars($this->comment
) . '"'."\n";
501 $rel = array_fill(0, count(explode('/', $this->path
)), '..');
502 $rel = implode('/', $rel);
503 $o.= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n";
504 $o.= ' xsi:noNamespaceSchemaLocation="'.$rel.'/lib/xmldb/xmldb.xsd"'."\n";
508 $o.= ' <TABLES>' . "\n";
509 foreach ($this->tables
as $table) {
510 $o.= $table->xmlOutput();
512 $o.= ' </TABLES>' . "\n";
514 /// Now the statements
515 if ($this->statements
) {
516 $o.= ' <STATEMENTS>' . "\n";
517 foreach ($this->statements
as $statement) {
518 $o.= $statement->xmlOutput();
520 $o.= ' </STATEMENTS>' . "\n";
528 * This function returns the number of uses of one table inside
529 * a whole XMLDStructure. Useful to detect if the table must be
530 * locked. Return false if no uses are found.
532 function getTableUses($tablename) {
536 /// Check if some foreign key in the whole structure is using it
537 /// (by comparing the reftable with the tablename)
538 $alltables = $this->getTables();
540 foreach ($alltables as $table) {
541 $keys = $table->getKeys();
543 foreach ($keys as $key) {
544 if ($key->getType() == XMLDB_KEY_FOREIGN
) {
545 if ($tablename == $key->getRefTable()) {
546 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
563 * This function returns the number of uses of one field inside
564 * a whole XMLDBStructure. Useful to detect if the field must be
565 * locked. Return false if no uses are found.
567 function getFieldUses($tablename, $fieldname) {
571 /// Check if any key in the table is using it
572 $table = $this->getTable($tablename);
573 if ($keys = $table->getKeys()) {
574 foreach ($keys as $key) {
575 if (in_array($fieldname, $key->getFields()) ||
576 in_array($fieldname, $key->getRefFields())) {
577 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
581 /// Check if any index in the table is using it
582 $table = $this->getTable($tablename);
583 if ($indexes = $table->getIndexes()) {
584 foreach ($indexes as $index) {
585 if (in_array($fieldname, $index->getFields())) {
586 $uses[] = 'table ' . $table->getName() . ' index ' . $index->getName();
590 /// Check if some foreign key in the whole structure is using it
591 /// By comparing the reftable and refields with the field)
592 $alltables = $this->getTables();
594 foreach ($alltables as $table) {
595 $keys = $table->getKeys();
597 foreach ($keys as $key) {
598 if ($key->getType() == XMLDB_KEY_FOREIGN
) {
599 if ($tablename == $key->getRefTable()) {
600 $reffieds = $key->getRefFields();
601 if (in_array($fieldname, $key->getRefFields())) {
602 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
620 * This function returns the number of uses of one key inside
621 * a whole XMLDBStructure. Useful to detect if the key must be
622 * locked. Return false if no uses are found.
624 function getKeyUses($tablename, $keyname) {
628 /// Check if some foreign key in the whole structure is using it
629 /// (by comparing the reftable and reffields with the fields in the key)
630 $mytable = $this->getTable($tablename);
631 $mykey = $mytable->getKey($keyname);
632 $alltables = $this->getTables();
633 if ($alltables && $mykey) {
634 foreach ($alltables as $table) {
635 $allkeys = $table->getKeys();
637 foreach ($allkeys as $key) {
638 if ($key->getType() != XMLDB_KEY_FOREIGN
) {
641 if ($key->getRefTable() == $tablename &&
642 implode(',', $key->getRefFields()) == implode(',', $mykey->getFields())) {
643 $uses[] = 'table ' . $table->getName() . ' key ' . $key->getName();
659 * This function returns the number of uses of one index inside
660 * a whole XMLDBStructure. Useful to detect if the index must be
661 * locked. Return false if no uses are found.
663 function getIndexUses($tablename, $indexname) {
667 /// Nothing to check, beause indexes haven't uses! Leave it here
668 /// for future checks...
679 * This function will return all the errors found in one structure
680 * looking recursively inside each table/statement. Returns
681 * an array of errors or false
683 function getAllErrors() {
686 /// First the structure itself
687 if ($this->getError()) {
688 $errors[] = $this->getError();
690 /// Delegate to tables
691 if ($tables = $this->getTables()) {
692 foreach ($tables as $table) {
693 if ($tableerrors = $table->getAllErrors()) {
697 /// Add them to the errors array
699 $errors = array_merge($errors, $tableerrors);
702 /// Delegate to statements
703 if ($statements = $this->getStatements()) {
704 foreach ($statements as $statement) {
705 if ($statement->getError()) {
706 $errors[] = $statement->getError();
711 if (count($errors)) {
719 * This function will return the SQL code needed to create the table for the specified DB and
720 * prefix. Just one simple wrapper over generators.
722 function getCreateStructureSQL ($dbtype, $prefix, $statement_end=true) {
726 if ($tables = $this->getTables()) {
727 foreach ($tables as $table) {
728 $results = array_merge($results, $table->getCreateTableSQL($dbtype, $prefix, $statement_end));
732 if ($statements = $this->getStatements()) {
733 foreach ($statements as $statement) {
734 $results = array_merge($results, $statement->getExecuteStatementSQL($dbtype, $prefix, $statement_end));