Search moodle.org's
Developer Documentation

See Release Notes

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

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