| 1 | efrain | 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 |  * Database manager instance is responsible for all database structure modifications.
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * @package    core_ddl
 | 
        
           |  |  | 21 |  * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
 | 
        
           |  |  | 22 |  *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
 | 
        
           |  |  | 23 |  *             2008 Petr Skoda                   http://skodak.org
 | 
        
           |  |  | 24 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 25 |  */
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 28 |   | 
        
           |  |  | 29 | /**
 | 
        
           |  |  | 30 |  * Database manager instance is responsible for all database structure modifications.
 | 
        
           |  |  | 31 |  *
 | 
        
           |  |  | 32 |  * It is using db specific generators to find out the correct SQL syntax to do that.
 | 
        
           |  |  | 33 |  *
 | 
        
           |  |  | 34 |  * @package    core_ddl
 | 
        
           |  |  | 35 |  * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
 | 
        
           |  |  | 36 |  *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
 | 
        
           |  |  | 37 |  *             2008 Petr Skoda                   http://skodak.org
 | 
        
           |  |  | 38 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 39 |  */
 | 
        
           |  |  | 40 | class database_manager {
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 |     /** @var moodle_database A moodle_database driver specific instance.*/
 | 
        
           |  |  | 43 |     protected $mdb;
 | 
        
           |  |  | 44 |   | 
        
           |  |  | 45 |     /** @var sql_generator A driver specific SQL generator instance. Public because XMLDB editor needs to access it.*/
 | 
        
           |  |  | 46 |     public $generator;
 | 
        
           |  |  | 47 |   | 
        
           |  |  | 48 |     /**
 | 
        
           |  |  | 49 |      * Creates a new database manager instance.
 | 
        
           |  |  | 50 |      * @param moodle_database $mdb A moodle_database driver specific instance.
 | 
        
           |  |  | 51 |      * @param sql_generator $generator A driver specific SQL generator instance.
 | 
        
           |  |  | 52 |      */
 | 
        
           |  |  | 53 |     public function __construct($mdb, $generator) {
 | 
        
           |  |  | 54 |         $this->mdb       = $mdb;
 | 
        
           |  |  | 55 |         $this->generator = $generator;
 | 
        
           |  |  | 56 |     }
 | 
        
           |  |  | 57 |   | 
        
           |  |  | 58 |     /**
 | 
        
           |  |  | 59 |      * Releases all resources
 | 
        
           |  |  | 60 |      */
 | 
        
           |  |  | 61 |     public function dispose() {
 | 
        
           |  |  | 62 |         if ($this->generator) {
 | 
        
           |  |  | 63 |             $this->generator->dispose();
 | 
        
           |  |  | 64 |             $this->generator = null;
 | 
        
           |  |  | 65 |         }
 | 
        
           |  |  | 66 |         $this->mdb = null;
 | 
        
           |  |  | 67 |     }
 | 
        
           |  |  | 68 |   | 
        
           |  |  | 69 |     /**
 | 
        
           |  |  | 70 |      * This function will execute an array of SQL commands.
 | 
        
           |  |  | 71 |      *
 | 
        
           |  |  | 72 |      * @param string[] $sqlarr Array of sql statements to execute.
 | 
        
           |  |  | 73 |      * @param array|null $tablenames an array of xmldb table names affected by this request.
 | 
        
           |  |  | 74 |      * @throws ddl_change_structure_exception This exception is thrown if any error is found.
 | 
        
           |  |  | 75 |      */
 | 
        
           |  |  | 76 |     protected function execute_sql_arr(array $sqlarr, $tablenames = null) {
 | 
        
           |  |  | 77 |         $this->mdb->change_database_structure($sqlarr, $tablenames);
 | 
        
           |  |  | 78 |     }
 | 
        
           |  |  | 79 |   | 
        
           |  |  | 80 |     /**
 | 
        
           |  |  | 81 |      * Execute a given sql command string.
 | 
        
           |  |  | 82 |      *
 | 
        
           |  |  | 83 |      * @param string $sql The sql string you wish to be executed.
 | 
        
           |  |  | 84 |      * @throws ddl_change_structure_exception This exception is thrown if any error is found.
 | 
        
           |  |  | 85 |      */
 | 
        
           |  |  | 86 |     protected function execute_sql($sql) {
 | 
        
           |  |  | 87 |         $this->mdb->change_database_structure($sql);
 | 
        
           |  |  | 88 |     }
 | 
        
           |  |  | 89 |   | 
        
           |  |  | 90 |     /**
 | 
        
           |  |  | 91 |      * Given one xmldb_table, check if it exists in DB (true/false).
 | 
        
           |  |  | 92 |      *
 | 
        
           |  |  | 93 |      * @param string|xmldb_table $table The table to be searched (string name or xmldb_table instance).
 | 
        
           |  |  | 94 |      * @return bool True is a table exists, false otherwise.
 | 
        
           |  |  | 95 |      */
 | 
        
           |  |  | 96 |     public function table_exists($table) {
 | 
        
           |  |  | 97 |         if (!is_string($table) and !($table instanceof xmldb_table)) {
 | 
        
           |  |  | 98 |             throw new ddl_exception('ddlunknownerror', NULL, 'incorrect table parameter!');
 | 
        
           |  |  | 99 |         }
 | 
        
           |  |  | 100 |         return $this->generator->table_exists($table);
 | 
        
           |  |  | 101 |     }
 | 
        
           |  |  | 102 |   | 
        
           |  |  | 103 |     /**
 | 
        
           |  |  | 104 |      * Reset a sequence to the id field of a table.
 | 
        
           |  |  | 105 |      * @param string|xmldb_table $table Name of table.
 | 
        
           |  |  | 106 |      * @throws ddl_exception thrown upon reset errors.
 | 
        
           |  |  | 107 |      */
 | 
        
           |  |  | 108 |     public function reset_sequence($table) {
 | 
        
           |  |  | 109 |         if (!is_string($table) and !($table instanceof xmldb_table)) {
 | 
        
           |  |  | 110 |             throw new ddl_exception('ddlunknownerror', NULL, 'incorrect table parameter!');
 | 
        
           |  |  | 111 |         } else {
 | 
        
           |  |  | 112 |             if ($table instanceof xmldb_table) {
 | 
        
           |  |  | 113 |                 $tablename = $table->getName();
 | 
        
           |  |  | 114 |             } else {
 | 
        
           |  |  | 115 |                 $tablename = $table;
 | 
        
           |  |  | 116 |             }
 | 
        
           |  |  | 117 |         }
 | 
        
           |  |  | 118 |   | 
        
           |  |  | 119 |         // Do not test if table exists because it is slow
 | 
        
           |  |  | 120 |   | 
        
           |  |  | 121 |         if (!$sqlarr = $this->generator->getResetSequenceSQL($table)) {
 | 
        
           |  |  | 122 |             throw new ddl_exception('ddlunknownerror', null, 'table reset sequence sql not generated');
 | 
        
           |  |  | 123 |         }
 | 
        
           |  |  | 124 |   | 
        
           |  |  | 125 |         $this->execute_sql_arr($sqlarr, array($tablename));
 | 
        
           |  |  | 126 |     }
 | 
        
           |  |  | 127 |   | 
        
           |  |  | 128 |     /**
 | 
        
           |  |  | 129 |      * Given one xmldb_field, check if it exists in DB (true/false).
 | 
        
           |  |  | 130 |      *
 | 
        
           |  |  | 131 |      * @param string|xmldb_table $table The table to be searched (string name or xmldb_table instance).
 | 
        
           |  |  | 132 |      * @param string|xmldb_field $field The field to be searched for (string name or xmldb_field instance).
 | 
        
           |  |  | 133 |      * @return boolean true is exists false otherwise.
 | 
        
           |  |  | 134 |      * @throws ddl_table_missing_exception
 | 
        
           |  |  | 135 |      */
 | 
        
           |  |  | 136 |     public function field_exists($table, $field) {
 | 
        
           |  |  | 137 |         // Calculate the name of the table
 | 
        
           |  |  | 138 |         if (is_string($table)) {
 | 
        
           |  |  | 139 |             $tablename = $table;
 | 
        
           |  |  | 140 |         } else {
 | 
        
           |  |  | 141 |             $tablename = $table->getName();
 | 
        
           |  |  | 142 |         }
 | 
        
           |  |  | 143 |   | 
        
           |  |  | 144 |         // Check the table exists
 | 
        
           |  |  | 145 |         if (!$this->table_exists($table)) {
 | 
        
           |  |  | 146 |             throw new ddl_table_missing_exception($tablename);
 | 
        
           |  |  | 147 |         }
 | 
        
           |  |  | 148 |   | 
        
           |  |  | 149 |         if (is_string($field)) {
 | 
        
           |  |  | 150 |             $fieldname = $field;
 | 
        
           |  |  | 151 |         } else {
 | 
        
           |  |  | 152 |             // Calculate the name of the table
 | 
        
           |  |  | 153 |             $fieldname = $field->getName();
 | 
        
           |  |  | 154 |         }
 | 
        
           |  |  | 155 |   | 
        
           |  |  | 156 |         // Get list of fields in table
 | 
        
           |  |  | 157 |         $columns = $this->mdb->get_columns($tablename);
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |         $exists = array_key_exists($fieldname,  $columns);
 | 
        
           |  |  | 160 |   | 
        
           |  |  | 161 |         return $exists;
 | 
        
           |  |  | 162 |     }
 | 
        
           |  |  | 163 |   | 
        
           |  |  | 164 |     /**
 | 
        
           |  |  | 165 |      * Given one xmldb_index, the function returns the name of the index in DB
 | 
        
           |  |  | 166 |      * of false if it doesn't exist
 | 
        
           |  |  | 167 |      *
 | 
        
           |  |  | 168 |      * @param xmldb_table $xmldb_table table to be searched
 | 
        
           |  |  | 169 |      * @param xmldb_index $xmldb_index the index to be searched
 | 
        
           |  |  | 170 |      * @param bool $returnall true means return array of all indexes, false means first index only as string
 | 
        
           |  |  | 171 |      * @return array|string|bool Index name, array of index names or false if no indexes are found.
 | 
        
           |  |  | 172 |      * @throws ddl_table_missing_exception Thrown when table is not found.
 | 
        
           |  |  | 173 |      */
 | 
        
           |  |  | 174 |     public function find_index_name(xmldb_table $xmldb_table, xmldb_index $xmldb_index, $returnall = false) {
 | 
        
           |  |  | 175 |         // Calculate the name of the table
 | 
        
           |  |  | 176 |         $tablename = $xmldb_table->getName();
 | 
        
           |  |  | 177 |   | 
        
           |  |  | 178 |         // Check the table exists
 | 
        
           |  |  | 179 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 180 |             throw new ddl_table_missing_exception($tablename);
 | 
        
           |  |  | 181 |         }
 | 
        
           |  |  | 182 |   | 
        
           |  |  | 183 |         // Extract index columns
 | 
        
           |  |  | 184 |         $indcolumns = $xmldb_index->getFields();
 | 
        
           |  |  | 185 |   | 
        
           |  |  | 186 |         // Get list of indexes in table
 | 
        
           |  |  | 187 |         $indexes = $this->mdb->get_indexes($tablename);
 | 
        
           |  |  | 188 |   | 
        
           |  |  | 189 |         $return = array();
 | 
        
           |  |  | 190 |   | 
        
           |  |  | 191 |         // Iterate over them looking for columns coincidence
 | 
        
           |  |  | 192 |         foreach ($indexes as $indexname => $index) {
 | 
        
           |  |  | 193 |             $columns = $index['columns'];
 | 
        
           |  |  | 194 |             // Check if index matches queried index
 | 
        
           |  |  | 195 |             $diferences = array_merge(array_diff($columns, $indcolumns), array_diff($indcolumns, $columns));
 | 
        
           |  |  | 196 |             // If no differences, we have find the index
 | 
        
           |  |  | 197 |             if (empty($diferences)) {
 | 
        
           |  |  | 198 |                 if ($returnall) {
 | 
        
           |  |  | 199 |                     $return[] = $indexname;
 | 
        
           |  |  | 200 |                 } else {
 | 
        
           |  |  | 201 |                     return $indexname;
 | 
        
           |  |  | 202 |                 }
 | 
        
           |  |  | 203 |             }
 | 
        
           |  |  | 204 |         }
 | 
        
           |  |  | 205 |   | 
        
           |  |  | 206 |         if ($return and $returnall) {
 | 
        
           |  |  | 207 |             return $return;
 | 
        
           |  |  | 208 |         }
 | 
        
           |  |  | 209 |   | 
        
           |  |  | 210 |         // Arriving here, index not found
 | 
        
           |  |  | 211 |         return false;
 | 
        
           |  |  | 212 |     }
 | 
        
           |  |  | 213 |   | 
        
           |  |  | 214 |     /**
 | 
        
           |  |  | 215 |      * Given one xmldb_index, check if it exists in DB (true/false).
 | 
        
           |  |  | 216 |      *
 | 
        
           |  |  | 217 |      * @param xmldb_table $xmldb_table The table to be searched.
 | 
        
           |  |  | 218 |      * @param xmldb_index $xmldb_index The index to be searched for.
 | 
        
           |  |  | 219 |      * @return boolean true id index exists, false otherwise.
 | 
        
           |  |  | 220 |      */
 | 
        
           |  |  | 221 |     public function index_exists(xmldb_table $xmldb_table, xmldb_index $xmldb_index) {
 | 
        
           |  |  | 222 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 223 |             return false;
 | 
        
           |  |  | 224 |         }
 | 
        
           |  |  | 225 |         return ($this->find_index_name($xmldb_table, $xmldb_index) !== false);
 | 
        
           |  |  | 226 |     }
 | 
        
           |  |  | 227 |   | 
        
           |  |  | 228 |     /**
 | 
        
           |  |  | 229 |      * This function IS NOT IMPLEMENTED. ONCE WE'LL BE USING RELATIONAL
 | 
        
           |  |  | 230 |      * INTEGRITY IT WILL BECOME MORE USEFUL. FOR NOW, JUST CALCULATE "OFFICIAL"
 | 
        
           |  |  | 231 |      * KEY NAMES WITHOUT ACCESSING TO DB AT ALL.
 | 
        
           |  |  | 232 |      * Given one xmldb_key, the function returns the name of the key in DB (if exists)
 | 
        
           |  |  | 233 |      * of false if it doesn't exist
 | 
        
           |  |  | 234 |      *
 | 
        
           |  |  | 235 |      * @param xmldb_table $xmldb_table The table to be searched.
 | 
        
           |  |  | 236 |      * @param xmldb_key $xmldb_key The key to be searched.
 | 
        
           |  |  | 237 |      * @return string key name if found
 | 
        
           |  |  | 238 |      */
 | 
        
           |  |  | 239 |     public function find_key_name(xmldb_table $xmldb_table, xmldb_key $xmldb_key) {
 | 
        
           |  |  | 240 |   | 
        
           |  |  | 241 |         $keycolumns = $xmldb_key->getFields();
 | 
        
           |  |  | 242 |   | 
        
           |  |  | 243 |         // Get list of keys in table
 | 
        
           |  |  | 244 |         // first primaries (we aren't going to use this now, because the MetaPrimaryKeys is awful)
 | 
        
           |  |  | 245 |             //TODO: To implement when we advance in relational integrity
 | 
        
           |  |  | 246 |         // then uniques (note that Moodle, for now, shouldn't have any UNIQUE KEY for now, but unique indexes)
 | 
        
           |  |  | 247 |             //TODO: To implement when we advance in relational integrity (note that AdoDB hasn't any MetaXXX for this.
 | 
        
           |  |  | 248 |         // then foreign (note that Moodle, for now, shouldn't have any FOREIGN KEY for now, but indexes)
 | 
        
           |  |  | 249 |             //TODO: To implement when we advance in relational integrity (note that AdoDB has one MetaForeignKeys()
 | 
        
           |  |  | 250 |             //but it's far from perfect.
 | 
        
           |  |  | 251 |         // TODO: To create the proper functions inside each generator to retrieve all the needed KEY info (name
 | 
        
           |  |  | 252 |         //       columns, reftable and refcolumns
 | 
        
           |  |  | 253 |   | 
        
           |  |  | 254 |         // So all we do is to return the official name of the requested key without any confirmation!)
 | 
        
           |  |  | 255 |         // One exception, hardcoded primary constraint names
 | 
        
           |  |  | 256 |         if ($this->generator->primary_key_name && $xmldb_key->getType() == XMLDB_KEY_PRIMARY) {
 | 
        
           |  |  | 257 |             return $this->generator->primary_key_name;
 | 
        
           |  |  | 258 |         } else {
 | 
        
           |  |  | 259 |             // Calculate the name suffix
 | 
        
           |  |  | 260 |             switch ($xmldb_key->getType()) {
 | 
        
           |  |  | 261 |                 case XMLDB_KEY_PRIMARY:
 | 
        
           |  |  | 262 |                     $suffix = 'pk';
 | 
        
           |  |  | 263 |                     break;
 | 
        
           |  |  | 264 |                 case XMLDB_KEY_UNIQUE:
 | 
        
           |  |  | 265 |                     $suffix = 'uk';
 | 
        
           |  |  | 266 |                     break;
 | 
        
           |  |  | 267 |                 case XMLDB_KEY_FOREIGN_UNIQUE:
 | 
        
           |  |  | 268 |                 case XMLDB_KEY_FOREIGN:
 | 
        
           |  |  | 269 |                     $suffix = 'fk';
 | 
        
           |  |  | 270 |                     break;
 | 
        
           |  |  | 271 |             }
 | 
        
           |  |  | 272 |             // And simply, return the official name
 | 
        
           |  |  | 273 |             return $this->generator->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), $suffix);
 | 
        
           |  |  | 274 |         }
 | 
        
           |  |  | 275 |     }
 | 
        
           |  |  | 276 |   | 
        
           |  |  | 277 |     /**
 | 
        
           |  |  | 278 |      * This function will delete all tables found in XMLDB file from db
 | 
        
           |  |  | 279 |      *
 | 
        
           |  |  | 280 |      * @param string $file Full path to the XML file to be used.
 | 
        
           |  |  | 281 |      * @return void
 | 
        
           |  |  | 282 |      */
 | 
        
           |  |  | 283 |     public function delete_tables_from_xmldb_file($file) {
 | 
        
           |  |  | 284 |   | 
        
           |  |  | 285 |         $xmldb_file = new xmldb_file($file);
 | 
        
           |  |  | 286 |   | 
        
           |  |  | 287 |         if (!$xmldb_file->fileExists()) {
 | 
        
           |  |  | 288 |             throw new ddl_exception('ddlxmlfileerror', null, 'File does not exist');
 | 
        
           |  |  | 289 |         }
 | 
        
           |  |  | 290 |   | 
        
           |  |  | 291 |         $loaded    = $xmldb_file->loadXMLStructure();
 | 
        
           |  |  | 292 |         $structure = $xmldb_file->getStructure();
 | 
        
           |  |  | 293 |   | 
        
           |  |  | 294 |         if (!$loaded || !$xmldb_file->isLoaded()) {
 | 
        
           |  |  | 295 |             // Show info about the error if we can find it
 | 
        
           |  |  | 296 |             if ($structure) {
 | 
        
           |  |  | 297 |                 if ($errors = $structure->getAllErrors()) {
 | 
        
           |  |  | 298 |                     throw new ddl_exception('ddlxmlfileerror', null, 'Errors found in XMLDB file: '. implode (', ', $errors));
 | 
        
           |  |  | 299 |                 }
 | 
        
           |  |  | 300 |             }
 | 
        
           |  |  | 301 |             throw new ddl_exception('ddlxmlfileerror', null, 'not loaded??');
 | 
        
           |  |  | 302 |         }
 | 
        
           |  |  | 303 |   | 
        
           |  |  | 304 |         if ($xmldb_tables = $structure->getTables()) {
 | 
        
           |  |  | 305 |             // Delete in opposite order, this should help with foreign keys in the future.
 | 
        
           |  |  | 306 |             $xmldb_tables = array_reverse($xmldb_tables);
 | 
        
           |  |  | 307 |             foreach($xmldb_tables as $table) {
 | 
        
           |  |  | 308 |                 if ($this->table_exists($table)) {
 | 
        
           |  |  | 309 |                     $this->drop_table($table);
 | 
        
           |  |  | 310 |                 }
 | 
        
           |  |  | 311 |             }
 | 
        
           |  |  | 312 |         }
 | 
        
           |  |  | 313 |     }
 | 
        
           |  |  | 314 |   | 
        
           |  |  | 315 |     /**
 | 
        
           |  |  | 316 |      * This function will drop the table passed as argument
 | 
        
           |  |  | 317 |      * and all the associated objects (keys, indexes, constraints, sequences, triggers)
 | 
        
           |  |  | 318 |      * will be dropped too.
 | 
        
           |  |  | 319 |      *
 | 
        
           |  |  | 320 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 321 |      * @return void
 | 
        
           |  |  | 322 |      */
 | 
        
           |  |  | 323 |     public function drop_table(xmldb_table $xmldb_table) {
 | 
        
           |  |  | 324 |         // Check table exists
 | 
        
           |  |  | 325 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 326 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 327 |         }
 | 
        
           |  |  | 328 |   | 
        
           |  |  | 329 |         if (!$sqlarr = $this->generator->getDropTableSQL($xmldb_table)) {
 | 
        
           |  |  | 330 |             throw new ddl_exception('ddlunknownerror', null, 'table drop sql not generated');
 | 
        
           |  |  | 331 |         }
 | 
        
           |  |  | 332 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 333 |   | 
        
           |  |  | 334 |         $this->generator->cleanup_after_drop($xmldb_table);
 | 
        
           |  |  | 335 |     }
 | 
        
           |  |  | 336 |   | 
        
           |  |  | 337 |     /**
 | 
        
           |  |  | 338 |      * Load an install.xml file, checking that it exists, and that the structure is OK.
 | 
        
           |  |  | 339 |      * @param string $file the full path to the XMLDB file.
 | 
        
           |  |  | 340 |      * @return xmldb_file the loaded file.
 | 
        
           |  |  | 341 |      */
 | 
        
           |  |  | 342 |     private function load_xmldb_file($file) {
 | 
        
           |  |  | 343 |         $xmldb_file = new xmldb_file($file);
 | 
        
           |  |  | 344 |   | 
        
           |  |  | 345 |         if (!$xmldb_file->fileExists()) {
 | 
        
           |  |  | 346 |             throw new ddl_exception('ddlxmlfileerror', null, 'File does not exist');
 | 
        
           |  |  | 347 |         }
 | 
        
           |  |  | 348 |   | 
        
           |  |  | 349 |         $loaded = $xmldb_file->loadXMLStructure();
 | 
        
           |  |  | 350 |         if (!$loaded || !$xmldb_file->isLoaded()) {
 | 
        
           |  |  | 351 |             // Show info about the error if we can find it
 | 
        
           |  |  | 352 |             if ($structure = $xmldb_file->getStructure()) {
 | 
        
           |  |  | 353 |                 if ($errors = $structure->getAllErrors()) {
 | 
        
           |  |  | 354 |                     throw new ddl_exception('ddlxmlfileerror', null, 'Errors found in XMLDB file: '. implode (', ', $errors));
 | 
        
           |  |  | 355 |                 }
 | 
        
           |  |  | 356 |             }
 | 
        
           |  |  | 357 |             throw new ddl_exception('ddlxmlfileerror', null, 'not loaded??');
 | 
        
           |  |  | 358 |         }
 | 
        
           |  |  | 359 |   | 
        
           |  |  | 360 |         return $xmldb_file;
 | 
        
           |  |  | 361 |     }
 | 
        
           |  |  | 362 |   | 
        
           |  |  | 363 |     /**
 | 
        
           |  |  | 364 |      * This function will load one entire XMLDB file and call install_from_xmldb_structure.
 | 
        
           |  |  | 365 |      *
 | 
        
           |  |  | 366 |      * @param string $file full path to the XML file to be used
 | 
        
           |  |  | 367 |      * @return void
 | 
        
           |  |  | 368 |      */
 | 
        
           |  |  | 369 |     public function install_from_xmldb_file($file) {
 | 
        
           |  |  | 370 |         $xmldb_file = $this->load_xmldb_file($file);
 | 
        
           |  |  | 371 |         $xmldb_structure = $xmldb_file->getStructure();
 | 
        
           |  |  | 372 |         $this->install_from_xmldb_structure($xmldb_structure);
 | 
        
           |  |  | 373 |     }
 | 
        
           |  |  | 374 |   | 
        
           |  |  | 375 |     /**
 | 
        
           |  |  | 376 |      * This function will load one entire XMLDB file and call install_from_xmldb_structure.
 | 
        
           |  |  | 377 |      *
 | 
        
           |  |  | 378 |      * @param string $file full path to the XML file to be used
 | 
        
           |  |  | 379 |      * @param string $tablename the name of the table.
 | 
        
           |  |  | 380 |      * @param bool $cachestructures boolean to decide if loaded xmldb structures can be safely cached
 | 
        
           |  |  | 381 |      *             useful for testunits loading the enormous main xml file hundred of times (100x)
 | 
        
           |  |  | 382 |      */
 | 
        
           |  |  | 383 |     public function install_one_table_from_xmldb_file($file, $tablename, $cachestructures = false) {
 | 
        
           |  |  | 384 |   | 
        
           |  |  | 385 |         static $xmldbstructurecache = array(); // To store cached structures
 | 
        
           |  |  | 386 |         if (!empty($xmldbstructurecache) && array_key_exists($file, $xmldbstructurecache)) {
 | 
        
           |  |  | 387 |             $xmldb_structure = $xmldbstructurecache[$file];
 | 
        
           |  |  | 388 |         } else {
 | 
        
           |  |  | 389 |             $xmldb_file = $this->load_xmldb_file($file);
 | 
        
           |  |  | 390 |             $xmldb_structure = $xmldb_file->getStructure();
 | 
        
           |  |  | 391 |             if ($cachestructures) {
 | 
        
           |  |  | 392 |                 $xmldbstructurecache[$file] = $xmldb_structure;
 | 
        
           |  |  | 393 |             }
 | 
        
           |  |  | 394 |         }
 | 
        
           |  |  | 395 |   | 
        
           |  |  | 396 |         $targettable = $xmldb_structure->getTable($tablename);
 | 
        
           |  |  | 397 |         if (is_null($targettable)) {
 | 
        
           |  |  | 398 |             throw new ddl_exception('ddlunknowntable', null, 'The table ' . $tablename . ' is not defined in file ' . $file);
 | 
        
           |  |  | 399 |         }
 | 
        
           |  |  | 400 |         $targettable->setNext(NULL);
 | 
        
           |  |  | 401 |         $targettable->setPrevious(NULL);
 | 
        
           |  |  | 402 |   | 
        
           |  |  | 403 |         $tempstructure = new xmldb_structure('temp');
 | 
        
           |  |  | 404 |         $tempstructure->addTable($targettable);
 | 
        
           |  |  | 405 |         $this->install_from_xmldb_structure($tempstructure);
 | 
        
           |  |  | 406 |     }
 | 
        
           |  |  | 407 |   | 
        
           |  |  | 408 |     /**
 | 
        
           |  |  | 409 |      * This function will generate all the needed SQL statements, specific for each
 | 
        
           |  |  | 410 |      * RDBMS type and, finally, it will execute all those statements against the DB.
 | 
        
           |  |  | 411 |      *
 | 
        
           |  |  | 412 |      * @param stdClass $xmldb_structure xmldb_structure object.
 | 
        
           |  |  | 413 |      * @return void
 | 
        
           |  |  | 414 |      */
 | 
        
           |  |  | 415 |     public function install_from_xmldb_structure($xmldb_structure) {
 | 
        
           |  |  | 416 |   | 
        
           |  |  | 417 |         if (!$sqlarr = $this->generator->getCreateStructureSQL($xmldb_structure)) {
 | 
        
           |  |  | 418 |             return; // nothing to do
 | 
        
           |  |  | 419 |         }
 | 
        
           |  |  | 420 |   | 
        
           |  |  | 421 |         $tablenames = array();
 | 
        
           |  |  | 422 |         foreach ($xmldb_structure as $xmldb_table) {
 | 
        
           |  |  | 423 |             if ($xmldb_table instanceof xmldb_table) {
 | 
        
           |  |  | 424 |                 $tablenames[] = $xmldb_table->getName();
 | 
        
           |  |  | 425 |             }
 | 
        
           |  |  | 426 |         }
 | 
        
           |  |  | 427 |         $this->execute_sql_arr($sqlarr, $tablenames);
 | 
        
           |  |  | 428 |     }
 | 
        
           |  |  | 429 |   | 
        
           |  |  | 430 |     /**
 | 
        
           |  |  | 431 |      * This function will create the table passed as argument with all its
 | 
        
           |  |  | 432 |      * fields/keys/indexes/sequences, everything based in the XMLDB object
 | 
        
           |  |  | 433 |      *
 | 
        
           |  |  | 434 |      * @param xmldb_table $xmldb_table Table object (full specs are required).
 | 
        
           |  |  | 435 |      * @return void
 | 
        
           |  |  | 436 |      */
 | 
        
           |  |  | 437 |     public function create_table(xmldb_table $xmldb_table) {
 | 
        
           |  |  | 438 |         // Check table doesn't exist
 | 
        
           |  |  | 439 |         if ($this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 440 |             throw new ddl_exception('ddltablealreadyexists', $xmldb_table->getName());
 | 
        
           |  |  | 441 |         }
 | 
        
           |  |  | 442 |   | 
        
           |  |  | 443 |         if (!$sqlarr = $this->generator->getCreateTableSQL($xmldb_table)) {
 | 
        
           |  |  | 444 |             throw new ddl_exception('ddlunknownerror', null, 'table create sql not generated');
 | 
        
           |  |  | 445 |         }
 | 
        
           |  |  | 446 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 447 |     }
 | 
        
           |  |  | 448 |   | 
        
           |  |  | 449 |     /**
 | 
        
           |  |  | 450 |      * This function will create the temporary table passed as argument with all its
 | 
        
           |  |  | 451 |      * fields/keys/indexes/sequences, everything based in the XMLDB object
 | 
        
           |  |  | 452 |      *
 | 
        
           |  |  | 453 |      * If table already exists ddl_exception will be thrown, please make sure
 | 
        
           |  |  | 454 |      * the table name does not collide with existing normal table!
 | 
        
           |  |  | 455 |      *
 | 
        
           |  |  | 456 |      * @param xmldb_table $xmldb_table Table object (full specs are required).
 | 
        
           |  |  | 457 |      * @return void
 | 
        
           |  |  | 458 |      */
 | 
        
           |  |  | 459 |     public function create_temp_table(xmldb_table $xmldb_table) {
 | 
        
           |  |  | 460 |   | 
        
           |  |  | 461 |         // Check table doesn't exist
 | 
        
           |  |  | 462 |         if ($this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 463 |             throw new ddl_exception('ddltablealreadyexists', $xmldb_table->getName());
 | 
        
           |  |  | 464 |         }
 | 
        
           |  |  | 465 |   | 
        
           |  |  | 466 |         if (!$sqlarr = $this->generator->getCreateTempTableSQL($xmldb_table)) {
 | 
        
           |  |  | 467 |             throw new ddl_exception('ddlunknownerror', null, 'temp table create sql not generated');
 | 
        
           |  |  | 468 |         }
 | 
        
           |  |  | 469 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 470 |     }
 | 
        
           |  |  | 471 |   | 
        
           |  |  | 472 |     /**
 | 
        
           |  |  | 473 |      * This function will drop the temporary table passed as argument with all its
 | 
        
           |  |  | 474 |      * fields/keys/indexes/sequences, everything based in the XMLDB object
 | 
        
           |  |  | 475 |      *
 | 
        
           |  |  | 476 |      * It is recommended to drop temp table when not used anymore.
 | 
        
           |  |  | 477 |      *
 | 
        
           |  |  | 478 |      * @deprecated since 2.3, use drop_table() for all table types
 | 
        
           |  |  | 479 |      * @param xmldb_table $xmldb_table Table object.
 | 
        
           |  |  | 480 |      * @return void
 | 
        
           |  |  | 481 |      */
 | 
        
           |  |  | 482 |     public function drop_temp_table(xmldb_table $xmldb_table) {
 | 
        
           |  |  | 483 |         debugging('database_manager::drop_temp_table() is deprecated, use database_manager::drop_table() instead');
 | 
        
           |  |  | 484 |         $this->drop_table($xmldb_table);
 | 
        
           |  |  | 485 |     }
 | 
        
           |  |  | 486 |   | 
        
           |  |  | 487 |     /**
 | 
        
           |  |  | 488 |      * This function will rename the table passed as argument
 | 
        
           |  |  | 489 |      * Before renaming the index, the function will check it exists
 | 
        
           |  |  | 490 |      *
 | 
        
           |  |  | 491 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 492 |      * @param string $newname New name of the index.
 | 
        
           |  |  | 493 |      * @return void
 | 
        
           |  |  | 494 |      */
 | 
        
           |  |  | 495 |     public function rename_table(xmldb_table $xmldb_table, $newname) {
 | 
        
           |  |  | 496 |         // Check newname isn't empty
 | 
        
           |  |  | 497 |         if (!$newname) {
 | 
        
           |  |  | 498 |             throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 | 
        
           |  |  | 499 |         }
 | 
        
           |  |  | 500 |   | 
        
           |  |  | 501 |         $check = new xmldb_table($newname);
 | 
        
           |  |  | 502 |   | 
        
           |  |  | 503 |         // Check table already renamed
 | 
        
           |  |  | 504 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 505 |             if ($this->table_exists($check)) {
 | 
        
           |  |  | 506 |                 throw new ddl_exception('ddlunknownerror', null, 'table probably already renamed');
 | 
        
           |  |  | 507 |             } else {
 | 
        
           |  |  | 508 |                 throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 509 |             }
 | 
        
           |  |  | 510 |         }
 | 
        
           |  |  | 511 |   | 
        
           |  |  | 512 |         // Check new table doesn't exist
 | 
        
           |  |  | 513 |         if ($this->table_exists($check)) {
 | 
        
           |  |  | 514 |             throw new ddl_exception('ddltablealreadyexists', $check->getName(), 'can not rename table');
 | 
        
           |  |  | 515 |         }
 | 
        
           |  |  | 516 |   | 
        
           |  |  | 517 |         if (!$sqlarr = $this->generator->getRenameTableSQL($xmldb_table, $newname)) {
 | 
        
           |  |  | 518 |             throw new ddl_exception('ddlunknownerror', null, 'table rename sql not generated');
 | 
        
           |  |  | 519 |         }
 | 
        
           |  |  | 520 |   | 
        
           |  |  | 521 |         $this->execute_sql_arr($sqlarr);
 | 
        
           |  |  | 522 |     }
 | 
        
           |  |  | 523 |   | 
        
           |  |  | 524 |     /**
 | 
        
           |  |  | 525 |      * This function will add the field to the table passed as arguments
 | 
        
           |  |  | 526 |      *
 | 
        
           |  |  | 527 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 528 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 529 |      * @return void
 | 
        
           |  |  | 530 |      */
 | 
        
           |  |  | 531 |     public function add_field(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 532 |          // Check the field doesn't exist
 | 
        
           |  |  | 533 |         if ($this->field_exists($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 534 |             throw new ddl_exception('ddlfieldalreadyexists', $xmldb_field->getName());
 | 
        
           |  |  | 535 |         }
 | 
        
           |  |  | 536 |   | 
        
           |  |  | 537 |         // If NOT NULL and no default given (we ask the generator about the
 | 
        
           |  |  | 538 |         // *real* default that will be used) check the table is empty
 | 
        
           |  |  | 539 |         if ($xmldb_field->getNotNull() && $this->generator->getDefaultValue($xmldb_field) === NULL && $this->mdb->count_records($xmldb_table->getName())) {
 | 
        
           |  |  | 540 |             throw new ddl_exception('ddlunknownerror', null, 'Field ' . $xmldb_table->getName() . '->' . $xmldb_field->getName() .
 | 
        
           |  |  | 541 |                       ' cannot be added. Not null fields added to non empty tables require default value. Create skipped');
 | 
        
           |  |  | 542 |         }
 | 
        
           |  |  | 543 |   | 
        
           |  |  | 544 |         if (!$sqlarr = $this->generator->getAddFieldSQL($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 545 |             throw new ddl_exception('ddlunknownerror', null, 'addfield sql not generated');
 | 
        
           |  |  | 546 |         }
 | 
        
           |  |  | 547 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 548 |     }
 | 
        
           |  |  | 549 |   | 
        
           |  |  | 550 |     /**
 | 
        
           |  |  | 551 |      * This function will drop the field from the table passed as arguments
 | 
        
           |  |  | 552 |      *
 | 
        
           |  |  | 553 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 554 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 555 |      * @return void
 | 
        
           |  |  | 556 |      */
 | 
        
           |  |  | 557 |     public function drop_field(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 558 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 559 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 560 |         }
 | 
        
           |  |  | 561 |         // Check the field exists
 | 
        
           |  |  | 562 |         if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 563 |             throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 | 
        
           |  |  | 564 |         }
 | 
        
           |  |  | 565 |         // Check for dependencies in the DB before performing any action
 | 
        
           |  |  | 566 |         $this->check_field_dependencies($xmldb_table, $xmldb_field);
 | 
        
           |  |  | 567 |   | 
        
           |  |  | 568 |         if (!$sqlarr = $this->generator->getDropFieldSQL($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 569 |             throw new ddl_exception('ddlunknownerror', null, 'drop_field sql not generated');
 | 
        
           |  |  | 570 |         }
 | 
        
           |  |  | 571 |   | 
        
           |  |  | 572 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 573 |     }
 | 
        
           |  |  | 574 |   | 
        
           |  |  | 575 |     /**
 | 
        
           |  |  | 576 |      * This function will change the type of the field in the table passed as arguments
 | 
        
           |  |  | 577 |      *
 | 
        
           |  |  | 578 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 579 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 580 |      * @return void
 | 
        
           |  |  | 581 |      */
 | 
        
           |  |  | 582 |     public function change_field_type(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 583 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 584 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 585 |         }
 | 
        
           |  |  | 586 |         // Check the field exists
 | 
        
           |  |  | 587 |         if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 588 |             throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 | 
        
           |  |  | 589 |         }
 | 
        
           |  |  | 590 |         // Check for dependencies in the DB before performing any action
 | 
        
           |  |  | 591 |         $this->check_field_dependencies($xmldb_table, $xmldb_field);
 | 
        
           |  |  | 592 |   | 
        
           |  |  | 593 |         if (!$sqlarr = $this->generator->getAlterFieldSQL($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 594 |             return; // probably nothing to do
 | 
        
           |  |  | 595 |         }
 | 
        
           |  |  | 596 |   | 
        
           |  |  | 597 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 598 |     }
 | 
        
           |  |  | 599 |   | 
        
           |  |  | 600 |     /**
 | 
        
           |  |  | 601 |      * This function will change the precision of the field in the table passed as arguments
 | 
        
           |  |  | 602 |      *
 | 
        
           |  |  | 603 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 604 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 605 |      * @return void
 | 
        
           |  |  | 606 |      */
 | 
        
           |  |  | 607 |     public function change_field_precision(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 608 |         // Just a wrapper over change_field_type. Does exactly the same processing
 | 
        
           |  |  | 609 |         $this->change_field_type($xmldb_table, $xmldb_field);
 | 
        
           |  |  | 610 |     }
 | 
        
           |  |  | 611 |   | 
        
           |  |  | 612 |     /**
 | 
        
           |  |  | 613 |      * This function will change the unsigned/signed of the field in the table passed as arguments
 | 
        
           |  |  | 614 |      *
 | 
        
           |  |  | 615 |      * @deprecated since 2.3, only singed numbers are allowed now, migration is automatic
 | 
        
           |  |  | 616 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 617 |      * @param xmldb_field $xmldb_field Field object (full specs are required).
 | 
        
           |  |  | 618 |      * @return void
 | 
        
           |  |  | 619 |      */
 | 
        
           |  |  | 620 |     public function change_field_unsigned(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 621 |         debugging('All unsigned numbers are converted to signed automatically during Moodle upgrade.');
 | 
        
           |  |  | 622 |         $this->change_field_type($xmldb_table, $xmldb_field);
 | 
        
           |  |  | 623 |     }
 | 
        
           |  |  | 624 |   | 
        
           |  |  | 625 |     /**
 | 
        
           |  |  | 626 |      * This function will change the nullability of the field in the table passed as arguments
 | 
        
           |  |  | 627 |      *
 | 
        
           |  |  | 628 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 629 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 630 |      * @return void
 | 
        
           |  |  | 631 |      */
 | 
        
           |  |  | 632 |     public function change_field_notnull(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 633 |         // Just a wrapper over change_field_type. Does exactly the same processing
 | 
        
           |  |  | 634 |         $this->change_field_type($xmldb_table, $xmldb_field);
 | 
        
           |  |  | 635 |     }
 | 
        
           |  |  | 636 |   | 
        
           |  |  | 637 |     /**
 | 
        
           |  |  | 638 |      * This function will change the default of the field in the table passed as arguments
 | 
        
           |  |  | 639 |      * One null value in the default field means delete the default
 | 
        
           |  |  | 640 |      *
 | 
        
           |  |  | 641 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 642 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 643 |      * @return void
 | 
        
           |  |  | 644 |      */
 | 
        
           |  |  | 645 |     public function change_field_default(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 646 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 647 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 648 |         }
 | 
        
           |  |  | 649 |         // Check the field exists
 | 
        
           |  |  | 650 |         if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 651 |             throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 | 
        
           |  |  | 652 |         }
 | 
        
           |  |  | 653 |         // Check for dependencies in the DB before performing any action
 | 
        
           |  |  | 654 |         $this->check_field_dependencies($xmldb_table, $xmldb_field);
 | 
        
           |  |  | 655 |   | 
        
           |  |  | 656 |         if (!$sqlarr = $this->generator->getModifyDefaultSQL($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 657 |             return; //Empty array = nothing to do = no error
 | 
        
           |  |  | 658 |         }
 | 
        
           |  |  | 659 |   | 
        
           |  |  | 660 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 661 |     }
 | 
        
           |  |  | 662 |   | 
        
           |  |  | 663 |     /**
 | 
        
           |  |  | 664 |      * This function will rename the field in the table passed as arguments
 | 
        
           |  |  | 665 |      * Before renaming the field, the function will check it exists
 | 
        
           |  |  | 666 |      *
 | 
        
           |  |  | 667 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 668 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 669 |      * @param string $newname New name of the field.
 | 
        
           |  |  | 670 |      * @return void
 | 
        
           |  |  | 671 |      */
 | 
        
           |  |  | 672 |     public function rename_field(xmldb_table $xmldb_table, xmldb_field $xmldb_field, $newname) {
 | 
        
           |  |  | 673 |         if (empty($newname)) {
 | 
        
           |  |  | 674 |             throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 | 
        
           |  |  | 675 |         }
 | 
        
           |  |  | 676 |   | 
        
           |  |  | 677 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 678 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 679 |         }
 | 
        
           |  |  | 680 |   | 
        
           |  |  | 681 |         // Check the field exists
 | 
        
           |  |  | 682 |         if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 683 |             throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 | 
        
           |  |  | 684 |         }
 | 
        
           |  |  | 685 |   | 
        
           |  |  | 686 |         // Check we have included full field specs
 | 
        
           |  |  | 687 |         if (!$xmldb_field->getType()) {
 | 
        
           |  |  | 688 |             throw new ddl_exception('ddlunknownerror', null,
 | 
        
           |  |  | 689 |                       'Field ' . $xmldb_table->getName() . '->' . $xmldb_field->getName() .
 | 
        
           |  |  | 690 |                       ' must contain full specs. Rename skipped');
 | 
        
           |  |  | 691 |         }
 | 
        
           |  |  | 692 |   | 
        
           |  |  | 693 |         // Check field isn't id. Renaming over that field is not allowed
 | 
        
           |  |  | 694 |         if ($xmldb_field->getName() == 'id') {
 | 
        
           |  |  | 695 |             throw new ddl_exception('ddlunknownerror', null,
 | 
        
           |  |  | 696 |                       'Field ' . $xmldb_table->getName() . '->' . $xmldb_field->getName() .
 | 
        
           |  |  | 697 |                       ' cannot be renamed. Rename skipped');
 | 
        
           |  |  | 698 |         }
 | 
        
           |  |  | 699 |   | 
        
           |  |  | 700 |         if (!$sqlarr = $this->generator->getRenameFieldSQL($xmldb_table, $xmldb_field, $newname)) {
 | 
        
           |  |  | 701 |             return; //Empty array = nothing to do = no error
 | 
        
           |  |  | 702 |         }
 | 
        
           |  |  | 703 |   | 
        
           |  |  | 704 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 705 |     }
 | 
        
           |  |  | 706 |   | 
        
           |  |  | 707 |     /**
 | 
        
           |  |  | 708 |      * This function will check, for the given table and field, if there there is any dependency
 | 
        
           |  |  | 709 |      * preventing the field to be modified. It's used by all the public methods that perform any
 | 
        
           |  |  | 710 |      * DDL change on fields, throwing one ddl_dependency_exception if dependencies are found.
 | 
        
           |  |  | 711 |      *
 | 
        
           |  |  | 712 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 713 |      * @param xmldb_field $xmldb_field Index object (full specs are required).
 | 
        
           |  |  | 714 |      * @return void
 | 
        
           |  |  | 715 |      * @throws ddl_dependency_exception|ddl_field_missing_exception|ddl_table_missing_exception if dependency not met.
 | 
        
           |  |  | 716 |      */
 | 
        
           |  |  | 717 |     private function check_field_dependencies(xmldb_table $xmldb_table, xmldb_field $xmldb_field) {
 | 
        
           |  |  | 718 |   | 
        
           |  |  | 719 |         // Check the table exists
 | 
        
           |  |  | 720 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 721 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 722 |         }
 | 
        
           |  |  | 723 |   | 
        
           |  |  | 724 |         // Check the field exists
 | 
        
           |  |  | 725 |         if (!$this->field_exists($xmldb_table, $xmldb_field)) {
 | 
        
           |  |  | 726 |             throw new ddl_field_missing_exception($xmldb_field->getName(), $xmldb_table->getName());
 | 
        
           |  |  | 727 |         }
 | 
        
           |  |  | 728 |   | 
        
           |  |  | 729 |         // Check the field isn't in use by any index in the table
 | 
        
           |  |  | 730 |         if ($indexes = $this->mdb->get_indexes($xmldb_table->getName(), false)) {
 | 
        
           |  |  | 731 |             foreach ($indexes as $indexname => $index) {
 | 
        
           |  |  | 732 |                 $columns = $index['columns'];
 | 
        
           |  |  | 733 |                 if (in_array($xmldb_field->getName(), $columns)) {
 | 
        
           |  |  | 734 |                     throw new ddl_dependency_exception('column', $xmldb_table->getName() . '->' . $xmldb_field->getName(),
 | 
        
           |  |  | 735 |                                                        'index', $indexname . ' (' . implode(', ', $columns)  . ')');
 | 
        
           |  |  | 736 |                 }
 | 
        
           |  |  | 737 |             }
 | 
        
           |  |  | 738 |         }
 | 
        
           |  |  | 739 |     }
 | 
        
           |  |  | 740 |   | 
        
           |  |  | 741 |     /**
 | 
        
           |  |  | 742 |      * This function will create the key in the table passed as arguments
 | 
        
           |  |  | 743 |      *
 | 
        
           |  |  | 744 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 745 |      * @param xmldb_key $xmldb_key Index object (full specs are required).
 | 
        
           |  |  | 746 |      * @return void
 | 
        
           |  |  | 747 |      */
 | 
        
           |  |  | 748 |     public function add_key(xmldb_table $xmldb_table, xmldb_key $xmldb_key) {
 | 
        
           |  |  | 749 |   | 
        
           |  |  | 750 |         if ($xmldb_key->getType() == XMLDB_KEY_PRIMARY) { // Prevent PRIMARY to be added (only in create table, being serious  :-P)
 | 
        
           |  |  | 751 |             throw new ddl_exception('ddlunknownerror', null, 'Primary Keys can be added at table create time only');
 | 
        
           |  |  | 752 |         }
 | 
        
           |  |  | 753 |   | 
        
           |  |  | 754 |         if (!$sqlarr = $this->generator->getAddKeySQL($xmldb_table, $xmldb_key)) {
 | 
        
           |  |  | 755 |             return; //Empty array = nothing to do = no error
 | 
        
           |  |  | 756 |         }
 | 
        
           |  |  | 757 |   | 
        
           |  |  | 758 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 759 |     }
 | 
        
           |  |  | 760 |   | 
        
           |  |  | 761 |     /**
 | 
        
           |  |  | 762 |      * This function will drop the key in the table passed as arguments
 | 
        
           |  |  | 763 |      *
 | 
        
           |  |  | 764 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 765 |      * @param xmldb_key $xmldb_key Key object (full specs are required).
 | 
        
           |  |  | 766 |      * @return void
 | 
        
           |  |  | 767 |      */
 | 
        
           |  |  | 768 |     public function drop_key(xmldb_table $xmldb_table, xmldb_key $xmldb_key) {
 | 
        
           |  |  | 769 |         if ($xmldb_key->getType() == XMLDB_KEY_PRIMARY) { // Prevent PRIMARY to be dropped (only in drop table, being serious  :-P)
 | 
        
           |  |  | 770 |             throw new ddl_exception('ddlunknownerror', null, 'Primary Keys can be deleted at table drop time only');
 | 
        
           |  |  | 771 |         }
 | 
        
           |  |  | 772 |   | 
        
           |  |  | 773 |         if (!$sqlarr = $this->generator->getDropKeySQL($xmldb_table, $xmldb_key)) {
 | 
        
           |  |  | 774 |             return; //Empty array = nothing to do = no error
 | 
        
           |  |  | 775 |         }
 | 
        
           |  |  | 776 |   | 
        
           |  |  | 777 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 778 |     }
 | 
        
           |  |  | 779 |   | 
        
           |  |  | 780 |     /**
 | 
        
           |  |  | 781 |      * This function will rename the key in the table passed as arguments
 | 
        
           |  |  | 782 |      * Experimental. Shouldn't be used at all in normal installation/upgrade!
 | 
        
           |  |  | 783 |      *
 | 
        
           |  |  | 784 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 785 |      * @param xmldb_key $xmldb_key key object (full specs are required).
 | 
        
           |  |  | 786 |      * @param string $newname New name of the key.
 | 
        
           |  |  | 787 |      * @return void
 | 
        
           |  |  | 788 |      */
 | 
        
           |  |  | 789 |     public function rename_key(xmldb_table $xmldb_table, xmldb_key $xmldb_key, $newname) {
 | 
        
           |  |  | 790 |         debugging('rename_key() is one experimental feature. You must not use it in production!', DEBUG_DEVELOPER);
 | 
        
           |  |  | 791 |   | 
        
           |  |  | 792 |         // Check newname isn't empty
 | 
        
           |  |  | 793 |         if (!$newname) {
 | 
        
           |  |  | 794 |             throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 | 
        
           |  |  | 795 |         }
 | 
        
           |  |  | 796 |   | 
        
           |  |  | 797 |         if (!$sqlarr = $this->generator->getRenameKeySQL($xmldb_table, $xmldb_key, $newname)) {
 | 
        
           |  |  | 798 |             throw new ddl_exception('ddlunknownerror', null, 'Some DBs do not support key renaming (MySQL, PostgreSQL, MsSQL). Rename skipped');
 | 
        
           |  |  | 799 |         }
 | 
        
           |  |  | 800 |   | 
        
           |  |  | 801 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 802 |     }
 | 
        
           |  |  | 803 |   | 
        
           |  |  | 804 |     /**
 | 
        
           |  |  | 805 |      * This function will create the index in the table passed as arguments
 | 
        
           |  |  | 806 |      * Before creating the index, the function will check it doesn't exists
 | 
        
           |  |  | 807 |      *
 | 
        
           |  |  | 808 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 809 |      * @param xmldb_index $xmldb_intex Index object (full specs are required).
 | 
        
           |  |  | 810 |      * @return void
 | 
        
           |  |  | 811 |      */
 | 
        
           |  |  | 812 |     public function add_index($xmldb_table, $xmldb_intex) {
 | 
        
           |  |  | 813 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 814 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 815 |         }
 | 
        
           |  |  | 816 |   | 
        
           |  |  | 817 |         // Check index doesn't exist
 | 
        
           |  |  | 818 |         if ($this->index_exists($xmldb_table, $xmldb_intex)) {
 | 
        
           |  |  | 819 |             throw new ddl_exception('ddlunknownerror', null,
 | 
        
           |  |  | 820 |                       'Index ' . $xmldb_table->getName() . '->' . $xmldb_intex->getName() .
 | 
        
           |  |  | 821 |                       ' already exists. Create skipped');
 | 
        
           |  |  | 822 |         }
 | 
        
           |  |  | 823 |   | 
        
           |  |  | 824 |         if (!$sqlarr = $this->generator->getAddIndexSQL($xmldb_table, $xmldb_intex)) {
 | 
        
           |  |  | 825 |             throw new ddl_exception('ddlunknownerror', null, 'add_index sql not generated');
 | 
        
           |  |  | 826 |         }
 | 
        
           |  |  | 827 |   | 
        
           |  |  | 828 |         try {
 | 
        
           |  |  | 829 |             $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 830 |         } catch (ddl_change_structure_exception $e) {
 | 
        
           |  |  | 831 |             // There could be a problem with the index length related to the row format of the table.
 | 
        
           |  |  | 832 |             // If we are using utf8mb4 and the row format is 'compact' or 'redundant' then we need to change it over to
 | 
        
           |  |  | 833 |             // 'compressed' or 'dynamic'.
 | 
        
           |  |  | 834 |             if (method_exists($this->mdb, 'convert_table_row_format')) {
 | 
        
           |  |  | 835 |                 $this->mdb->convert_table_row_format($xmldb_table->getName());
 | 
        
           |  |  | 836 |                 $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 837 |             } else {
 | 
        
           |  |  | 838 |                 // It's some other problem that we are currently not handling.
 | 
        
           |  |  | 839 |                 throw $e;
 | 
        
           |  |  | 840 |             }
 | 
        
           |  |  | 841 |         }
 | 
        
           |  |  | 842 |     }
 | 
        
           |  |  | 843 |   | 
        
           |  |  | 844 |     /**
 | 
        
           |  |  | 845 |      * This function will drop the index in the table passed as arguments
 | 
        
           |  |  | 846 |      * Before dropping the index, the function will check it exists
 | 
        
           |  |  | 847 |      *
 | 
        
           |  |  | 848 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 849 |      * @param xmldb_index $xmldb_intex Index object (full specs are required).
 | 
        
           |  |  | 850 |      * @return void
 | 
        
           |  |  | 851 |      */
 | 
        
           |  |  | 852 |     public function drop_index($xmldb_table, $xmldb_intex) {
 | 
        
           |  |  | 853 |         if (!$this->table_exists($xmldb_table)) {
 | 
        
           |  |  | 854 |             throw new ddl_table_missing_exception($xmldb_table->getName());
 | 
        
           |  |  | 855 |         }
 | 
        
           |  |  | 856 |   | 
        
           |  |  | 857 |         // Check index exists
 | 
        
           |  |  | 858 |         if (!$this->index_exists($xmldb_table, $xmldb_intex)) {
 | 
        
           |  |  | 859 |             throw new ddl_exception('ddlunknownerror', null,
 | 
        
           |  |  | 860 |                       'Index ' . $xmldb_table->getName() . '->' . $xmldb_intex->getName() .
 | 
        
           |  |  | 861 |                       ' does not exist. Drop skipped');
 | 
        
           |  |  | 862 |         }
 | 
        
           |  |  | 863 |   | 
        
           |  |  | 864 |         if (!$sqlarr = $this->generator->getDropIndexSQL($xmldb_table, $xmldb_intex)) {
 | 
        
           |  |  | 865 |             throw new ddl_exception('ddlunknownerror', null, 'drop_index sql not generated');
 | 
        
           |  |  | 866 |         }
 | 
        
           |  |  | 867 |   | 
        
           |  |  | 868 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 869 |     }
 | 
        
           |  |  | 870 |   | 
        
           |  |  | 871 |     /**
 | 
        
           |  |  | 872 |      * This function will rename the index in the table passed as arguments
 | 
        
           |  |  | 873 |      * Before renaming the index, the function will check it exists
 | 
        
           |  |  | 874 |      * Experimental. Shouldn't be used at all!
 | 
        
           |  |  | 875 |      *
 | 
        
           |  |  | 876 |      * @param xmldb_table $xmldb_table Table object (just the name is mandatory).
 | 
        
           |  |  | 877 |      * @param xmldb_index $xmldb_intex Index object (full specs are required).
 | 
        
           |  |  | 878 |      * @param string $newname New name of the index.
 | 
        
           |  |  | 879 |      * @return void
 | 
        
           |  |  | 880 |      */
 | 
        
           |  |  | 881 |     public function rename_index($xmldb_table, $xmldb_intex, $newname) {
 | 
        
           |  |  | 882 |         debugging('rename_index() is one experimental feature. You must not use it in production!', DEBUG_DEVELOPER);
 | 
        
           |  |  | 883 |   | 
        
           |  |  | 884 |         // Check newname isn't empty
 | 
        
           |  |  | 885 |         if (!$newname) {
 | 
        
           |  |  | 886 |             throw new ddl_exception('ddlunknownerror', null, 'newname can not be empty');
 | 
        
           |  |  | 887 |         }
 | 
        
           |  |  | 888 |   | 
        
           |  |  | 889 |         // Check index exists
 | 
        
           |  |  | 890 |         if (!$this->index_exists($xmldb_table, $xmldb_intex)) {
 | 
        
           |  |  | 891 |             throw new ddl_exception('ddlunknownerror', null,
 | 
        
           |  |  | 892 |                       'Index ' . $xmldb_table->getName() . '->' . $xmldb_intex->getName() .
 | 
        
           |  |  | 893 |                       ' does not exist. Rename skipped');
 | 
        
           |  |  | 894 |         }
 | 
        
           |  |  | 895 |   | 
        
           |  |  | 896 |         if (!$sqlarr = $this->generator->getRenameIndexSQL($xmldb_table, $xmldb_intex, $newname)) {
 | 
        
           |  |  | 897 |             throw new ddl_exception('ddlunknownerror', null, 'Some DBs do not support index renaming (MySQL). Rename skipped');
 | 
        
           |  |  | 898 |         }
 | 
        
           |  |  | 899 |   | 
        
           |  |  | 900 |         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
 | 
        
           |  |  | 901 |     }
 | 
        
           |  |  | 902 |   | 
        
           |  |  | 903 |     /**
 | 
        
           |  |  | 904 |      * Get the list of install.xml files.
 | 
        
           |  |  | 905 |      *
 | 
        
           |  |  | 906 |      * @return array
 | 
        
           |  |  | 907 |      */
 | 
        
           |  |  | 908 |     public function get_install_xml_files(): array {
 | 
        
           |  |  | 909 |         global $CFG;
 | 
        
           |  |  | 910 |         require_once($CFG->libdir.'/adminlib.php');
 | 
        
           |  |  | 911 |   | 
        
           |  |  | 912 |         $files = [];
 | 
        
           |  |  | 913 |         $dbdirs = get_db_directories();
 | 
        
           |  |  | 914 |         foreach ($dbdirs as $dbdir) {
 | 
        
           |  |  | 915 |             $filename = "{$dbdir}/install.xml";
 | 
        
           |  |  | 916 |             if (file_exists($filename)) {
 | 
        
           |  |  | 917 |                 $files[] = $filename;
 | 
        
           |  |  | 918 |             }
 | 
        
           |  |  | 919 |         }
 | 
        
           |  |  | 920 |   | 
        
           |  |  | 921 |         return $files;
 | 
        
           |  |  | 922 |     }
 | 
        
           |  |  | 923 |   | 
        
           |  |  | 924 |     /**
 | 
        
           |  |  | 925 |      * Reads the install.xml files for Moodle core and modules and returns an array of
 | 
        
           |  |  | 926 |      * xmldb_structure object with xmldb_table from these files.
 | 
        
           |  |  | 927 |      * @return xmldb_structure schema from install.xml files
 | 
        
           |  |  | 928 |      */
 | 
        
           |  |  | 929 |     public function get_install_xml_schema() {
 | 
        
           |  |  | 930 |         global $CFG;
 | 
        
           |  |  | 931 |         require_once($CFG->libdir.'/adminlib.php');
 | 
        
           |  |  | 932 |   | 
        
           |  |  | 933 |         $schema = new xmldb_structure('export');
 | 
        
           |  |  | 934 |         $schema->setVersion($CFG->version);
 | 
        
           |  |  | 935 |   | 
        
           |  |  | 936 |         foreach ($this->get_install_xml_files() as $filename) {
 | 
        
           |  |  | 937 |             $xmldb_file = new xmldb_file($filename);
 | 
        
           |  |  | 938 |             if (!$xmldb_file->loadXMLStructure()) {
 | 
        
           |  |  | 939 |                 continue;
 | 
        
           |  |  | 940 |             }
 | 
        
           |  |  | 941 |             $structure = $xmldb_file->getStructure();
 | 
        
           |  |  | 942 |             $tables = $structure->getTables();
 | 
        
           |  |  | 943 |             foreach ($tables as $table) {
 | 
        
           |  |  | 944 |                 $table->setPrevious(null);
 | 
        
           |  |  | 945 |                 $table->setNext(null);
 | 
        
           |  |  | 946 |                 $schema->addTable($table);
 | 
        
           |  |  | 947 |             }
 | 
        
           |  |  | 948 |         }
 | 
        
           |  |  | 949 |         return $schema;
 | 
        
           |  |  | 950 |     }
 | 
        
           |  |  | 951 |   | 
        
           |  |  | 952 |     /**
 | 
        
           |  |  | 953 |      * Checks the database schema against a schema specified by an xmldb_structure object
 | 
        
           |  |  | 954 |      * @param xmldb_structure $schema export schema describing all known tables
 | 
        
           |  |  | 955 |      * @param array $options
 | 
        
           |  |  | 956 |      * @return array keyed by table name with array of difference messages as values
 | 
        
           |  |  | 957 |      */
 | 
        
           | 1441 | ariadna | 958 |     public function check_database_schema(xmldb_structure $schema, ?array $options = null) {
 | 
        
           | 1 | efrain | 959 |         $alloptions = array(
 | 
        
           |  |  | 960 |             'extratables' => true,
 | 
        
           |  |  | 961 |             'missingtables' => true,
 | 
        
           |  |  | 962 |             'extracolumns' => true,
 | 
        
           |  |  | 963 |             'missingcolumns' => true,
 | 
        
           |  |  | 964 |             'changedcolumns' => true,
 | 
        
           |  |  | 965 |             'missingindexes' => true,
 | 
        
           |  |  | 966 |             'extraindexes' => true
 | 
        
           |  |  | 967 |         );
 | 
        
           |  |  | 968 |   | 
        
           |  |  | 969 |         $typesmap = array(
 | 
        
           |  |  | 970 |             'I' => XMLDB_TYPE_INTEGER,
 | 
        
           |  |  | 971 |             'R' => XMLDB_TYPE_INTEGER,
 | 
        
           |  |  | 972 |             'N' => XMLDB_TYPE_NUMBER,
 | 
        
           |  |  | 973 |             'F' => XMLDB_TYPE_NUMBER, // Nobody should be using floats!
 | 
        
           |  |  | 974 |             'C' => XMLDB_TYPE_CHAR,
 | 
        
           |  |  | 975 |             'X' => XMLDB_TYPE_TEXT,
 | 
        
           |  |  | 976 |             'B' => XMLDB_TYPE_BINARY,
 | 
        
           |  |  | 977 |             'T' => XMLDB_TYPE_TIMESTAMP,
 | 
        
           |  |  | 978 |             'D' => XMLDB_TYPE_DATETIME,
 | 
        
           |  |  | 979 |         );
 | 
        
           |  |  | 980 |   | 
        
           |  |  | 981 |         $options = (array)$options;
 | 
        
           |  |  | 982 |         $options = array_merge($alloptions, $options);
 | 
        
           |  |  | 983 |   | 
        
           |  |  | 984 |         // Note: the error descriptions are not supposed to be localised,
 | 
        
           |  |  | 985 |         //       it is intended for developers and skilled admins only.
 | 
        
           |  |  | 986 |         $errors = array();
 | 
        
           |  |  | 987 |   | 
        
           |  |  | 988 |         /** @var string[] $dbtables */
 | 
        
           |  |  | 989 |         $dbtables = $this->mdb->get_tables(false);
 | 
        
           |  |  | 990 |         /** @var xmldb_table[] $tables */
 | 
        
           |  |  | 991 |         $tables = $schema->getTables();
 | 
        
           |  |  | 992 |   | 
        
           |  |  | 993 |         foreach ($tables as $table) {
 | 
        
           |  |  | 994 |             $tablename = $table->getName();
 | 
        
           |  |  | 995 |   | 
        
           |  |  | 996 |             if ($options['missingtables']) {
 | 
        
           |  |  | 997 |                 // Missing tables are a fatal problem.
 | 
        
           |  |  | 998 |                 if (empty($dbtables[$tablename])) {
 | 
        
           |  |  | 999 |                     $errors[$tablename][] = "table is missing";
 | 
        
           |  |  | 1000 |                     continue;
 | 
        
           |  |  | 1001 |                 }
 | 
        
           |  |  | 1002 |             }
 | 
        
           |  |  | 1003 |   | 
        
           |  |  | 1004 |             /** @var database_column_info[] $dbfields */
 | 
        
           |  |  | 1005 |             $dbfields = $this->mdb->get_columns($tablename, false);
 | 
        
           |  |  | 1006 |             $dbindexes = $this->mdb->get_indexes($tablename);
 | 
        
           |  |  | 1007 |             /** @var xmldb_field[] $fields */
 | 
        
           |  |  | 1008 |             $fields = $table->getFields();
 | 
        
           |  |  | 1009 |   | 
        
           |  |  | 1010 |             foreach ($fields as $field) {
 | 
        
           |  |  | 1011 |                 $fieldname = $field->getName();
 | 
        
           |  |  | 1012 |                 if (empty($dbfields[$fieldname])) {
 | 
        
           |  |  | 1013 |                     if ($options['missingcolumns']) {
 | 
        
           |  |  | 1014 |                         // Missing columns are a fatal problem.
 | 
        
           |  |  | 1015 |                         $errors[$tablename][] = "column '$fieldname' is missing";
 | 
        
           |  |  | 1016 |                     }
 | 
        
           |  |  | 1017 |                 } else if ($options['changedcolumns']) {
 | 
        
           |  |  | 1018 |                     $dbfield = $dbfields[$fieldname];
 | 
        
           |  |  | 1019 |   | 
        
           |  |  | 1020 |                     if (!isset($typesmap[$dbfield->meta_type])) {
 | 
        
           |  |  | 1021 |                         $errors[$tablename][] = "column '$fieldname' has unsupported type '$dbfield->meta_type'";
 | 
        
           |  |  | 1022 |                     } else {
 | 
        
           |  |  | 1023 |                         $dbtype = $typesmap[$dbfield->meta_type];
 | 
        
           |  |  | 1024 |                         $type = $field->getType();
 | 
        
           |  |  | 1025 |                         if ($type == XMLDB_TYPE_FLOAT) {
 | 
        
           |  |  | 1026 |                             $type = XMLDB_TYPE_NUMBER;
 | 
        
           |  |  | 1027 |                         }
 | 
        
           |  |  | 1028 |                         if ($type != $dbtype) {
 | 
        
           |  |  | 1029 |                             if ($expected = array_search($type, $typesmap)) {
 | 
        
           |  |  | 1030 |                                 $errors[$tablename][] = "column '$fieldname' has incorrect type '$dbfield->meta_type', expected '$expected'";
 | 
        
           |  |  | 1031 |                             } else {
 | 
        
           |  |  | 1032 |                                 $errors[$tablename][] = "column '$fieldname' has incorrect type '$dbfield->meta_type'";
 | 
        
           |  |  | 1033 |                             }
 | 
        
           |  |  | 1034 |                         } else {
 | 
        
           |  |  | 1035 |                             if ($field->getNotNull() != $dbfield->not_null) {
 | 
        
           |  |  | 1036 |                                 if ($field->getNotNull()) {
 | 
        
           |  |  | 1037 |                                     $errors[$tablename][] = "column '$fieldname' should be NOT NULL ($dbfield->meta_type)";
 | 
        
           |  |  | 1038 |                                 } else {
 | 
        
           |  |  | 1039 |                                     $errors[$tablename][] = "column '$fieldname' should allow NULL ($dbfield->meta_type)";
 | 
        
           |  |  | 1040 |                                 }
 | 
        
           |  |  | 1041 |                             }
 | 
        
           |  |  | 1042 |                             switch ($dbtype) {
 | 
        
           |  |  | 1043 |                                 case XMLDB_TYPE_TEXT:
 | 
        
           |  |  | 1044 |                                 case XMLDB_TYPE_BINARY:
 | 
        
           |  |  | 1045 |                                     // No length check necessary - there is one size only now.
 | 
        
           |  |  | 1046 |                                     break;
 | 
        
           |  |  | 1047 |   | 
        
           |  |  | 1048 |                                 case XMLDB_TYPE_NUMBER:
 | 
        
           |  |  | 1049 |                                     $lengthmismatch = $field->getLength() != $dbfield->max_length;
 | 
        
           |  |  | 1050 |                                     $decimalmismatch = $field->getDecimals() != $dbfield->scale;
 | 
        
           |  |  | 1051 |                                     // Do not use floats in any new code, they are deprecated in XMLDB editor!
 | 
        
           |  |  | 1052 |                                     if ($field->getType() != XMLDB_TYPE_FLOAT && ($lengthmismatch || $decimalmismatch)) {
 | 
        
           |  |  | 1053 |                                         $size = "({$field->getLength()},{$field->getDecimals()})";
 | 
        
           |  |  | 1054 |                                         $dbsize = "($dbfield->max_length,$dbfield->scale)";
 | 
        
           |  |  | 1055 |                                         $errors[$tablename][] = "column '$fieldname' size is $dbsize,".
 | 
        
           |  |  | 1056 |                                             " expected $size ($dbfield->meta_type)";
 | 
        
           |  |  | 1057 |                                     }
 | 
        
           |  |  | 1058 |                                     break;
 | 
        
           |  |  | 1059 |   | 
        
           |  |  | 1060 |                                 case XMLDB_TYPE_CHAR:
 | 
        
           |  |  | 1061 |                                     // This is not critical, but they should ideally match.
 | 
        
           |  |  | 1062 |                                     if ($field->getLength() != $dbfield->max_length) {
 | 
        
           |  |  | 1063 |                                         $errors[$tablename][] = "column '$fieldname' length is $dbfield->max_length,".
 | 
        
           |  |  | 1064 |                                             " expected {$field->getLength()} ($dbfield->meta_type)";
 | 
        
           |  |  | 1065 |                                     }
 | 
        
           |  |  | 1066 |                                     break;
 | 
        
           |  |  | 1067 |   | 
        
           |  |  | 1068 |                                 case XMLDB_TYPE_INTEGER:
 | 
        
           |  |  | 1069 |                                     // Integers may be bigger in some DBs.
 | 
        
           |  |  | 1070 |                                     $length = $field->getLength();
 | 
        
           |  |  | 1071 |                                     if ($length > 18) {
 | 
        
           |  |  | 1072 |                                         // Integers are not supposed to be bigger than 18.
 | 
        
           |  |  | 1073 |                                         $length = 18;
 | 
        
           |  |  | 1074 |                                     }
 | 
        
           |  |  | 1075 |                                     if ($length > $dbfield->max_length) {
 | 
        
           |  |  | 1076 |                                         $errors[$tablename][] = "column '$fieldname' length is $dbfield->max_length,".
 | 
        
           |  |  | 1077 |                                             " expected at least {$field->getLength()} ($dbfield->meta_type)";
 | 
        
           |  |  | 1078 |                                     }
 | 
        
           |  |  | 1079 |                                     break;
 | 
        
           |  |  | 1080 |   | 
        
           |  |  | 1081 |                                 case XMLDB_TYPE_TIMESTAMP:
 | 
        
           |  |  | 1082 |                                     $errors[$tablename][] = "column '$fieldname' is a timestamp,".
 | 
        
           |  |  | 1083 |                                         " this type is not supported ($dbfield->meta_type)";
 | 
        
           |  |  | 1084 |                                     continue 2;
 | 
        
           |  |  | 1085 |   | 
        
           |  |  | 1086 |                                 case XMLDB_TYPE_DATETIME:
 | 
        
           |  |  | 1087 |                                     $errors[$tablename][] = "column '$fieldname' is a datetime,".
 | 
        
           |  |  | 1088 |                                         "this type is not supported ($dbfield->meta_type)";
 | 
        
           |  |  | 1089 |                                     continue 2;
 | 
        
           |  |  | 1090 |   | 
        
           |  |  | 1091 |                                 default:
 | 
        
           |  |  | 1092 |                                     // Report all other unsupported types as problems.
 | 
        
           |  |  | 1093 |                                     $errors[$tablename][] = "column '$fieldname' has unknown type ($dbfield->meta_type)";
 | 
        
           |  |  | 1094 |                                     continue 2;
 | 
        
           |  |  | 1095 |   | 
        
           |  |  | 1096 |                             }
 | 
        
           |  |  | 1097 |   | 
        
           |  |  | 1098 |                             // Note: The empty string defaults are a bit messy...
 | 
        
           |  |  | 1099 |                             if ($field->getDefault() != $dbfield->default_value) {
 | 
        
           |  |  | 1100 |                                 $default = is_null($field->getDefault()) ? 'NULL' : $field->getDefault();
 | 
        
           |  |  | 1101 |                                 $dbdefault = is_null($dbfield->default_value) ? 'NULL' : $dbfield->default_value;
 | 
        
           |  |  | 1102 |                                 $errors[$tablename][] = "column '$fieldname' has default '$dbdefault', expected '$default' ($dbfield->meta_type)";
 | 
        
           |  |  | 1103 |                             }
 | 
        
           |  |  | 1104 |                         }
 | 
        
           |  |  | 1105 |                     }
 | 
        
           |  |  | 1106 |                 }
 | 
        
           |  |  | 1107 |                 unset($dbfields[$fieldname]);
 | 
        
           |  |  | 1108 |             }
 | 
        
           |  |  | 1109 |   | 
        
           |  |  | 1110 |             // Check for missing indexes/keys.
 | 
        
           |  |  | 1111 |             if ($options['missingindexes']) {
 | 
        
           |  |  | 1112 |                 // Check the foreign keys.
 | 
        
           |  |  | 1113 |                 if ($keys = $table->getKeys()) {
 | 
        
           |  |  | 1114 |                     foreach ($keys as $key) {
 | 
        
           |  |  | 1115 |                         // Primary keys are skipped.
 | 
        
           |  |  | 1116 |                         if ($key->getType() == XMLDB_KEY_PRIMARY) {
 | 
        
           |  |  | 1117 |                             continue;
 | 
        
           |  |  | 1118 |                         }
 | 
        
           |  |  | 1119 |   | 
        
           |  |  | 1120 |                         $keyname = $key->getName();
 | 
        
           |  |  | 1121 |   | 
        
           |  |  | 1122 |                         // Create the interim index.
 | 
        
           |  |  | 1123 |                         $index = new xmldb_index('anyname');
 | 
        
           |  |  | 1124 |                         $index->setFields($key->getFields());
 | 
        
           |  |  | 1125 |                         switch ($key->getType()) {
 | 
        
           |  |  | 1126 |                             case XMLDB_KEY_UNIQUE:
 | 
        
           |  |  | 1127 |                             case XMLDB_KEY_FOREIGN_UNIQUE:
 | 
        
           |  |  | 1128 |                                 $index->setUnique(true);
 | 
        
           |  |  | 1129 |                                 break;
 | 
        
           |  |  | 1130 |                             case XMLDB_KEY_FOREIGN:
 | 
        
           |  |  | 1131 |                                 $index->setUnique(false);
 | 
        
           |  |  | 1132 |                                 break;
 | 
        
           |  |  | 1133 |                         }
 | 
        
           |  |  | 1134 |                         if (!$this->index_exists($table, $index)) {
 | 
        
           |  |  | 1135 |                             $errors[$tablename][] = $this->get_missing_index_error($table, $index, $keyname);
 | 
        
           |  |  | 1136 |                         } else {
 | 
        
           |  |  | 1137 |                             $this->remove_index_from_dbindex($dbindexes, $index);
 | 
        
           |  |  | 1138 |                         }
 | 
        
           |  |  | 1139 |                     }
 | 
        
           |  |  | 1140 |                 }
 | 
        
           |  |  | 1141 |   | 
        
           |  |  | 1142 |                 // Check the indexes.
 | 
        
           |  |  | 1143 |                 if ($indexes = $table->getIndexes()) {
 | 
        
           |  |  | 1144 |                     foreach ($indexes as $index) {
 | 
        
           |  |  | 1145 |                         if (!$this->index_exists($table, $index)) {
 | 
        
           |  |  | 1146 |                             $errors[$tablename][] = $this->get_missing_index_error($table, $index, $index->getName());
 | 
        
           |  |  | 1147 |                         } else {
 | 
        
           |  |  | 1148 |                             $this->remove_index_from_dbindex($dbindexes, $index);
 | 
        
           |  |  | 1149 |                         }
 | 
        
           |  |  | 1150 |                     }
 | 
        
           |  |  | 1151 |                 }
 | 
        
           |  |  | 1152 |             }
 | 
        
           |  |  | 1153 |   | 
        
           |  |  | 1154 |             // Check if we should show the extra indexes.
 | 
        
           |  |  | 1155 |             if ($options['extraindexes']) {
 | 
        
           |  |  | 1156 |                 // Hack - skip for table 'search_simpledb_index' as this plugin adds indexes dynamically on install
 | 
        
           |  |  | 1157 |                 // which are not included in install.xml. See search/engine/simpledb/db/install.php.
 | 
        
           |  |  | 1158 |                 if ($tablename != 'search_simpledb_index') {
 | 
        
           |  |  | 1159 |                     foreach ($dbindexes as $indexname => $index) {
 | 
        
           |  |  | 1160 |                         $errors[$tablename][] = "Unexpected index '$indexname'.";
 | 
        
           |  |  | 1161 |                     }
 | 
        
           |  |  | 1162 |                 }
 | 
        
           |  |  | 1163 |             }
 | 
        
           |  |  | 1164 |   | 
        
           |  |  | 1165 |             // Check for extra columns (indicates unsupported hacks) - modify install.xml if you want to pass validation.
 | 
        
           |  |  | 1166 |             foreach ($dbfields as $fieldname => $dbfield) {
 | 
        
           |  |  | 1167 |                 if ($options['extracolumns']) {
 | 
        
           |  |  | 1168 |                     $errors[$tablename][] = "column '$fieldname' is not expected ($dbfield->meta_type)";
 | 
        
           |  |  | 1169 |                 }
 | 
        
           |  |  | 1170 |             }
 | 
        
           |  |  | 1171 |             unset($dbtables[$tablename]);
 | 
        
           |  |  | 1172 |         }
 | 
        
           |  |  | 1173 |   | 
        
           |  |  | 1174 |         if ($options['extratables']) {
 | 
        
           |  |  | 1175 |             // Look for unsupported tables - local custom tables should be in /local/xxxx/db/install.xml file.
 | 
        
           |  |  | 1176 |             // If there is no prefix, we can not say if table is ours, sorry.
 | 
        
           |  |  | 1177 |             if ($this->generator->prefix !== '') {
 | 
        
           |  |  | 1178 |                 foreach ($dbtables as $tablename => $unused) {
 | 
        
           |  |  | 1179 |                     if (strpos($tablename, 'pma_') === 0) {
 | 
        
           |  |  | 1180 |                         // Ignore phpmyadmin tables.
 | 
        
           |  |  | 1181 |                         continue;
 | 
        
           |  |  | 1182 |                     }
 | 
        
           |  |  | 1183 |                     if (strpos($tablename, 'test') === 0) {
 | 
        
           |  |  | 1184 |                         // Legacy simple test db tables need to be eventually removed,
 | 
        
           |  |  | 1185 |                         // report them as problems!
 | 
        
           |  |  | 1186 |                         $errors[$tablename][] = "table is not expected (it may be a leftover after Simpletest unit tests)";
 | 
        
           |  |  | 1187 |                     } else {
 | 
        
           |  |  | 1188 |                         $errors[$tablename][] = "table is not expected";
 | 
        
           |  |  | 1189 |                     }
 | 
        
           |  |  | 1190 |                 }
 | 
        
           |  |  | 1191 |             }
 | 
        
           |  |  | 1192 |         }
 | 
        
           |  |  | 1193 |   | 
        
           |  |  | 1194 |         return $errors;
 | 
        
           |  |  | 1195 |     }
 | 
        
           |  |  | 1196 |   | 
        
           |  |  | 1197 |     /**
 | 
        
           |  |  | 1198 |      * Returns a string describing the missing index error.
 | 
        
           |  |  | 1199 |      *
 | 
        
           |  |  | 1200 |      * @param xmldb_table $table
 | 
        
           |  |  | 1201 |      * @param xmldb_index $index
 | 
        
           |  |  | 1202 |      * @param string $indexname
 | 
        
           |  |  | 1203 |      * @return string
 | 
        
           |  |  | 1204 |      */
 | 
        
           |  |  | 1205 |     private function get_missing_index_error(xmldb_table $table, xmldb_index $index, string $indexname): string {
 | 
        
           |  |  | 1206 |         $sqlarr = $this->generator->getAddIndexSQL($table, $index);
 | 
        
           |  |  | 1207 |         $sqlarr = $this->generator->getEndedStatements($sqlarr);
 | 
        
           |  |  | 1208 |         $sqltoadd = reset($sqlarr);
 | 
        
           |  |  | 1209 |   | 
        
           |  |  | 1210 |         return "Missing index '" . $indexname . "' " . "(" . $index->readableInfo() . "). \n" . $sqltoadd;
 | 
        
           |  |  | 1211 |     }
 | 
        
           |  |  | 1212 |   | 
        
           |  |  | 1213 |     /**
 | 
        
           |  |  | 1214 |      * Removes an index from the array $dbindexes if it is found.
 | 
        
           |  |  | 1215 |      *
 | 
        
           |  |  | 1216 |      * @param array $dbindexes
 | 
        
           |  |  | 1217 |      * @param xmldb_index $index
 | 
        
           |  |  | 1218 |      */
 | 
        
           |  |  | 1219 |     private function remove_index_from_dbindex(array &$dbindexes, xmldb_index $index) {
 | 
        
           |  |  | 1220 |         foreach ($dbindexes as $key => $dbindex) {
 | 
        
           |  |  | 1221 |             if ($dbindex['columns'] == $index->getFields()) {
 | 
        
           |  |  | 1222 |                 unset($dbindexes[$key]);
 | 
        
           |  |  | 1223 |             }
 | 
        
           |  |  | 1224 |         }
 | 
        
           |  |  | 1225 |     }
 | 
        
           |  |  | 1226 | }
 |