Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 402 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 table
  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_table extends xmldb_object {
  30  
  31      /** @var xmldb_field[] table columns */
  32      protected $fields;
  33  
  34      /** @var xmldb_key[] keys */
  35      protected $keys;
  36  
  37      /** @var xmldb_index[] indexes */
  38      protected $indexes;
  39  
  40      /**
  41       * Note:
  42       *  - Oracle has 30 chars limit for all names,
  43       *    2 chars are reserved for prefix.
  44       *
  45       * @const maximum length of field names
  46       */
  47      const NAME_MAX_LENGTH = 28;
  48  
  49      /**
  50       * Creates one new xmldb_table
  51       * @param string $name
  52       */
  53      public function __construct($name) {
  54          parent::__construct($name);
  55          $this->fields = array();
  56          $this->keys = array();
  57          $this->indexes = array();
  58      }
  59  
  60      /**
  61       * Add one field to the table, allowing to specify the desired  order
  62       * If it's not specified, then the field is added at the end
  63       * @param xmldb_field $field
  64       * @param xmldb_object $after
  65       * @return xmldb_field
  66       */
  67      public function addField($field, $after=null) {
  68  
  69          // Detect duplicates first
  70          if ($this->getField($field->getName())) {
  71              throw new coding_exception('Duplicate field '.$field->getName().' specified in table '.$this->getName());
  72          }
  73  
  74          // Calculate the previous and next fields
  75          $prevfield = null;
  76          $nextfield = null;
  77  
  78          if (!$after) {
  79              $allfields = $this->getFields();
  80              if (!empty($allfields)) {
  81                  end($allfields);
  82                  $prevfield = $allfields[key($allfields)];
  83              }
  84          } else {
  85              $prevfield = $this->getField($after);
  86          }
  87          if ($prevfield && $prevfield->getNext()) {
  88              $nextfield = $this->getField($prevfield->getNext());
  89          }
  90  
  91          // Set current field previous and next attributes
  92          if ($prevfield) {
  93              $field->setPrevious($prevfield->getName());
  94              $prevfield->setNext($field->getName());
  95          }
  96          if ($nextfield) {
  97              $field->setNext($nextfield->getName());
  98              $nextfield->setPrevious($field->getName());
  99          }
 100          // Some more attributes
 101          $field->setLoaded(true);
 102          $field->setChanged(true);
 103          // Add the new field
 104          $this->fields[] = $field;
 105          // Reorder the field
 106          $this->orderFields($this->fields);
 107          // Recalculate the hash
 108          $this->calculateHash(true);
 109          // We have one new field, so the table has changed
 110          $this->setChanged(true);
 111  
 112          return $field;
 113      }
 114  
 115      /**
 116       * Add one key to the table, allowing to specify the desired  order
 117       * If it's not specified, then the key is added at the end
 118       * @param xmldb_key $key
 119       * @param xmldb_object $after
 120       */
 121      public function addKey($key, $after=null) {
 122  
 123          // Detect duplicates first
 124          if ($this->getKey($key->getName())) {
 125              throw new coding_exception('Duplicate key '.$key->getName().' specified in table '.$this->getName());
 126          }
 127  
 128          // Make sure there are no indexes with the key column specs because they would collide.
 129          $newfields = $key->getFields();
 130          $allindexes = $this->getIndexes();
 131          foreach ($allindexes as $index) {
 132              $fields = $index->getFields();
 133              if ($fields === $newfields) {
 134                  throw new coding_exception('Index '.$index->getName().' collides with key'.$key->getName().' specified in table '.$this->getName());
 135              }
 136          }
 137  
 138          // Calculate the previous and next keys
 139          $prevkey = null;
 140          $nextkey = null;
 141  
 142          if (!$after) {
 143              $allkeys = $this->getKeys();
 144              if (!empty($allkeys)) {
 145                  end($allkeys);
 146                  $prevkey = $allkeys[key($allkeys)];
 147              }
 148          } else {
 149              $prevkey = $this->getKey($after);
 150          }
 151          if ($prevkey && $prevkey->getNext()) {
 152              $nextkey = $this->getKey($prevkey->getNext());
 153          }
 154  
 155          // Set current key previous and next attributes
 156          if ($prevkey) {
 157              $key->setPrevious($prevkey->getName());
 158              $prevkey->setNext($key->getName());
 159          }
 160          if ($nextkey) {
 161              $key->setNext($nextkey->getName());
 162              $nextkey->setPrevious($key->getName());
 163          }
 164          // Some more attributes
 165          $key->setLoaded(true);
 166          $key->setChanged(true);
 167          // Add the new key
 168          $this->keys[] = $key;
 169          // Reorder the keys
 170          $this->orderKeys($this->keys);
 171          // Recalculate the hash
 172          $this->calculateHash(true);
 173          // We have one new field, so the table has changed
 174          $this->setChanged(true);
 175      }
 176  
 177      /**
 178       * Add one index to the table, allowing to specify the desired  order
 179       * If it's not specified, then the index is added at the end
 180       * @param xmldb_index $index
 181       * @param xmldb_object $after
 182       */
 183      public function addIndex($index, $after=null) {
 184  
 185          // Detect duplicates first
 186          if ($this->getIndex($index->getName())) {
 187              throw new coding_exception('Duplicate index '.$index->getName().' specified in table '.$this->getName());
 188          }
 189  
 190          // Make sure there are no keys with the index column specs because they would collide.
 191          $newfields = $index->getFields();
 192          $allkeys = $this->getKeys();
 193          foreach ($allkeys as $key) {
 194              $fields = $key->getFields();
 195              if ($fields === $newfields) {
 196                  throw new coding_exception('Key '.$key->getName().' collides with index'.$index->getName().' specified in table '.$this->getName());
 197              }
 198          }
 199  
 200          // Calculate the previous and next indexes
 201          $previndex = null;
 202          $nextindex = null;
 203  
 204          if (!$after) {
 205              $allindexes = $this->getIndexes();
 206              if (!empty($allindexes)) {
 207                  end($allindexes);
 208                  $previndex = $allindexes[key($allindexes)];
 209              }
 210          } else {
 211              $previndex = $this->getIndex($after);
 212          }
 213          if ($previndex && $previndex->getNext()) {
 214              $nextindex = $this->getIndex($previndex->getNext());
 215          }
 216  
 217          // Set current index previous and next attributes
 218          if ($previndex) {
 219              $index->setPrevious($previndex->getName());
 220              $previndex->setNext($index->getName());
 221          }
 222          if ($nextindex) {
 223              $index->setNext($nextindex->getName());
 224              $nextindex->setPrevious($index->getName());
 225          }
 226  
 227          // Some more attributes
 228          $index->setLoaded(true);
 229          $index->setChanged(true);
 230          // Add the new index
 231          $this->indexes[] = $index;
 232          // Reorder the indexes
 233          $this->orderIndexes($this->indexes);
 234          // Recalculate the hash
 235          $this->calculateHash(true);
 236          // We have one new index, so the table has changed
 237          $this->setChanged(true);
 238      }
 239  
 240      /**
 241       * This function will return the array of fields in the table
 242       * @return xmldb_field[]
 243       */
 244      public function getFields() {
 245          return $this->fields;
 246      }
 247  
 248      /**
 249       * This function will return the array of keys in the table
 250       * @return xmldb_key[]
 251       */
 252      public function getKeys() {
 253          return $this->keys;
 254      }
 255  
 256      /**
 257       * This function will return the array of indexes in the table
 258       * @return xmldb_index[]
 259       */
 260      public function getIndexes() {
 261          return $this->indexes;
 262      }
 263  
 264      /**
 265       * Returns one xmldb_field
 266       * @param string $fieldname
 267       * @return xmldb_field|null
 268       */
 269      public function getField($fieldname) {
 270          $i = $this->findFieldInArray($fieldname);
 271          if ($i !== null) {
 272              return $this->fields[$i];
 273          }
 274          return null;
 275      }
 276  
 277      /**
 278       * Returns the position of one field in the array.
 279       * @param string $fieldname
 280       * @return int|null index of the field, or null if not found.
 281       */
 282      public function findFieldInArray($fieldname) {
 283          foreach ($this->fields as $i => $field) {
 284              if ($fieldname == $field->getName()) {
 285                  return $i;
 286              }
 287          }
 288          return null;
 289      }
 290  
 291      /**
 292       * This function will reorder the array of fields
 293       * @return bool whether the reordering succeeded.
 294       */
 295      public function orderFields() {
 296          $result = $this->orderElements($this->fields);
 297          if ($result) {
 298              $this->setFields($result);
 299              return true;
 300          } else {
 301              return false;
 302          }
 303      }
 304  
 305      /**
 306       * Returns one xmldb_key
 307       * @param string $keyname
 308       * @return xmldb_key|null
 309       */
 310      public function getKey($keyname) {
 311          $i = $this->findKeyInArray($keyname);
 312          if ($i !== null) {
 313              return $this->keys[$i];
 314          }
 315          return null;
 316      }
 317  
 318      /**
 319       * Returns the position of one key in the array.
 320       * @param string $keyname
 321       * @return int|null index of the key, or null if not found.
 322       */
 323      public function findKeyInArray($keyname) {
 324          foreach ($this->keys as $i => $key) {
 325              if ($keyname == $key->getName()) {
 326                  return $i;
 327              }
 328          }
 329          return null;
 330      }
 331  
 332      /**
 333       * This function will reorder the array of keys
 334       * @return bool whether the reordering succeeded.
 335       */
 336      public function orderKeys() {
 337          $result = $this->orderElements($this->keys);
 338          if ($result) {
 339              $this->setKeys($result);
 340              return true;
 341          } else {
 342              return false;
 343          }
 344      }
 345  
 346      /**
 347       * Returns one xmldb_index
 348       * @param string $indexname
 349       * @return xmldb_index|null
 350       */
 351      public function getIndex($indexname) {
 352          $i = $this->findIndexInArray($indexname);
 353          if ($i !== null) {
 354              return $this->indexes[$i];
 355          }
 356          return null;
 357      }
 358  
 359      /**
 360       * Returns the position of one index in the array.
 361       * @param string $indexname
 362       * @return int|null index of the index, or null if not found.
 363       */
 364      public function findIndexInArray($indexname) {
 365          foreach ($this->indexes as $i => $index) {
 366              if ($indexname == $index->getName()) {
 367                  return $i;
 368              }
 369          }
 370          return null;
 371      }
 372  
 373      /**
 374       * This function will reorder the array of indexes
 375       * @return bool whether the reordering succeeded.
 376       */
 377      public function orderIndexes() {
 378          $result = $this->orderElements($this->indexes);
 379          if ($result) {
 380              $this->setIndexes($result);
 381              return true;
 382          } else {
 383              return false;
 384          }
 385      }
 386  
 387      /**
 388       * This function will set the array of fields in the table
 389       * @param xmldb_field[] $fields
 390       */
 391      public function setFields($fields) {
 392          $this->fields = $fields;
 393      }
 394  
 395      /**
 396       * This function will set the array of keys in the table
 397       * @param xmldb_key[] $keys
 398       */
 399      public function setKeys($keys) {
 400          $this->keys = $keys;
 401      }
 402  
 403      /**
 404       * This function will set the array of indexes in the table
 405       * @param xmldb_index[] $indexes
 406       */
 407      public function setIndexes($indexes) {
 408          $this->indexes = $indexes;
 409      }
 410  
 411      /**
 412       * Delete one field from the table
 413       * @param string $fieldname
 414       */
 415      public function deleteField($fieldname) {
 416  
 417          $field = $this->getField($fieldname);
 418          if ($field) {
 419              $i = $this->findFieldInArray($fieldname);
 420              // Look for prev and next field
 421              $prevfield = $this->getField($field->getPrevious());
 422              $nextfield = $this->getField($field->getNext());
 423              // Change their previous and next attributes
 424              if ($prevfield) {
 425                  $prevfield->setNext($field->getNext());
 426              }
 427              if ($nextfield) {
 428                  $nextfield->setPrevious($field->getPrevious());
 429              }
 430              // Delete the field
 431              unset($this->fields[$i]);
 432              // Reorder the whole structure
 433              $this->orderFields($this->fields);
 434              // Recalculate the hash
 435              $this->calculateHash(true);
 436              // We have one deleted field, so the table has changed
 437              $this->setChanged(true);
 438          }
 439      }
 440  
 441      /**
 442       * Delete one key from the table
 443       * @param string $keyname
 444       */
 445      public function deleteKey($keyname) {
 446  
 447          $key = $this->getKey($keyname);
 448          if ($key) {
 449              $i = $this->findKeyInArray($keyname);
 450              // Look for prev and next key
 451              $prevkey = $this->getKey($key->getPrevious());
 452              $nextkey = $this->getKey($key->getNext());
 453              // Change their previous and next attributes
 454              if ($prevkey) {
 455                  $prevkey->setNext($key->getNext());
 456              }
 457              if ($nextkey) {
 458                  $nextkey->setPrevious($key->getPrevious());
 459              }
 460              // Delete the key
 461              unset($this->keys[$i]);
 462              // Reorder the Keys
 463              $this->orderKeys($this->keys);
 464              // Recalculate the hash
 465              $this->calculateHash(true);
 466              // We have one deleted key, so the table has changed
 467              $this->setChanged(true);
 468          }
 469      }
 470  
 471      /**
 472       * Delete one index from the table
 473       * @param string $indexname
 474       */
 475      public function deleteIndex($indexname) {
 476  
 477          $index = $this->getIndex($indexname);
 478          if ($index) {
 479              $i = $this->findIndexInArray($indexname);
 480              // Look for prev and next index
 481              $previndex = $this->getIndex($index->getPrevious());
 482              $nextindex = $this->getIndex($index->getNext());
 483              // Change their previous and next attributes
 484              if ($previndex) {
 485                  $previndex->setNext($index->getNext());
 486              }
 487              if ($nextindex) {
 488                  $nextindex->setPrevious($index->getPrevious());
 489              }
 490              // Delete the index
 491              unset($this->indexes[$i]);
 492              // Reorder the indexes
 493              $this->orderIndexes($this->indexes);
 494              // Recalculate the hash
 495              $this->calculateHash(true);
 496              // We have one deleted index, so the table has changed
 497              $this->setChanged(true);
 498          }
 499      }
 500  
 501      /**
 502       * Load data from XML to the table
 503       * @param array $xmlarr
 504       * @return bool success
 505       */
 506      public function arr2xmldb_table($xmlarr) {
 507  
 508          global $CFG;
 509  
 510          $result = true;
 511  
 512          // Debug the table
 513          // traverse_xmlize($xmlarr);                   //Debug
 514          // print_object ($GLOBALS['traverse_array']);  //Debug
 515          // $GLOBALS['traverse_array']="";              //Debug
 516  
 517          // Process table attributes (name, comment, previoustable and nexttable)
 518          if (isset($xmlarr['@']['NAME'])) {
 519              $this->name = trim($xmlarr['@']['NAME']);
 520          } else {
 521              $this->errormsg = 'Missing NAME attribute';
 522              $this->debug($this->errormsg);
 523              $result = false;
 524          }
 525          if (isset($xmlarr['@']['COMMENT'])) {
 526              $this->comment = trim($xmlarr['@']['COMMENT']);
 527          } else if (!empty($CFG->xmldbdisablecommentchecking)) {
 528              $this->comment = '';
 529          } else {
 530              $this->errormsg = 'Missing COMMENT attribute';
 531              $this->debug($this->errormsg);
 532              $result = false;
 533          }
 534  
 535          // Iterate over fields
 536          if (isset($xmlarr['#']['FIELDS']['0']['#']['FIELD'])) {
 537              foreach ($xmlarr['#']['FIELDS']['0']['#']['FIELD'] as $xmlfield) {
 538                  if (!$result) { //Skip on error
 539                      continue;
 540                  }
 541                  $name = trim($xmlfield['@']['NAME']);
 542                  $field = new xmldb_field($name);
 543                  $field->arr2xmldb_field($xmlfield);
 544                  $this->fields[] = $field;
 545                  if (!$field->isLoaded()) {
 546                      $this->errormsg = 'Problem loading field ' . $name;
 547                      $this->debug($this->errormsg);
 548                      $result = false;
 549                  }
 550              }
 551          } else {
 552              $this->errormsg = 'Missing FIELDS section';
 553              $this->debug($this->errormsg);
 554              $result = false;
 555          }
 556  
 557          // Perform some general checks over fields
 558          if ($result && $this->fields) {
 559              // Check field names are ok (lowercase, a-z _-)
 560              if (!$this->checkNameValues($this->fields)) {
 561                  $this->errormsg = 'Some FIELDS name values are incorrect';
 562                  $this->debug($this->errormsg);
 563                  $result = false;
 564              }
 565              // Compute prev/next.
 566              $this->fixPrevNext($this->fields);
 567              // Order fields
 568              if ($result && !$this->orderFields($this->fields)) {
 569                  $this->errormsg = 'Error ordering the fields';
 570                  $this->debug($this->errormsg);
 571                  $result = false;
 572              }
 573          }
 574  
 575          // Iterate over keys
 576          if (isset($xmlarr['#']['KEYS']['0']['#']['KEY'])) {
 577              foreach ($xmlarr['#']['KEYS']['0']['#']['KEY'] as $xmlkey) {
 578                  if (!$result) { //Skip on error
 579                      continue;
 580                  }
 581                  $name = trim($xmlkey['@']['NAME']);
 582                  $key = new xmldb_key($name);
 583                  $key->arr2xmldb_key($xmlkey);
 584                  $this->keys[] = $key;
 585                  if (!$key->isLoaded()) {
 586                      $this->errormsg = 'Problem loading key ' . $name;
 587                      $this->debug($this->errormsg);
 588                      $result = false;
 589                  }
 590              }
 591          } else {
 592              $this->errormsg = 'Missing KEYS section (at least one PK must exist)';
 593              $this->debug($this->errormsg);
 594              $result = false;
 595          }
 596  
 597          // Perform some general checks over keys
 598          if ($result && $this->keys) {
 599              // Check keys names are ok (lowercase, a-z _-)
 600              if (!$this->checkNameValues($this->keys)) {
 601                  $this->errormsg = 'Some KEYS name values are incorrect';
 602                  $this->debug($this->errormsg);
 603                  $result = false;
 604              }
 605              // Compute prev/next.
 606              $this->fixPrevNext($this->keys);
 607              // Order keys
 608              if ($result && !$this->orderKeys($this->keys)) {
 609                  $this->errormsg = 'Error ordering the keys';
 610                  $this->debug($this->errormsg);
 611                  $result = false;
 612              }
 613              // TODO: Only one PK
 614              // TODO: Not keys with repeated fields
 615              // TODO: Check fields and reffieds exist in table
 616          }
 617  
 618          // Iterate over indexes
 619          if (isset($xmlarr['#']['INDEXES']['0']['#']['INDEX'])) {
 620              foreach ($xmlarr['#']['INDEXES']['0']['#']['INDEX'] as $xmlindex) {
 621                  if (!$result) { //Skip on error
 622                      continue;
 623                  }
 624                  $name = trim($xmlindex['@']['NAME']);
 625                  $index = new xmldb_index($name);
 626                  $index->arr2xmldb_index($xmlindex);
 627                  $this->indexes[] = $index;
 628                  if (!$index->isLoaded()) {
 629                      $this->errormsg = 'Problem loading index ' . $name;
 630                      $this->debug($this->errormsg);
 631                      $result = false;
 632                  }
 633              }
 634          }
 635  
 636          // Perform some general checks over indexes
 637          if ($result && $this->indexes) {
 638              // Check field names are ok (lowercase, a-z _-)
 639              if (!$this->checkNameValues($this->indexes)) {
 640                  $this->errormsg = 'Some INDEXES name values are incorrect';
 641                  $this->debug($this->errormsg);
 642                  $result = false;
 643              }
 644              // Compute prev/next.
 645              $this->fixPrevNext($this->indexes);
 646              // Order indexes
 647              if ($result && !$this->orderIndexes($this->indexes)) {
 648                  $this->errormsg = 'Error ordering the indexes';
 649                  $this->debug($this->errormsg);
 650                  $result = false;
 651              }
 652              // TODO: Not indexes with repeated fields
 653              // TODO: Check fields exist in table
 654          }
 655  
 656          // Set some attributes
 657          if ($result) {
 658              $this->loaded = true;
 659          }
 660          $this->calculateHash();
 661          return $result;
 662      }
 663  
 664      /**
 665       * This function calculate and set the hash of one xmldb_table
 666       * @param bool $recursive
 667       */
 668       public function calculateHash($recursive = false) {
 669          if (!$this->loaded) {
 670              $this->hash = null;
 671          } else {
 672              $key = $this->name . $this->comment;
 673              if ($this->fields) {
 674                  foreach ($this->fields as $fie) {
 675                      $field = $this->getField($fie->getName());
 676                      if ($recursive) {
 677                          $field->calculateHash($recursive);
 678                      }
 679                      $key .= $field->getHash();
 680                  }
 681              }
 682              if ($this->keys) {
 683                  foreach ($this->keys as $ke) {
 684                      $k = $this->getKey($ke->getName());
 685                      if ($recursive) {
 686                          $k->calculateHash($recursive);
 687                      }
 688                      $key .= $k->getHash();
 689                  }
 690              }
 691              if ($this->indexes) {
 692                  foreach ($this->indexes as $in) {
 693                      $index = $this->getIndex($in->getName());
 694                      if ($recursive) {
 695                          $index->calculateHash($recursive);
 696                      }
 697                      $key .= $index->getHash();
 698                  }
 699              }
 700              $this->hash = md5($key);
 701          }
 702      }
 703  
 704      /**
 705       * Validates the table restrictions (does not validate child elements).
 706       *
 707       * The error message should not be localised because it is intended for developers,
 708       * end users and admins should never see these problems!
 709       *
 710       * @param xmldb_table $xmldb_table optional when object is table
 711       * @return string null if ok, error message if problem found
 712       */
 713      public function validateDefinition(xmldb_table $xmldb_table=null) {
 714          // table parameter is ignored
 715          $name = $this->getName();
 716          if (strlen($name) > self::NAME_MAX_LENGTH) {
 717              return 'Invalid table name {'.$name.'}: name is too long. Limit is '.self::NAME_MAX_LENGTH.' chars.';
 718          }
 719          if (!preg_match('/^[a-z][a-z0-9_]*$/', $name)) {
 720              return 'Invalid table name {'.$name.'}: name includes invalid characters.';
 721          }
 722  
 723          return null;
 724      }
 725  
 726      /**
 727       * This function will output the XML text for one table
 728       * @return string
 729       */
 730      public function xmlOutput() {
 731          $o = '';
 732          $o.= '    <TABLE NAME="' . $this->name . '"';
 733          if ($this->comment) {
 734              $o.= ' COMMENT="' . htmlspecialchars($this->comment, ENT_COMPAT) . '"';
 735          }
 736          $o.= '>' . "\n";
 737          // Now the fields
 738          if ($this->fields) {
 739              $o.= '      <FIELDS>' . "\n";
 740              foreach ($this->fields as $field) {
 741                  $o.= $field->xmlOutput();
 742              }
 743              $o.= '      </FIELDS>' . "\n";
 744          }
 745          // Now the keys
 746          if ($this->keys) {
 747              $o.= '      <KEYS>' . "\n";
 748              foreach ($this->keys as $key) {
 749                  $o.= $key->xmlOutput();
 750              }
 751              $o.= '      </KEYS>' . "\n";
 752          }
 753          // Now the indexes
 754          if ($this->indexes) {
 755              $o.= '      <INDEXES>' . "\n";
 756              foreach ($this->indexes as $index) {
 757                  $o.= $index->xmlOutput();
 758              }
 759              $o.= '      </INDEXES>' . "\n";
 760          }
 761          $o.= '    </TABLE>' . "\n";
 762  
 763          return $o;
 764      }
 765  
 766      /**
 767       * This function will add one new field to the table with all
 768       * its attributes defined
 769       *
 770       * @param string $name name of the field
 771       * @param int $type XMLDB_TYPE_INTEGER, XMLDB_TYPE_NUMBER, XMLDB_TYPE_CHAR, XMLDB_TYPE_TEXT, XMLDB_TYPE_BINARY
 772       * @param string $precision length for integers and chars, two-comma separated numbers for numbers
 773       * @param bool $unsigned XMLDB_UNSIGNED or null (or false)
 774       * @param bool $notnull XMLDB_NOTNULL or null (or false)
 775       * @param bool $sequence XMLDB_SEQUENCE or null (or false)
 776       * @param mixed $default meaningful default o null (or false)
 777       * @param xmldb_object $previous name of the previous field in the table or null (or false)
 778       * @return xmlddb_field
 779       */
 780      public function add_field($name, $type, $precision=null, $unsigned=null, $notnull=null, $sequence=null, $default=null, $previous=null) {
 781          $field = new xmldb_field($name, $type, $precision, $unsigned, $notnull, $sequence, $default);
 782          $this->addField($field, $previous);
 783  
 784          return $field;
 785      }
 786  
 787      /**
 788       * This function will add one new key to the table with all
 789       * its attributes defined
 790       *
 791       * @param string $name name of the key
 792       * @param int $type XMLDB_KEY_PRIMARY, XMLDB_KEY_UNIQUE, XMLDB_KEY_FOREIGN
 793       * @param array $fields an array of fieldnames to build the key over
 794       * @param string $reftable name of the table the FK points to or null
 795       * @param array $reffields an array of fieldnames in the FK table or null
 796       */
 797      public function add_key($name, $type, $fields, $reftable=null, $reffields=null) {
 798          $key = new xmldb_key($name, $type, $fields, $reftable, $reffields);
 799          $this->addKey($key);
 800      }
 801  
 802      /**
 803       * This function will add one new index to the table with all
 804       * its attributes defined
 805       *
 806       * @param string $name name of the index
 807       * @param int $type XMLDB_INDEX_UNIQUE, XMLDB_INDEX_NOTUNIQUE
 808       * @param array $fields an array of fieldnames to build the index over
 809       * @param array $hints optional index type hints
 810       */
 811      public function add_index($name, $type, $fields, $hints = array()) {
 812          $index = new xmldb_index($name, $type, $fields, $hints);
 813          $this->addIndex($index);
 814      }
 815  
 816      /**
 817       * This function will return all the errors found in one table
 818       * looking recursively inside each field/key/index. Returns
 819       * an array of errors or false
 820       */
 821      public function getAllErrors() {
 822  
 823          $errors = array();
 824          // First the table itself
 825          if ($this->getError()) {
 826              $errors[] = $this->getError();
 827          }
 828          // Delegate to fields
 829          if ($fields = $this->getFields()) {
 830              foreach ($fields as $field) {
 831                  if ($field->getError()) {
 832                      $errors[] = $field->getError();
 833                  }
 834              }
 835          }
 836          // Delegate to keys
 837          if ($keys = $this->getKeys()) {
 838              foreach ($keys as $key) {
 839                  if ($key->getError()) {
 840                      $errors[] = $key->getError();
 841                  }
 842              }
 843          }
 844          // Delegate to indexes
 845          if ($indexes = $this->getIndexes()) {
 846              foreach ($indexes as $index) {
 847                  if ($index->getError()) {
 848                      $errors[] = $index->getError();
 849                  }
 850              }
 851          }
 852          // Return decision
 853          if (count($errors)) {
 854              return $errors;
 855          } else {
 856              return false;
 857          }
 858      }
 859  }