Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   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   * General database importer class
  19   *
  20   * @package    core_dtl
  21   * @copyright  2008 Andrei Bautu
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  /**
  28   * Base class for database import operations. This class implements
  29   * basic callbacks for import operations and defines the @see import_database
  30   * method as a common method for all importers. In general, subclasses will
  31   * override import_database and call other methods in appropriate moments.
  32   * Between a single pair of calls to @see begin_database_import and
  33   * @see finish_database_import, multiple non-overlapping pairs of calls may
  34   * be made to @see begin_table_import and @see finish_database_import for
  35   * different tables.
  36   * Between one pair of calls to @see begin_table_import and
  37   * @see finish_database_import multiple calls may be made to
  38   * @see import_table_data for the same table.
  39   * This class can be used directly, if the standard control flow (defined above)
  40   * is respected.
  41   */
  42  class database_importer {
  43      /** @var moodle_database Connection to the target database (a @see moodle_database object). */
  44      protected $mdb;
  45      /** @var database_manager Database manager of the target database (a @see database_manager object). */
  46      protected $manager;
  47      /** @var xmldb_structure Target database schema in XMLDB format (a @see xmldb_structure object). */
  48      protected $schema;
  49      /**
  50       * Boolean flag - whether or not to check that XML database schema matches
  51       * the RDBMS database schema before importing (used by
  52       * @see begin_database_import).
  53       * @var bool
  54       */
  55      protected $check_schema;
  56      /** @var string How to use transactions. */
  57      protected $transactionmode = 'allinone';
  58      /** @var moodle_transaction Transaction object */
  59      protected $transaction;
  60  
  61      /**
  62       * Object constructor.
  63       *
  64       * @param moodle_database $mdb Connection to the target database (a
  65       * @see moodle_database object). Use null to use the current $DB connection.
  66       * @param boolean $check_schema - whether or not to check that XML database
  67       * schema matches the RDBMS database schema before importing (inside
  68       * @see begin_database_import).
  69       */
  70      public function __construct(moodle_database $mdb, $check_schema=true) {
  71          $this->mdb          = $mdb;
  72          $this->manager      = $mdb->get_manager();
  73          $this->schema       = $this->manager->get_install_xml_schema();
  74          $this->check_schema = $check_schema;
  75      }
  76  
  77      /**
  78       * How to use transactions during the import.
  79       * @param string $mode 'pertable', 'allinone' or 'none'.
  80       */
  81      public function set_transaction_mode($mode) {
  82          if (!in_array($mode, array('pertable', 'allinone', 'none'))) {
  83              throw new coding_exception('Unknown transaction mode', $mode);
  84          }
  85          $this->transactionmode = $mode;
  86      }
  87  
  88      /**
  89       * Callback function. Should be called only once database per import
  90       * operation, before any database changes are made. It will check the database
  91       * schema if @see check_schema is true
  92       *
  93       * @throws dbtransfer_exception if any checking (e.g. database schema, Moodle
  94       * version) fails
  95       *
  96       * @param float $version the version of the system which generated the data
  97       * @param string $timestamp the timestamp of the data (in ISO 8601) format.
  98       * @return void
  99       */
 100      public function begin_database_import($version, $timestamp) {
 101          global $CFG;
 102  
 103          if (!$this->mdb->get_tables()) {
 104              // No tables present yet, time to create all tables.
 105              $this->manager->install_from_xmldb_structure($this->schema);
 106          }
 107  
 108          if (round($version, 2) !== round($CFG->version, 2)) { // version might be in decimal format too
 109              $a = (object)array('schemaver'=>$version, 'currentver'=>$CFG->version);
 110              throw new dbtransfer_exception('importversionmismatchexception', $a);
 111          }
 112  
 113          $options = [
 114              'changedcolumns' => false, // Column types may be fixed by transfer.
 115              'missingindexes' => false, // No need to worry about indexes for transfering data.
 116              'extraindexes' => false
 117          ];
 118          if ($this->check_schema and $errors = $this->manager->check_database_schema($this->schema, $options)) {
 119              $details = '';
 120              foreach ($errors as $table=>$items) {
 121                  $details .= '<div>'.get_string('table').' '.$table.':';
 122                  $details .= '<ul>';
 123                  foreach ($items as $item) {
 124                      $details .= '<li>'.$item.'</li>';
 125                  }
 126                  $details .= '</ul></div>';
 127              }
 128              throw new dbtransfer_exception('importschemaexception', $details);
 129          }
 130          if ($this->transactionmode == 'allinone') {
 131              $this->transaction = $this->mdb->start_delegated_transaction();
 132          }
 133      }
 134  
 135      /**
 136       * Callback function. Should be called only once per table import operation,
 137       * before any table changes are made. It will delete all table data.
 138       *
 139       * @throws dbtransfer_exception an unknown table import is attempted
 140       * @throws ddl_table_missing_exception if the table is missing
 141       *
 142       * @param string $tablename - the name of the table that will be imported
 143       * @param string $schemaHash - the hash of the xmldb_table schema of the table
 144       * @return void
 145       */
 146      public function begin_table_import($tablename, $schemaHash) {
 147          if ($this->transactionmode == 'pertable') {
 148              $this->transaction = $this->mdb->start_delegated_transaction();
 149          }
 150          if (!$table = $this->schema->getTable($tablename)) {
 151              throw new dbtransfer_exception('unknowntableexception', $tablename);
 152          }
 153          if ($schemaHash != $table->getHash()) {
 154              throw new dbtransfer_exception('differenttableexception', $tablename);
 155          }
 156          // this should not happen, unless someone drops tables after import started
 157          if (!$this->manager->table_exists($table)) {
 158              throw new ddl_table_missing_exception($tablename);
 159          }
 160          $this->mdb->delete_records($tablename);
 161      }
 162  
 163      /**
 164       * Callback function. Should be called only once per table import operation,
 165       * after all table changes are made. It will reset table sequences if any.
 166       * @param string $tablename
 167       * @return void
 168       */
 169      public function finish_table_import($tablename) {
 170          $table  = $this->schema->getTable($tablename);
 171          $fields = $table->getFields();
 172          foreach ($fields as $field) {
 173              if ($field->getSequence()) {
 174                  $this->manager->reset_sequence($tablename);
 175                  return;
 176              }
 177          }
 178          if ($this->transactionmode == 'pertable') {
 179              $this->transaction->allow_commit();
 180          }
 181      }
 182  
 183      /**
 184       * Callback function. Should be called only once database per import
 185       * operation, after all database changes are made. It will commit changes.
 186       * @return void
 187       */
 188      public function finish_database_import() {
 189          if ($this->transactionmode == 'allinone') {
 190              $this->transaction->allow_commit();
 191          }
 192      }
 193  
 194      /**
 195       * Callback function. Should be called only once per record import operation, only
 196       * between @see begin_table_import and @see finish_table_import calls.
 197       * It will insert table data.
 198       *
 199       * @throws dml_exception if data insert operation failed
 200       *
 201       * @param string $tablename - the name of the table in which data will be
 202       * imported
 203       * @param object $data - data object (fields and values will be inserted
 204       * into table)
 205       * @return void
 206       */
 207      public function import_table_data($tablename, $data) {
 208          $this->mdb->import_record($tablename, $data);
 209      }
 210  
 211      /**
 212       * Common import method
 213       * @return void
 214       */
 215      public function import_database() {
 216          // implement in subclasses
 217      }
 218  }