Merge branch 'MDL-29276' of git://github.com/mouneyrac/moodle
[moodle.git] / lib / xmldb / xmldb_table.php
blobed77de8832332ed0cf5bfe1e8218bfc9216cf56e
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 table
29 class xmldb_table extends xmldb_object {
31 var $fields;
32 var $keys;
33 var $indexes;
35 /**
36 * Creates one new xmldb_table
38 function __construct($name) {
39 parent::__construct($name);
40 $this->fields = array();
41 $this->keys = array();
42 $this->indexes = array();
45 /**
46 * Add one field to the table, allowing to specify the desired order
47 * If it's not specified, then the field is added at the end
49 function addField(&$field, $after=NULL) {
51 /// Detect duplicates first
52 if ($this->getField($field->getName())) {
53 throw new coding_exception('Duplicate field '.$field->getName().' specified in table '.$this->getName());
56 /// Calculate the previous and next fields
57 $prevfield = NULL;
58 $nextfield = NULL;
60 if (!$after) {
61 $allfields =& $this->getFields();
62 if (!empty($allfields)) {
63 end($allfields);
64 $prevfield =& $allfields[key($allfields)];
66 } else {
67 $prevfield =& $this->getField($after);
69 if ($prevfield && $prevfield->getNext()) {
70 $nextfield =& $this->getField($prevfield->getNext());
73 /// Set current field previous and next attributes
74 if ($prevfield) {
75 $field->setPrevious($prevfield->getName());
76 $prevfield->setNext($field->getName());
78 if ($nextfield) {
79 $field->setNext($nextfield->getName());
80 $nextfield->setPrevious($field->getName());
82 /// Some more attributes
83 $field->setLoaded(true);
84 $field->setChanged(true);
85 /// Add the new field
86 $this->fields[] = $field;
87 /// Reorder the field
88 $this->orderFields($this->fields);
89 /// Recalculate the hash
90 $this->calculateHash(true);
91 /// We have one new field, so the table has changed
92 $this->setChanged(true);
94 return $field;
97 /**
98 * Add one key to the table, allowing to specify the desired order
99 * If it's not specified, then the key is added at the end
101 function addKey(&$key, $after=NULL) {
103 /// Detect duplicates first
104 if ($this->getKey($key->getName())) {
105 throw new coding_exception('Duplicate key '.$key->getName().' specified in table '.$this->getName());
108 /// Calculate the previous and next keys
109 $prevkey = NULL;
110 $nextkey = NULL;
112 if (!$after) {
113 $allkeys =& $this->getKeys();
114 if (!empty($allkeys)) {
115 end($allkeys);
116 $prevkey =& $allkeys[key($allkeys)];
118 } else {
119 $prevkey =& $this->getKey($after);
121 if ($prevkey && $prevkey->getNext()) {
122 $nextkey =& $this->getKey($prevkey->getNext());
125 /// Set current key previous and next attributes
126 if ($prevkey) {
127 $key->setPrevious($prevkey->getName());
128 $prevkey->setNext($key->getName());
130 if ($nextkey) {
131 $key->setNext($nextkey->getName());
132 $nextkey->setPrevious($key->getName());
134 /// Some more attributes
135 $key->setLoaded(true);
136 $key->setChanged(true);
137 /// Add the new key
138 $this->keys[] = $key;
139 /// Reorder the keys
140 $this->orderKeys($this->keys);
141 /// Recalculate the hash
142 $this->calculateHash(true);
143 /// We have one new field, so the table has changed
144 $this->setChanged(true);
148 * Add one index to the table, allowing to specify the desired order
149 * If it's not specified, then the index is added at the end
151 function addIndex(&$index, $after=NULL) {
153 /// Detect duplicates first
154 if ($this->getIndex($index->getName())) {
155 throw new coding_exception('Duplicate index '.$index->getName().' specified in table '.$this->getName());
158 /// Calculate the previous and next indexes
159 $previndex = NULL;
160 $nextindex = NULL;
162 if (!$after) {
163 $allindexes =& $this->getIndexes();
164 if (!empty($allindexes)) {
165 end($allindexes);
166 $previndex =& $allindexes[key($allindexes)];
168 } else {
169 $previndex =& $this->getIndex($after);
171 if ($previndex && $previndex->getNext()) {
172 $nextindex =& $this->getIndex($previndex->getNext());
175 /// Set current index previous and next attributes
176 if ($previndex) {
177 $index->setPrevious($previndex->getName());
178 $previndex->setNext($index->getName());
180 if ($nextindex) {
181 $index->setNext($nextindex->getName());
182 $nextindex->setPrevious($index->getName());
185 /// Some more attributes
186 $index->setLoaded(true);
187 $index->setChanged(true);
188 /// Add the new index
189 $this->indexes[] = $index;
190 /// Reorder the indexes
191 $this->orderIndexes($this->indexes);
192 /// Recalculate the hash
193 $this->calculateHash(true);
194 /// We have one new index, so the table has changed
195 $this->setChanged(true);
199 * This function will return the array of fields in the table
201 function &getFields() {
202 return $this->fields;
206 * This function will return the array of keys in the table
208 function &getKeys() {
209 return $this->keys;
213 * This function will return the array of indexes in the table
215 function &getIndexes() {
216 return $this->indexes;
220 * Returns one xmldb_field
222 function &getField($fieldname) {
223 $i = $this->findFieldInArray($fieldname);
224 if ($i !== NULL) {
225 return $this->fields[$i];
227 $null = NULL;
228 return $null;
232 * Returns the position of one field in the array.
234 function &findFieldInArray($fieldname) {
235 foreach ($this->fields as $i => $field) {
236 if ($fieldname == $field->getName()) {
237 return $i;
240 $null = NULL;
241 return $null;
245 * This function will reorder the array of fields
247 function orderFields() {
248 $result = $this->orderElements($this->fields);
249 if ($result) {
250 $this->setFields($result);
251 return true;
252 } else {
253 return false;
258 * Returns one xmldb_key
260 function &getKey($keyname) {
261 $i = $this->findKeyInArray($keyname);
262 if ($i !== NULL) {
263 return $this->keys[$i];
265 $null = NULL;
266 return $null;
270 * Returns the position of one key in the array.
272 function &findKeyInArray($keyname) {
273 foreach ($this->keys as $i => $key) {
274 if ($keyname == $key->getName()) {
275 return $i;
278 $null = NULL;
279 return $null;
283 * This function will reorder the array of keys
285 function orderKeys() {
286 $result = $this->orderElements($this->keys);
287 if ($result) {
288 $this->setKeys($result);
289 return true;
290 } else {
291 return false;
296 * Returns one xmldb_index
298 function &getIndex($indexname) {
299 $i = $this->findIndexInArray($indexname);
300 if ($i !== NULL) {
301 return $this->indexes[$i];
303 $null = NULL;
304 return $null;
308 * Returns the position of one index in the array.
310 function &findIndexInArray($indexname) {
311 foreach ($this->indexes as $i => $index) {
312 if ($indexname == $index->getName()) {
313 return $i;
316 $null = NULL;
317 return $null;
321 * This function will reorder the array of indexes
323 function orderIndexes() {
324 $result = $this->orderElements($this->indexes);
325 if ($result) {
326 $this->setIndexes($result);
327 return true;
328 } else {
329 return false;
334 * This function will set the array of fields in the table
336 function setFields($fields) {
337 $this->fields = $fields;
341 * This function will set the array of keys in the table
343 function setKeys($keys) {
344 $this->keys = $keys;
348 * This function will set the array of indexes in the table
350 function setIndexes($indexes) {
351 $this->indexes = $indexes;
355 * Delete one field from the table
357 function deleteField($fieldname) {
359 $field =& $this->getField($fieldname);
360 if ($field) {
361 $i = $this->findFieldInArray($fieldname);
362 /// Look for prev and next field
363 $prevfield =& $this->getField($field->getPrevious());
364 $nextfield =& $this->getField($field->getNext());
365 /// Change their previous and next attributes
366 if ($prevfield) {
367 $prevfield->setNext($field->getNext());
369 if ($nextfield) {
370 $nextfield->setPrevious($field->getPrevious());
372 /// Delete the field
373 unset($this->fields[$i]);
374 /// Reorder the whole structure
375 $this->orderFields($this->fields);
376 /// Recalculate the hash
377 $this->calculateHash(true);
378 /// We have one deleted field, so the table has changed
379 $this->setChanged(true);
384 * Delete one key from the table
386 function deleteKey($keyname) {
388 $key =& $this->getKey($keyname);
389 if ($key) {
390 $i = $this->findKeyInArray($keyname);
391 /// Look for prev and next key
392 $prevkey =& $this->getKey($key->getPrevious());
393 $nextkey =& $this->getKey($key->getNext());
394 /// Change their previous and next attributes
395 if ($prevkey) {
396 $prevkey->setNext($key->getNext());
398 if ($nextkey) {
399 $nextkey->setPrevious($key->getPrevious());
401 /// Delete the key
402 unset($this->keys[$i]);
403 /// Reorder the Keys
404 $this->orderKeys($this->keys);
405 /// Recalculate the hash
406 $this->calculateHash(true);
407 /// We have one deleted key, so the table has changed
408 $this->setChanged(true);
413 * Delete one index from the table
415 function deleteIndex($indexname) {
417 $index =& $this->getIndex($indexname);
418 if ($index) {
419 $i = $this->findIndexInArray($indexname);
420 /// Look for prev and next index
421 $previndex =& $this->getIndex($index->getPrevious());
422 $nextindex =& $this->getIndex($index->getNext());
423 /// Change their previous and next attributes
424 if ($previndex) {
425 $previndex->setNext($index->getNext());
427 if ($nextindex) {
428 $nextindex->setPrevious($index->getPrevious());
430 /// Delete the index
431 unset($this->indexes[$i]);
432 /// Reorder the indexes
433 $this->orderIndexes($this->indexes);
434 /// Recalculate the hash
435 $this->calculateHash(true);
436 /// We have one deleted index, so the table has changed
437 $this->setChanged(true);
442 * Load data from XML to the table
444 function arr2xmldb_table($xmlarr) {
446 global $CFG;
448 $result = true;
450 /// Debug the table
451 /// traverse_xmlize($xmlarr); //Debug
452 /// print_object ($GLOBALS['traverse_array']); //Debug
453 /// $GLOBALS['traverse_array']=""; //Debug
455 /// Process table attributes (name, comment, previoustable and nexttable)
456 if (isset($xmlarr['@']['NAME'])) {
457 $this->name = trim($xmlarr['@']['NAME']);
458 } else {
459 $this->errormsg = 'Missing NAME attribute';
460 $this->debug($this->errormsg);
461 $result = false;
463 if (isset($xmlarr['@']['COMMENT'])) {
464 $this->comment = trim($xmlarr['@']['COMMENT']);
465 } else if (!empty($CFG->xmldbdisablecommentchecking)) {
466 $this->comment = '';
467 } else {
468 $this->errormsg = 'Missing COMMENT attribute';
469 $this->debug($this->errormsg);
470 $result = false;
472 if (isset($xmlarr['@']['PREVIOUS'])) {
473 $this->previous = trim($xmlarr['@']['PREVIOUS']);
475 if (isset($xmlarr['@']['NEXT'])) {
476 $this->next = trim($xmlarr['@']['NEXT']);
479 /// Iterate over fields
480 if (isset($xmlarr['#']['FIELDS']['0']['#']['FIELD'])) {
481 foreach ($xmlarr['#']['FIELDS']['0']['#']['FIELD'] as $xmlfield) {
482 if (!$result) { //Skip on error
483 continue;
485 $name = trim($xmlfield['@']['NAME']);
486 $field = new xmldb_field($name);
487 $field->arr2xmldb_field($xmlfield);
488 $this->fields[] = $field;
489 if (!$field->isLoaded()) {
490 $this->errormsg = 'Problem loading field ' . $name;
491 $this->debug($this->errormsg);
492 $result = false;
495 } else {
496 $this->errormsg = 'Missing FIELDS section';
497 $this->debug($this->errormsg);
498 $result = false;
501 /// Perform some general checks over fields
502 if ($result && $this->fields) {
503 /// Check field names are ok (lowercase, a-z _-)
504 if (!$this->checkNameValues($this->fields)) {
505 $this->errormsg = 'Some FIELDS name values are incorrect';
506 $this->debug($this->errormsg);
507 $result = false;
509 /// Check previous & next are ok (duplicates and existing fields)
510 $this->fixPrevNext($this->fields);
511 if ($result && !$this->checkPreviousNextValues($this->fields)) {
512 $this->errormsg = 'Some FIELDS previous/next values are incorrect';
513 $this->debug($this->errormsg);
514 $result = false;
516 /// Order fields
517 if ($result && !$this->orderFields($this->fields)) {
518 $this->errormsg = 'Error ordering the fields';
519 $this->debug($this->errormsg);
520 $result = false;
524 /// Iterate over keys
525 if (isset($xmlarr['#']['KEYS']['0']['#']['KEY'])) {
526 foreach ($xmlarr['#']['KEYS']['0']['#']['KEY'] as $xmlkey) {
527 if (!$result) { //Skip on error
528 continue;
530 $name = trim($xmlkey['@']['NAME']);
531 $key = new xmldb_key($name);
532 $key->arr2xmldb_key($xmlkey);
533 $this->keys[] = $key;
534 if (!$key->isLoaded()) {
535 $this->errormsg = 'Problem loading key ' . $name;
536 $this->debug($this->errormsg);
537 $result = false;
540 } else {
541 $this->errormsg = 'Missing KEYS section (at least one PK must exist)';
542 $this->debug($this->errormsg);
543 $result = false;
546 /// Perform some general checks over keys
547 if ($result && $this->keys) {
548 /// Check keys names are ok (lowercase, a-z _-)
549 if (!$this->checkNameValues($this->keys)) {
550 $this->errormsg = 'Some KEYS name values are incorrect';
551 $this->debug($this->errormsg);
552 $result = false;
554 /// Check previous & next are ok (duplicates and existing keys)
555 $this->fixPrevNext($this->keys);
556 if ($result && !$this->checkPreviousNextValues($this->keys)) {
557 $this->errormsg = 'Some KEYS previous/next values are incorrect';
558 $this->debug($this->errormsg);
559 $result = false;
561 /// Order keys
562 if ($result && !$this->orderKeys($this->keys)) {
563 $this->errormsg = 'Error ordering the keys';
564 $this->debug($this->errormsg);
565 $result = false;
567 /// TODO: Only one PK
568 /// TODO: Not keys with repeated fields
569 /// TODO: Check fields and reffieds exist in table
572 /// Iterate over indexes
573 if (isset($xmlarr['#']['INDEXES']['0']['#']['INDEX'])) {
574 foreach ($xmlarr['#']['INDEXES']['0']['#']['INDEX'] as $xmlindex) {
575 if (!$result) { //Skip on error
576 continue;
578 $name = trim($xmlindex['@']['NAME']);
579 $index = new xmldb_index($name);
580 $index->arr2xmldb_index($xmlindex);
581 $this->indexes[] = $index;
582 if (!$index->isLoaded()) {
583 $this->errormsg = 'Problem loading index ' . $name;
584 $this->debug($this->errormsg);
585 $result = false;
590 /// Perform some general checks over indexes
591 if ($result && $this->indexes) {
592 /// Check field names are ok (lowercase, a-z _-)
593 if (!$this->checkNameValues($this->indexes)) {
594 $this->errormsg = 'Some INDEXES name values are incorrect';
595 $this->debug($this->errormsg);
596 $result = false;
598 /// Check previous & next are ok (duplicates and existing INDEXES)
599 $this->fixPrevNext($this->indexes);
600 if ($result && !$this->checkPreviousNextValues($this->indexes)) {
601 $this->errormsg = 'Some INDEXES previous/next values are incorrect';
602 $this->debug($this->errormsg);
603 $result = false;
605 /// Order indexes
606 if ($result && !$this->orderIndexes($this->indexes)) {
607 $this->errormsg = 'Error ordering the indexes';
608 $this->debug($this->errormsg);
609 $result = false;
611 /// TODO: Not indexes with repeated fields
612 /// TODO: Check fields exist in table
615 /// Set some attributes
616 if ($result) {
617 $this->loaded = true;
619 $this->calculateHash();
620 return $result;
624 * This function calculate and set the hash of one xmldb_table
626 function calculateHash($recursive = false) {
627 if (!$this->loaded) {
628 $this->hash = NULL;
629 } else {
630 $key = $this->name . $this->comment;
631 if ($this->fields) {
632 foreach ($this->fields as $fie) {
633 $field =& $this->getField($fie->getName());
634 if ($recursive) {
635 $field->calculateHash($recursive);
637 $key .= $field->getHash();
640 if ($this->keys) {
641 foreach ($this->keys as $ke) {
642 $k =& $this->getKey($ke->getName());
643 if ($recursive) {
644 $k->calculateHash($recursive);
646 $key .= $k->getHash();
649 if ($this->indexes) {
650 foreach ($this->indexes as $in) {
651 $index =& $this->getIndex($in->getName());
652 if ($recursive) {
653 $index->calculateHash($recursive);
655 $key .= $index->getHash();
658 $this->hash = md5($key);
663 * This function will output the XML text for one table
665 function xmlOutput() {
666 $o = '';
667 $o.= ' <TABLE NAME="' . $this->name . '"';
668 if ($this->comment) {
669 $o.= ' COMMENT="' . htmlspecialchars($this->comment) . '"';
671 if ($this->previous) {
672 $o.= ' PREVIOUS="' . $this->previous . '"';
674 if ($this->next) {
675 $o.= ' NEXT="' . $this->next . '"';
677 $o.= '>' . "\n";
678 /// Now the fields
679 if ($this->fields) {
680 $o.= ' <FIELDS>' . "\n";
681 foreach ($this->fields as $field) {
682 $o.= $field->xmlOutput();
684 $o.= ' </FIELDS>' . "\n";
686 /// Now the keys
687 if ($this->keys) {
688 $o.= ' <KEYS>' . "\n";
689 foreach ($this->keys as $key) {
690 $o.= $key->xmlOutput();
692 $o.= ' </KEYS>' . "\n";
694 /// Now the indexes
695 if ($this->indexes) {
696 $o.= ' <INDEXES>' . "\n";
697 foreach ($this->indexes as $index) {
698 $o.= $index->xmlOutput();
700 $o.= ' </INDEXES>' . "\n";
702 $o.= ' </TABLE>' . "\n";
704 return $o;
707 /// TODO: Delete for 2.1 (deprecated in 2.0).
708 /// Deprecated API starts here
709 function addFieldInfo($name, $type, $precision=null, $unsigned=null, $notnull=null, $sequence=null, $enum=null, $enumvalues=null, $default=null, $previous=null) {
711 debugging('XMLDBTable->addFieldInfo() has been deprecated in Moodle 2.0. Will be out in Moodle 2.1. Please use xmldb_table->add_field() instead', DEBUG_DEVELOPER);
712 if ($enum) {
713 debugging('Also, ENUMs support has been dropped in Moodle 2.0. Your fields specs are incorrect because you are trying to introduce one new ENUM. Created DB estructures will ignore that.');
716 return $this->add_field($name, $type, $precision, $unsigned, $notnull, $sequence, $default, $previous);
719 /// Deprecated API ends here
722 * This function will add one new field to the table with all
723 * its attributes defined
725 * @param string name name of the field
726 * @param string type XMLDB_TYPE_INTEGER, XMLDB_TYPE_NUMBER, XMLDB_TYPE_CHAR, XMLDB_TYPE_TEXT, XMLDB_TYPE_BINARY
727 * @param string precision length for integers and chars, two-comma separated numbers for numbers and 'small', 'medium', 'big' for texts and binaries
728 * @param string unsigned XMLDB_UNSIGNED or null (or false)
729 * @param string notnull XMLDB_NOTNULL or null (or false)
730 * @param string sequence XMLDB_SEQUENCE or null (or false)
731 * @param string default meaningful default o null (or false)
732 * @param string previous name of the previous field in the table or null (or false)
734 function add_field($name, $type, $precision=null, $unsigned=null, $notnull=null, $sequence=null, $default=null, $previous=null) {
735 $field = new xmldb_field($name, $type, $precision, $unsigned, $notnull, $sequence, $default);
736 $this->addField($field, $previous);
738 return $field;
741 /// TODO: Delete for 2.1 (deprecated in 2.0).
742 /// Deprecated API starts here
744 function addKeyInfo($name, $type, $fields, $reftable=null, $reffields=null) {
746 debugging('XMLDBTable->addKeyInfo() has been deprecated in Moodle 2.0. Will be out in Moodle 2.1. Please use xmldb_table->add_key() instead', DEBUG_DEVELOPER);
748 return $this->add_key($name, $type, $fields, $reftable, $reffields);
751 /// Deprecated API ends here
754 * This function will add one new key to the table with all
755 * its attributes defined
757 * @param string name name of the key
758 * @param string type XMLDB_KEY_PRIMARY, XMLDB_KEY_UNIQUE, XMLDB_KEY_FOREIGN
759 * @param array fields an array of fieldnames to build the key over
760 * @param string reftable name of the table the FK points to or null
761 * @param array reffields an array of fieldnames in the FK table or null
763 function add_key($name, $type, $fields, $reftable=null, $reffields=null) {
764 $key = new xmldb_key($name, $type, $fields, $reftable, $reffields);
765 $this->addKey($key);
768 /// TODO: Delete for 2.1 (deprecated in 2.0).
769 /// Deprecated API starts here
770 function addIndexInfo($name, $type, $fields) {
772 debugging('XMLDBTable->addIndexInfo() has been deprecated in Moodle 2.0. Will be out in Moodle 2.1. Please use xmldb_table->add_index() instead', DEBUG_DEVELOPER);
774 return $this->add_index($name, $type, $fields);
777 /// Deprecated API ends here
780 * This function will add one new index to the table with all
781 * its attributes defined
783 * @param string name name of the index
784 * @param string type XMLDB_INDEX_UNIQUE, XMLDB_INDEX_NOTUNIQUE
785 * @param array fields an array of fieldnames to build the index over
787 function add_index($name, $type, $fields) {
788 $index = new xmldb_index($name, $type, $fields);
789 $this->addIndex($index);
793 * This function will return all the errors found in one table
794 * looking recursively inside each field/key/index. Returns
795 * an array of errors or false
797 function getAllErrors() {
799 $errors = array();
800 /// First the table itself
801 if ($this->getError()) {
802 $errors[] = $this->getError();
804 /// Delegate to fields
805 if ($fields = $this->getFields()) {
806 foreach ($fields as $field) {
807 if ($field->getError()) {
808 $errors[] = $field->getError();
812 /// Delegate to keys
813 if ($keys = $this->getKeys()) {
814 foreach ($keys as $key) {
815 if ($key->getError()) {
816 $errors[] = $key->getError();
820 /// Delegate to indexes
821 if ($indexes = $this->getIndexes()) {
822 foreach ($indexes as $index) {
823 if ($index->getError()) {
824 $errors[] = $index->getError();
828 /// Return decision
829 if (count($errors)) {
830 return $errors;
831 } else {
832 return false;
837 /// TODO: Delete for 2.1 (deeprecated in 2.0).
838 /// Deprecated API starts here
839 class XMLDBTable extends xmldb_table {
841 function __construct($name) {
842 parent::__construct($name);
846 /// Deprecated API ends here