diff --git a/admin/dbtransfer/database_export_form.php b/admin/dbtransfer/database_export_form.php
new file mode 100644
index 00000000000..9ea1e37750c
--- /dev/null
+++ b/admin/dbtransfer/database_export_form.php
@@ -0,0 +1,15 @@
+libdir.'/formslib.php';
+
+class database_export_form extends moodleform {
+
+ function definition() {
+ $mform = $this->_form;
+
+ $mform->addElement('header', 'database', get_string('dbexport', 'dbtransfer'));
+ $mform->addElement('textarea', 'description', get_string('description'), array('rows'=>5, 'cols'=>60));
+
+ $this->add_action_buttons(false, get_string('exportdata', 'dbtransfer'));
+ }
+}
diff --git a/admin/dbtransfer/database_transfer_form.php b/admin/dbtransfer/database_transfer_form.php
new file mode 100644
index 00000000000..4dcd1d3164d
--- /dev/null
+++ b/admin/dbtransfer/database_transfer_form.php
@@ -0,0 +1,47 @@
+libdir.'/formslib.php';
+
+class database_transfer_form extends moodleform {
+
+ function definition() {
+ $mform = $this->_form;
+
+ $mform->addElement('header', 'database', get_string('dbtransfer', 'dbtransfer'));
+
+ $supported = array (
+ 'mysqli/adodb',
+ 'mysql/adodb',
+ 'postgres7/adodb',
+ 'mssql_n/adodb',
+ 'mssql/adodb',
+ 'odbc_mssql/adodb',
+ 'oci8po/adodb',
+ 'sqlite3/pdo',
+ );
+ $drivers = array();
+ foreach($supported as $driver) {
+ list($dbtype, $dblibrary) = explode('/', $driver);
+ $targetdb = moodle_database::get_driver_instance($dbtype, $dblibrary);
+ if ($targetdb->driver_installed() !== true) {
+ continue;
+ }
+ $drivers[$driver] = $driver;
+ }
+
+ $mform->addElement('select', 'driver', get_string('dbtype', 'install'), $drivers);
+ $mform->addElement('text', 'dbhost', get_string('dbhost', 'install'));
+ $mform->addElement('text', 'dbname', get_string('database', 'install'));
+ $mform->addElement('text', 'dbuser', get_string('user'));
+ $mform->addElement('text', 'dbpass', get_string('password'));
+ $mform->addElement('text', 'prefix', get_string('dbprefix', 'install'));
+
+ $mform->addRule('dbhost', get_string('required'), 'required', null);
+ $mform->addRule('dbname', get_string('required'), 'required', null);
+ $mform->addRule('dbuser', get_string('required'), 'required', null);
+ $mform->addRule('dbpass', get_string('required'), 'required', null);
+ $mform->addRule('prefix', get_string('required'), 'required', null);
+
+ $this->add_action_buttons(false, get_string('transferdata', 'dbtransfer'));
+ }
+}
diff --git a/admin/dbtransfer/dbexport.php b/admin/dbtransfer/dbexport.php
new file mode 100644
index 00000000000..02d28f0e0d0
--- /dev/null
+++ b/admin/dbtransfer/dbexport.php
@@ -0,0 +1,21 @@
+get_data()) {
+ dbtransfer_export_xml_database($data->description, $DB);
+ die;
+}
+
+admin_externalpage_print_header();
+// TODO: add some more info here
+$form->display();
+admin_externalpage_print_footer();
diff --git a/admin/dbtransfer/index.php b/admin/dbtransfer/index.php
new file mode 100644
index 00000000000..cb25530dbeb
--- /dev/null
+++ b/admin/dbtransfer/index.php
@@ -0,0 +1,34 @@
+get_data()) {
+ list($dbtype, $dblibrary) = explode('/', $data->driver);
+ $targetdb = moodle_database::get_driver_instance($dbtype, $dblibrary);
+ if (!$targetdb->connect($data->dbhost, $data->dbuser, $data->dbpass, $data->dbname, false, $data->prefix, null)) {
+ throw new dbtransfer_exception('notargetconectexception', null, "$CFG->wwwroot/$CFG->admin/dbtransfer/");
+ }
+ if ($targetdb->get_tables()) {
+ // TODO add exception or string...
+ error('Sorry, tables already exist in selected database. Can not continue.');
+ }
+ admin_externalpage_print_header();
+ dbtransfer_transfer_database($DB, $targetdb);
+ notify(get_string('success'), 'notifysuccess');
+ print_continue("$CFG->wwwroot/$CFG->admin/");
+ admin_externalpage_print_footer();
+ die;
+}
+
+admin_externalpage_print_header();
+// TODO: add some more info here
+$form->display();
+admin_externalpage_print_footer();
diff --git a/admin/dbtransfer/lib.php b/admin/dbtransfer/lib.php
new file mode 100644
index 00000000000..4241c303950
--- /dev/null
+++ b/admin/dbtransfer/lib.php
@@ -0,0 +1,52 @@
+2GB fails in 32bit operating systems - needs warning
+ - we may run out of disk space exporting to srever file - we must verify the file is not truncated; read from the end of file?
+ - when sending file >4GB - FAT32 limit, Apache limit, browser limit - needs warning
+ - there must be some form of progress bar during export, transfer - new tracking class could be passed around
+ - command line operation - could work around some 2G/4G limits in PHP; useful for cron full backups
+ - by default allow exporting into empty database only (no tables with the same prefix yet)
+ - all dangerous operation (like deleting of all data) should be confirmed by key found in special file in dataroot
+ (user would need file access to dataroot which might prevent various "accidents")
+ - implement "Export/import running" notification in lib/setup.php (similar to new upgrade flag in config table)
+ - gzip compression when storing xml file - the xml is very verbose and full of repeated tags (zip is not suitable here at all)
+ this could help us keep the files bellow 2G (expected ratio is > 10:1)
+
+*/
+
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/dtllib.php');
+
+
+function dbtransfer_export_xml_database($description, $mdb) {
+ @set_time_limit(0);
+
+ session_write_close(); // release session
+
+ header('Content-Type: application/xhtml+xml');
+ header('Content-Disposition: attachment; filename=database.xml');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate,post-check=0,pre-check=0');
+ header('Pragma: public');
+
+ while(@ob_flush());
+
+ $var = new file_xml_database_exporter('php://output', $mdb);
+ $var->export_database($description);
+
+ // no more output
+ die;
+}
+
+
+function dbtransfer_transfer_database($sourcedb, $targetdb) {
+ @set_time_limit(0);
+
+ session_write_close(); // release session
+
+ $var = new database_mover($sourcedb, $targetdb);
+ $var->export_database(null);
+}
diff --git a/admin/settings/misc.php b/admin/settings/misc.php
index b4cb7160976..21ef81e0d4d 100644
--- a/admin/settings/misc.php
+++ b/admin/settings/misc.php
@@ -5,10 +5,12 @@
if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
// Experimental settings page
- $temp = new admin_settingpage('experimental', get_string('experimental', 'admin'));
+ $ADMIN->add('misc', new admin_category('experimental', get_string('experimental','admin')));
+
+ $temp = new admin_settingpage('experimentalsettings', get_string('experimentalsettings', 'admin'));
$temp->add(new admin_setting_configcheckbox('enableglobalsearch', get_string('enableglobalsearch', 'admin'), get_string('configenableglobalsearch', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('smartpix', get_string('smartpix', 'admin'), get_string('configsmartpix', 'admin'), 0));
- $item = new admin_setting_configcheckbox('enablehtmlpurifier', get_string('enablehtmlpurifier', 'admin'), get_string('configenablehtmlpurifier', 'admin'), 0);
+ $item = new admin_setting_configcheckbox('enablehtmlpurifier', get_string('enablehtmlpurifier', 'admin'), get_string('configenablehtmlpurifier', 'admin'), 0);
$item->set_updatedcallback('reset_text_filters_cache');
$temp->add($item);
@@ -17,7 +19,11 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$temp->add(new admin_setting_configcheckbox('enablecompletion', get_string('enablecompletion','completion'), get_string('configenablecompletion','completion'), COMPLETION_DISABLED));
$temp->add(new admin_setting_pickroles('progresstrackedroles', get_string('progresstrackedroles','completion'), get_string('configprogresstrackedroles', 'completion'), array('moodle/legacy:student')));
- $ADMIN->add('misc', $temp);
+ $ADMIN->add('experimental', $temp);
+
+ // DB transfer related pages
+ $ADMIN->add('experimental', new admin_externalpage('dbtransfer', get_string('dbtransfer', 'dbtransfer'), $CFG->wwwroot.'/'.$CFG->admin.'/dbtransfer/index.php', 'moodle/site:config', false));
+ $ADMIN->add('experimental', new admin_externalpage('dbexport', get_string('dbexport', 'dbtransfer'), $CFG->wwwroot.'/'.$CFG->admin.'/dbtransfer/dbexport.php', 'moodle/site:config', false));
// XMLDB editor
$ADMIN->add('misc', new admin_externalpage('xmldbeditor', get_string('xmldbeditor'), "$CFG->wwwroot/$CFG->admin/xmldb/"));
diff --git a/lang/en_utf8/admin.php b/lang/en_utf8/admin.php
index a17d998c95b..88f29d7f064 100644
--- a/lang/en_utf8/admin.php
+++ b/lang/en_utf8/admin.php
@@ -361,6 +361,7 @@ $string['errors'] = 'Errors';
$string['errorsetting'] = 'Could not save setting:';
$string['errorwithsettings'] = 'Some settings were not changed due to an error.';
$string['experimental'] = 'Experimental';
+$string['experimentalsettings'] = 'Experimental settings';
$string['extendedusernamechars'] = 'Allow extended characters in usernames';
$string['filecreated'] = 'New file created';
$string['filestoredin'] = 'Save file into folder :';
diff --git a/lang/en_utf8/dbtransfer.php b/lang/en_utf8/dbtransfer.php
new file mode 100644
index 00000000000..3adbaba86b6
--- /dev/null
+++ b/lang/en_utf8/dbtransfer.php
@@ -0,0 +1,13 @@
+ $a';
+$string['importschemaexception'] = 'Current database structure does not match all install.xml files.
$a';
+$string['importversionmismatchexception'] = 'Current version $->currentver does match exported version $a->schemaver.';
+$string['malformedxmlexception'] = 'Malformed XML found, can not continue.';
+$string['notargetconectexception'] = 'Can not connect target database, sorry.';
+$string['transferdata'] = 'Transfer data';
+$string['unknowntableexception'] = 'Unknown table $a found in export file.';
diff --git a/lib/dtl/database_exporter.php b/lib/dtl/database_exporter.php
index 2c6b573c28c..34789ab5a0b 100644
--- a/lib/dtl/database_exporter.php
+++ b/lib/dtl/database_exporter.php
@@ -104,25 +104,31 @@ abstract class database_exporter {
* @see $check_schema is true), queries the database and calls
* appropiate callbacks.
*
- * @exception export_exception if any checking (e.g. database schema) fails
+ * @exception dbtransfer_exception if any checking (e.g. database schema) fails
*
* @param string $description a user description of the data.
*/
public function export_database($description=null) {
global $CFG;
- if ($this->check_schema and $this->manager->check_database_schema($this->schema)) {
- //TODO put message in error lang
- throw new export_exception('XMLDB schema does not match database schema.');
+ if ($this->check_schema and $errors = $this->manager->check_database_schema($this->schema)) {
+ $details = '';
+ foreach ($errors as $table=>$items) {
+ $details .= '
'.get_string('table').' '.$table.':';
+ $details .= '
';
+ foreach ($items as $item) {
+ $details .= '- '.$item.'
';
+ }
+ $details .= '
';
+ }
+ throw new dbtransfer_exception('exportschemaexception', $details);
}
$tables = $this->schema->getTables();
$this->begin_database_export($CFG->version, $CFG->release, date('c'), $description);
foreach ($tables as $table) {
$rs = $this->mdb->get_recordset_sql('SELECT * FROM {'.$table->getName().'}');
- //TODO remove this when dml will have exceptions
if (!$rs) {
- //TODO put message in error lang
- throw new export_exception('An error occured while reading the database.');
+ throw new ddl_table_missing_exception($table->getName());
}
$this->begin_table_export($table);
foreach ($rs as $row) {
diff --git a/lib/dtl/database_importer.php b/lib/dtl/database_importer.php
index 24cf79d7d90..e88f66c12c9 100644
--- a/lib/dtl/database_importer.php
+++ b/lib/dtl/database_importer.php
@@ -57,7 +57,7 @@ class database_importer {
* operation, before any database changes are made. It will check the database
* schema if @see check_schema is true
*
- * @exception import_exception if any checking (e.g. database schema, Moodle
+ * @exception dbtransfer_exception if any checking (e.g. database schema, Moodle
* version) fails
*
* @param float $version the version of the system which generated the data
@@ -73,13 +73,21 @@ class database_importer {
}
if (round($version, 2) !== round($CFG->version, 2)) { // version might be in decimal format too
- //TODO put message in error lang
- throw new import_exception('Current Moodle version does not match exported Moodle version.');
+ $a = (object)array('schemaver'=>$version, 'currentver'=>$CFG->version);
+ throw new dbtransfer_exception('importversionmismatchexception', $a);
}
- if ($this->check_schema && $this->manager->check_database_schema($this->schema)) {
- //TODO put message in error lang
- throw new import_exception('XMLDB schema does not match database schema.');
+ if ($this->check_schema and $errors = $this->manager->check_database_schema($this->schema)) {
+ $details = '';
+ foreach ($errors as $table=>$items) {
+ $details .= ''.get_string('table').' '.$table.':';
+ $details .= '
';
+ foreach ($items as $item) {
+ $details .= '- '.$item.'
';
+ }
+ $details .= '
';
+ }
+ throw new dbtransfer_exception('importschemaexception', $details);
}
$this->mdb->begin_sql();
}
@@ -88,7 +96,7 @@ class database_importer {
* Callback function. Should be called only once per table import operation,
* before any table changes are made. It will delete all table data.
*
- * @exception import_exception an unknown table import is attempted
+ * @exception dbtransfer_exception an unknown table import is attempted
* @exception ddl_table_missing_exception if the table is missing
*
* @param string $tablename - the name of the table that will be imported
@@ -97,16 +105,13 @@ class database_importer {
*/
public function begin_table_import($tablename, $schemaHash) {
if (!$table = $this->schema->getTable($tablename)) {
- //TODO put message in error lang
- throw new import_exception('Unknown table in import data');
+ throw new dbtransfer_exception('unknowntableexception', $tablename);
}
if ($schemaHash != $table->getHash()) {
- throw new import_exception('XMLDB schema does not match database schema.');
+ throw new dbtransfer_exception('differenttableexception', $tablename);
}
// this should not happen, unless someone drops tables after import started
if (!$this->manager->table_exists($table)) {
- // in the future, missing tables will be recreated with
- //$this->manager->create_table($table);
throw new ddl_table_missing_exception($tablename);
}
$this->mdb->delete_records($tablename);
diff --git a/lib/dtl/database_mover.php b/lib/dtl/database_mover.php
index cdadf243efe..415b753d8d1 100644
--- a/lib/dtl/database_mover.php
+++ b/lib/dtl/database_mover.php
@@ -7,15 +7,15 @@ class database_mover extends database_exporter {
/**
* Object constructor.
*
- * @param moodle_database $mdb_target Connection to the target database (a
- * @see moodle_database object).
* @param moodle_database $mdb Connection to the source database (a
* @see moodle_database object).
+ * @param moodle_database $mdb_target Connection to the target database (a
+ * @see moodle_database object).
* @param boolean $check_schema - whether or not to check that XML database
* schema matches the RDBMS database schema before exporting (used by
* @see export_database).
*/
- public function __construct(moodle_database $mdb_target, moodle_database $mdb_source, $check_schema=true) {
+ public function __construct(moodle_database $mdb_source, moodle_database $mdb_target, $check_schema=true) {
parent::__construct($mdb_source, $check_schema);
$this->importer = new database_importer($mdb_target, $check_schema);
}
diff --git a/lib/dtl/file_xml_database_exporter.php b/lib/dtl/file_xml_database_exporter.php
index 6fec2e4610d..e0f5c3e268a 100644
--- a/lib/dtl/file_xml_database_exporter.php
+++ b/lib/dtl/file_xml_database_exporter.php
@@ -12,7 +12,7 @@ class file_xml_database_exporter extends xml_database_exporter {
/**
* Object constructor.
*
- * @param string $filepath - path to the XML data file. Use null for PHP
+ * @param string $filepath - path to the XML data file. Use 'php://output' for PHP
* output stream.
* @param moodle_database $mdb Connection to the source database
* @see xml_database_exporter::__construct()
@@ -20,9 +20,6 @@ class file_xml_database_exporter extends xml_database_exporter {
* @see xml_database_exporter::__construct()
*/
public function __construct($filepath, moodle_database $mdb, $check_schema=true) {
- if (is_null($filepath)) {
- $filepath = 'php://output';
- }
parent::__construct($mdb, $check_schema);
$this->filepath = $filepath;
}
@@ -38,7 +35,7 @@ class file_xml_database_exporter extends xml_database_exporter {
* Specific implementation for file exporting the database: it opens output stream, calls
* superclass @see database_exporter::export_database() and closes output stream.
*
- * @exception export_exception if any checking (e.g. database schema) fails
+ * @exception dbtransfer_exception if any checking (e.g. database schema) fails
*
* @param string $description a user description of the data.
*/
diff --git a/lib/dtl/file_xml_database_importer.php b/lib/dtl/file_xml_database_importer.php
index 8d6dfc63091..5a0ef0f22c9 100644
--- a/lib/dtl/file_xml_database_importer.php
+++ b/lib/dtl/file_xml_database_importer.php
@@ -10,7 +10,7 @@ class file_xml_database_importer extends xml_database_importer {
/**
* Object constructor.
*
- * @param string $filepath - path to the XML data file. Use null for PHP
+ * @param string $filepath - path to the XML data file. Use 'php://input' for PHP
* input stream.
* @param moodle_database $mdb Connection to the target database
* @see xml_database_importer::__construct()
@@ -18,9 +18,6 @@ class file_xml_database_importer extends xml_database_importer {
* @see xml_database_importer::__construct()
*/
public function __construct($filepath, moodle_database $mdb, $check_schema=true) {
- if (is_null($filepath)) {
- $filepath = 'php://input';
- }
$this->filepath = $filepath;
parent::__construct($mdb, $check_schema);
}
@@ -35,8 +32,7 @@ class file_xml_database_importer extends xml_database_importer {
$parser = $this->get_parser();
while ($data = fread($file, 65536)) {
if (!xml_parse($parser, $data, feof($file))) {
- //TODO localize
- throw new import_exception("XML data not well-formed.");
+ throw new dbtransfer_exception('malformedxmlexception');
}
}
xml_parser_free($parser);
diff --git a/lib/dtl/string_xml_database_exporter.php b/lib/dtl/string_xml_database_exporter.php
index 84649929191..3bc9e8e641e 100644
--- a/lib/dtl/string_xml_database_exporter.php
+++ b/lib/dtl/string_xml_database_exporter.php
@@ -26,7 +26,7 @@ class string_xml_database_exporter extends xml_database_exporter {
* Specific implementation for memory exporting the database: it clear the buffer
* and calls superclass @see database_exporter::export_database().
*
- * @exception export_exception if any checking (e.g. database schema) fails
+ * @exception dbtransfer_exception if any checking (e.g. database schema) fails
* @param string $description a user description of the data.
* @return void
*/
diff --git a/lib/dtl/string_xml_database_importer.php b/lib/dtl/string_xml_database_importer.php
index 983d566df5d..42e8a1391b5 100644
--- a/lib/dtl/string_xml_database_importer.php
+++ b/lib/dtl/string_xml_database_importer.php
@@ -29,8 +29,7 @@ class string_xml_database_importer extends xml_database_importer {
public function import_database() {
$parser = $this->get_parser();
if (!xml_parse($parser, $this->data, true)) {
- //TODO localize
- throw new import_exception("XML data not well-formed.");
+ throw new dbtransfer_exception('malformedxmlexception');
}
xml_parser_free($parser);
}
diff --git a/lib/dtl/xml_database_importer.php b/lib/dtl/xml_database_importer.php
index 4badbc2edb7..4f4e9c6f567 100644
--- a/lib/dtl/xml_database_importer.php
+++ b/lib/dtl/xml_database_importer.php
@@ -39,29 +39,29 @@ abstract class xml_database_importer extends database_importer {
switch ($tag) {
case 'moodle_database' :
if (empty($attributes['version']) || empty($attributes['timestamp'])) {
- throw new import_exception('Missing tag attribute in data file.');
+ throw new dbtransfer_exception('malformedxmlexception');
}
$this->begin_database_import($attributes['version'], $attributes['timestamp']);
break;
case 'table' :
if (isset($this->current_table)) {
- throw new import_exception('Unexpected tag in data file.');
+ throw new dbtransfer_exception('malformedxmlexception');
}
if (empty($attributes['name']) || empty($attributes['schemaHash'])) {
- throw new import_exception('Missing tag attribute in data file.');
+ throw new dbtransfer_exception('malformedxmlexception');
}
$this->current_table = $attributes['name'];
$this->begin_table_import($this->current_table, $attributes['schemaHash']);
break;
case 'record' :
if (isset($this->current_row) || !isset($this->current_table)) {
- throw new import_exception('Unexpected tag in data file.');
+ throw new dbtransfer_exception('malformedxmlexception');
}
$this->current_row = new object();
break;
case 'field' :
if (isset($this->current_field) || !isset($this->current_row)) {
- throw new import_exception('Unexpected tag in data file.');
+ throw new dbtransfer_exception('malformedxmlexception');
}
$this->current_field = $attributes['name'];
$this->current_data = '';
@@ -72,8 +72,7 @@ abstract class xml_database_importer extends database_importer {
}
break;
default :
- //TODO localize
- throw new import_exception('XML content not valid for import operation.');
+ throw new dbtransfer_exception('malformedxmlexception');
}
}
@@ -113,8 +112,7 @@ abstract class xml_database_importer extends database_importer {
break;
default :
- //TODO put message in error lang
- throw new import_exception('XML content not valid for import operation.');
+ throw new dbtransfer_exception('malformedxmlexception');
}
}
diff --git a/lib/dtllib.php b/lib/dtllib.php
index d5e8874fc10..34ce69436a7 100644
--- a/lib/dtllib.php
+++ b/lib/dtllib.php
@@ -41,23 +41,16 @@ require_once($CFG->libdir.'/dtl/file_xml_database_importer.php');
require_once($CFG->libdir.'/dtl/string_xml_database_importer.php');
/**
- * Exception class for export operations.
+ * Exception class for db transfer
* @see moodle_exception
- * TODO subclass for specific purposes
*/
-class export_exception extends moodle_exception {
- function __construct($errorcode, $a=null, $debuginfo=null) {
- parent::__construct($errorcode, '', '', $a, $debuginfo);
+class dbtransfer_exception extends moodle_exception {
+ function __construct($errorcode, $a=null, $link='', $debuginfo=null) {
+ global $CFG;
+ if (empty($link)) {
+ $link = "$CFG->wwwroot/$CFG->admin/";
+ }
+ parent::__construct($errorcode, 'dbtransfer', $link, $a, $debuginfo);
}
}
-/**
- * Exception class for import operations.
- * @see moodle_exception
- * TODO subclass for specific purposes
- */
-class import_exception extends moodle_exception {
- function __construct($errorcode, $a=null, $debuginfo=null) {
- parent::__construct($errorcode, '', '', $a, $debuginfo);
- }
-}