Merge branch '44315-27' of git://github.com/samhemelryk/moodle
[moodle.git] / lib / dtl / database_importer.php
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/>.
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  */
25 defined('MOODLE_INTERNAL') || die();
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;
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     }
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     }
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;
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         }
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         }
113         if ($this->check_schema and $errors = $this->manager->check_database_schema($this->schema)) {
114             $details = '';
115             foreach ($errors as $table=>$items) {
116                 $details .= '<div>'.get_string('table').' '.$table.':';
117                 $details .= '<ul>';
118                 foreach ($items as $item) {
119                     $details .= '<li>'.$item.'</li>';
120                 }
121                 $details .= '</ul></div>';
122             }
123             throw new dbtransfer_exception('importschemaexception', $details);
124         }
125         if ($this->transactionmode == 'allinone') {
126             $this->transaction = $this->mdb->start_delegated_transaction();
127         }
128     }
130     /**
131      * Callback function. Should be called only once per table import operation,
132      * before any table changes are made. It will delete all table data.
133      *
134      * @throws dbtransfer_exception an unknown table import is attempted
135      * @throws ddl_table_missing_exception if the table is missing
136      *
137      * @param string $tablename - the name of the table that will be imported
138      * @param string $schemaHash - the hash of the xmldb_table schema of the table
139      * @return void
140      */
141     public function begin_table_import($tablename, $schemaHash) {
142         if ($this->transactionmode == 'pertable') {
143             $this->transaction = $this->mdb->start_delegated_transaction();
144         }
145         if (!$table = $this->schema->getTable($tablename)) {
146             throw new dbtransfer_exception('unknowntableexception', $tablename);
147         }
148         if ($schemaHash != $table->getHash()) {
149             throw new dbtransfer_exception('differenttableexception', $tablename);
150         }
151         // this should not happen, unless someone drops tables after import started
152         if (!$this->manager->table_exists($table)) {
153             throw new ddl_table_missing_exception($tablename);
154         }
155         $this->mdb->delete_records($tablename);
156     }
158     /**
159      * Callback function. Should be called only once per table import operation,
160      * after all table changes are made. It will reset table sequences if any.
161      * @param string $tablename
162      * @return void
163      */
164     public function finish_table_import($tablename) {
165         $table  = $this->schema->getTable($tablename);
166         $fields = $table->getFields();
167         foreach ($fields as $field) {
168             if ($field->getSequence()) {
169                 $this->manager->reset_sequence($tablename);
170                 return;
171             }
172         }
173         if ($this->transactionmode == 'pertable') {
174             $this->transaction->allow_commit();
175         }
176     }
178     /**
179      * Callback function. Should be called only once database per import
180      * operation, after all database changes are made. It will commit changes.
181      * @return void
182      */
183     public function finish_database_import() {
184         if ($this->transactionmode == 'allinone') {
185             $this->transaction->allow_commit();
186         }
187     }
189     /**
190      * Callback function. Should be called only once per record import operation, only
191      * between @see begin_table_import and @see finish_table_import calls.
192      * It will insert table data.
193      *
194      * @throws dml_exception if data insert operation failed
195      *
196      * @param string $tablename - the name of the table in which data will be
197      * imported
198      * @param object $data - data object (fields and values will be inserted
199      * into table)
200      * @return void
201      */
202     public function import_table_data($tablename, $data) {
203         $this->mdb->import_record($tablename, $data);
204     }
206     /**
207      * Common import method
208      * @return void
209      */
210     public function import_database() {
211         // implement in subclasses
212     }