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 Index
  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_index extends xmldb_object {
  30  
  31      /** @var bool is unique? */
  32      protected $unique;
  33  
  34      /** @var array index fields */
  35      protected $fields;
  36  
  37      /** @var array index hints */
  38      protected $hints;
  39  
  40      /**
  41       * Note:
  42       *  - MySQL: MyISAM has a limit of 1000 bytes for any key including composed, InnoDB has limit 3500 bytes.
  43       *
  44       * @const max length of composed indexes, one utf-8 char is 3 bytes in the worst case
  45       */
  46      const INDEX_COMPOSED_MAX_BYTES = 999;
  47  
  48      /**
  49       * Note:
  50       *  - MySQL: InnoDB limits size of index on single column to 767bytes (256 chars)
  51       *
  52       * @const single column index length limit, one utf-8 char is 3 bytes in the worst case
  53       */
  54      const INDEX_MAX_BYTES = 765;
  55  
  56      /**
  57       * Creates one new xmldb_index
  58       *
  59       * @param string $name
  60       * @param string $type XMLDB_INDEX_UNIQUE, XMLDB_INDEX_NOTUNIQUE
  61       * @param array $fields an array of fieldnames to build the index over
  62       * @param array $hints an array of optional hints
  63       */
  64      public function __construct($name, $type=null, $fields=array(), $hints=array()) {
  65          $this->unique = false;
  66          $this->fields = array();
  67          $this->hints = array();
  68          parent::__construct($name);
  69          $this->set_attributes($type, $fields, $hints);
  70      }
  71  
  72      /**
  73       * Set all the attributes of one xmldb_index
  74       *
  75       * @param string type XMLDB_INDEX_UNIQUE, XMLDB_INDEX_NOTUNIQUE
  76       * @param array fields an array of fieldnames to build the index over
  77       * @param array $hints array of optional hints
  78       */
  79      public function set_attributes($type, $fields, $hints = array()) {
  80          $this->unique = !empty($type) ? true : false;
  81          $this->fields = $fields;
  82          $this->hints = $hints;
  83      }
  84  
  85      /**
  86       * Get the index unique
  87       * @return bool
  88       */
  89      public function getUnique() {
  90          return $this->unique;
  91      }
  92  
  93      /**
  94       * Set the index unique
  95       * @param bool $unique
  96       */
  97      public function setUnique($unique = true) {
  98          $this->unique = $unique;
  99      }
 100  
 101      /**
 102       * Set the index fields
 103       * @param array $fields
 104       */
 105      public function setFields($fields) {
 106          $this->fields = $fields;
 107      }
 108  
 109      /**
 110       * Get the index fields
 111       * @return array
 112       */
 113      public function getFields() {
 114          return $this->fields;
 115      }
 116  
 117      /**
 118       * Set optional index hints.
 119       * @param array $hints
 120       */
 121      public function setHints($hints) {
 122          $this->hints = $hints;
 123      }
 124  
 125      /**
 126       * Returns optional index hints.
 127       * @return array
 128       */
 129      public function getHints() {
 130          return $this->hints;
 131      }
 132  
 133      /**
 134       * Load data from XML to the index
 135       * @param $xmlarr array
 136       * @return bool
 137       */
 138      public function arr2xmldb_index($xmlarr) {
 139  
 140          $result = true;
 141  
 142          // Debug the table
 143          // traverse_xmlize($xmlarr);                   //Debug
 144          // print_object ($GLOBALS['traverse_array']);  //Debug
 145          // $GLOBALS['traverse_array']="";              //Debug
 146  
 147          // Process key attributes (name, unique, fields, comment, previous, next)
 148          if (isset($xmlarr['@']['NAME'])) {
 149              $this->name = trim($xmlarr['@']['NAME']);
 150          } else {
 151              $this->errormsg = 'Missing NAME attribute';
 152              $this->debug($this->errormsg);
 153              $result = false;
 154          }
 155  
 156          if (isset($xmlarr['@']['UNIQUE'])) {
 157              $unique = strtolower(trim($xmlarr['@']['UNIQUE']));
 158              if ($unique == 'true') {
 159                  $this->unique = true;
 160              } else if ($unique == 'false') {
 161                  $this->unique = false;
 162              } else {
 163                  $this->errormsg = 'Incorrect UNIQUE attribute (true/false allowed)';
 164                  $this->debug($this->errormsg);
 165                  $result = false;
 166              }
 167          } else {
 168                  $this->errormsg = 'Undefined UNIQUE attribute';
 169                  $this->debug($this->errormsg);
 170                  $result = false;
 171          }
 172  
 173          if (isset($xmlarr['@']['FIELDS'])) {
 174              $fields = strtolower(trim($xmlarr['@']['FIELDS']));
 175              if ($fields) {
 176                  $fieldsarr = explode(',',$fields);
 177                  if ($fieldsarr) {
 178                      foreach ($fieldsarr as $key => $element) {
 179                          $fieldsarr [$key] = trim($element);
 180                      }
 181                  } else {
 182                      $this->errormsg = 'Incorrect FIELDS attribute (comma separated of fields)';
 183                      $this->debug($this->errormsg);
 184                      $result = false;
 185                  }
 186              } else {
 187                  $this->errormsg = 'Empty FIELDS attribute';
 188                  $this->debug($this->errormsg);
 189                  $result = false;
 190              }
 191          } else {
 192              $this->errormsg = 'Missing FIELDS attribute';
 193              $this->debug($this->errormsg);
 194              $result = false;
 195          }
 196          // Finally, set the array of fields
 197          $this->fields = $fieldsarr;
 198  
 199          if (isset($xmlarr['@']['HINTS'])) {
 200              $this->hints = array();
 201              $hints = strtolower(trim($xmlarr['@']['HINTS']));
 202              if ($hints !== '') {
 203                  $hints = explode(',', $hints);
 204                  $this->hints = array_map('trim', $hints);
 205              }
 206          }
 207  
 208          if (isset($xmlarr['@']['COMMENT'])) {
 209              $this->comment = trim($xmlarr['@']['COMMENT']);
 210          }
 211  
 212          // Set some attributes
 213          if ($result) {
 214              $this->loaded = true;
 215          }
 216          $this->calculateHash();
 217          return $result;
 218      }
 219  
 220      /**
 221       * This function calculate and set the hash of one xmldb_index
 222       * @retur nvoid, changes $this->hash
 223       */
 224       public function calculateHash($recursive = false) {
 225          if (!$this->loaded) {
 226              $this->hash = null;
 227          } else {
 228              $key = $this->unique . implode (', ', $this->fields) . implode (', ', $this->hints);
 229              $this->hash = md5($key);
 230          }
 231      }
 232  
 233      /**
 234       *This function will output the XML text for one index
 235       * @return string
 236       */
 237      public function xmlOutput() {
 238          $o = '';
 239          $o.= '        <INDEX NAME="' . $this->name . '"';
 240          if ($this->unique) {
 241              $unique = 'true';
 242          } else {
 243              $unique = 'false';
 244          }
 245          $o.= ' UNIQUE="' . $unique . '"';
 246          $o.= ' FIELDS="' . implode(', ', $this->fields) . '"';
 247          if ($this->hints) {
 248              $o.= ' HINTS="' . implode(', ', $this->hints) . '"';
 249          }
 250          if ($this->comment) {
 251              $o.= ' COMMENT="' . htmlspecialchars($this->comment, ENT_COMPAT) . '"';
 252          }
 253          $o.= '/>' . "\n";
 254  
 255          return $o;
 256      }
 257  
 258      /**
 259       * This function will set all the attributes of the xmldb_index object
 260       * based on information passed in one ADOindex
 261       * @param array
 262       * @return void
 263       */
 264      public function setFromADOIndex($adoindex) {
 265  
 266          // Set the unique field
 267          $this->unique = false;
 268          // Set the fields, converting all them to lowercase
 269          $fields = array_flip(array_change_key_case(array_flip($adoindex['columns'])));
 270          $this->fields = $fields;
 271          // Some more fields
 272          $this->loaded = true;
 273          $this->changed = true;
 274      }
 275  
 276      /**
 277       * Returns the PHP code needed to define one xmldb_index
 278       * @return string
 279       */
 280      public function getPHP() {
 281  
 282          $result = '';
 283  
 284          // The type
 285          $unique = $this->getUnique();
 286          if (!empty($unique)) {
 287              $result .= 'XMLDB_INDEX_UNIQUE, ';
 288          } else {
 289              $result .= 'XMLDB_INDEX_NOTUNIQUE, ';
 290          }
 291          // The fields
 292          $indexfields = $this->getFields();
 293          if (!empty($indexfields)) {
 294              $result .= "['".  implode("', '", $indexfields) . "']";
 295          } else {
 296              $result .= 'null';
 297          }
 298          // Hints
 299          $hints = $this->getHints();
 300          if (!empty($hints)) {
 301              $result .= ", ['".  implode("', '", $hints) . "']";
 302          }
 303  
 304          // Return result
 305          return $result;
 306      }
 307  
 308      /**
 309       * Shows info in a readable format
 310       * @return string
 311       */
 312      public function readableInfo() {
 313          $o = '';
 314          // unique
 315          if ($this->unique) {
 316              $o .= 'unique';
 317          } else {
 318              $o .= 'not unique';
 319          }
 320          // fields
 321          $o .= ' (' . implode(', ', $this->fields) . ')';
 322  
 323          if ($this->hints) {
 324              $o .= ' [' . implode(', ', $this->hints) . ']';
 325          }
 326  
 327          return $o;
 328      }
 329  
 330      /**
 331       * Validates the index restrictions.
 332       *
 333       * The error message should not be localised because it is intended for developers,
 334       * end users and admins should never see these problems!
 335       *
 336       * @param xmldb_table $xmldb_table optional when object is table
 337       * @return string null if ok, error message if problem found
 338       */
 339      public function validateDefinition(xmldb_table $xmldb_table=null) {
 340          if (!$xmldb_table) {
 341              return 'Invalid xmldb_index->validateDefinition() call, $xmldb_table is required.';
 342          }
 343  
 344          $total = 0;
 345          foreach ($this->getFields() as $fieldname) {
 346              if (!$field = $xmldb_table->getField($fieldname)) {
 347                  // argh, we do not have the fields loaded yet, this should not happen during install
 348                  continue;
 349              }
 350  
 351              switch ($field->getType()) {
 352                  case XMLDB_TYPE_INTEGER:
 353                      $total += 8; // big int
 354                      break;
 355  
 356                  case XMLDB_TYPE_NUMBER:
 357                      $total += 12; // this is just a guess
 358                      break;
 359  
 360                  case XMLDB_TYPE_FLOAT:
 361                      $total += 8; // double precision
 362                      break;
 363  
 364                  case XMLDB_TYPE_CHAR:
 365                      if ($field->getLength() > self::INDEX_MAX_BYTES / 3) {
 366                          return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: XMLDB_TYPE_CHAR field "'.$field->getName().'" can not be indexed because it is too long.'
 367                                  .' Limit is '.(self::INDEX_MAX_BYTES/3).' chars.';
 368                      }
 369                      $total += ($field->getLength() * 3); // the most complex utf-8 chars have 3 bytes
 370                      break;
 371  
 372                  case XMLDB_TYPE_TEXT:
 373                      return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: XMLDB_TYPE_TEXT field "'.$field->getName().'" can not be indexed';
 374                      break;
 375  
 376                  case XMLDB_TYPE_BINARY:
 377                      return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: XMLDB_TYPE_BINARY field "'.$field->getName().'" can not be indexed';
 378                      break;
 379  
 380                  case XMLDB_TYPE_DATETIME:
 381                      $total += 8; // this is just a guess
 382                      break;
 383  
 384                  case XMLDB_TYPE_TIMESTAMP:
 385                      $total += 8; // this is just a guess
 386                      break;
 387              }
 388          }
 389  
 390          if ($total > self::INDEX_COMPOSED_MAX_BYTES) {
 391              return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: the composed index on fields "'.implode(',', $this->getFields()).'" is too long.'
 392                      .' Limit is '.self::INDEX_COMPOSED_MAX_BYTES.' bytes / '.(self::INDEX_COMPOSED_MAX_BYTES/3).' chars.';
 393          }
 394  
 395          return null;
 396      }
 397  }