From 1e2c7351113f5310e38da704dc66893e6dc82c4e Mon Sep 17 00:00:00 2001 From: David Mudrak Date: Mon, 9 May 2011 10:16:00 +0200 Subject: [PATCH] MDL-27376 MDL-27377 MDL-27378 Backup converters refactoring - work in progress These are David's changes of Mark's code that replace the plan/tasks/steps infrastructure with a bit simpler one. The changes will be described in the next commit that will actually finish the conversion. TODO: refactor backup/converter/moodle1/stepslib.php into conversion handlers. --- .../controller/restore_controller.class.php | 8 +- .../convertlib.php} | 76 +- backup/converter/moodle1/converter.class.php | 132 ---- backup/converter/moodle1/handlerlib.php | 245 +++++++ backup/converter/moodle1/lib.php | 672 ++++++++++++++++++ .../{testconverter.php => testlib.php} | 4 +- backup/converter/moodle1/stepslib.php | 2 +- backup/converter/moodle1/taskslib.php | 107 --- backup/moodle2/convert_stepslib.php | 51 -- .../util/converter/plan_converter.class.php | 165 ----- .../util/factories/convert_factory.class.php | 52 +- .../helper/backup_general_helper.class.php | 3 +- backup/util/helper/convert_helper.class.php | 59 +- ...nvert_structure_parser_processor.class.php | 104 --- .../helper/simpletest/testconverthelper.php | 2 +- backup/util/includes/convert_includes.php | 39 +- .../structure/convert_path_element.class.php | 31 - .../convert_forum_activity_task.class.php | 42 -- .../{convert_forum_stepslib.php => lib.php} | 31 +- 19 files changed, 1046 insertions(+), 779 deletions(-) rename backup/{util/converter/base_converter.class.php => converter/convertlib.php} (80%) delete mode 100644 backup/converter/moodle1/converter.class.php create mode 100644 backup/converter/moodle1/handlerlib.php create mode 100644 backup/converter/moodle1/lib.php rename backup/converter/moodle1/simpletest/{testconverter.php => testlib.php} (94%) delete mode 100644 backup/converter/moodle1/taskslib.php delete mode 100644 backup/moodle2/convert_stepslib.php delete mode 100644 backup/util/converter/plan_converter.class.php delete mode 100644 backup/util/helper/convert_structure_parser_processor.class.php delete mode 100644 backup/util/structure/convert_path_element.class.php delete mode 100644 mod/forum/backup/moodle1/convert_forum_activity_task.class.php rename mod/forum/backup/moodle1/{convert_forum_stepslib.php => lib.php} (54%) diff --git a/backup/controller/restore_controller.class.php b/backup/controller/restore_controller.class.php index c2078172d98a5..c68433c882496 100644 --- a/backup/controller/restore_controller.class.php +++ b/backup/controller/restore_controller.class.php @@ -381,14 +381,18 @@ public static function get_tempdir_name($courseid = 0, $userid = 0) { */ public function convert() { global $CFG; - require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); + require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php'); if ($this->status != backup::STATUS_REQUIRE_CONV) { throw new restore_controller_exception('cannot_convert_not_required_status'); } // Run conversion to the proper format - convert_helper::to_moodle2_format($this->get_tempdir(), $this->format); + if (!convert_helper::to_moodle2_format($this->get_tempdir(), $this->format)) { + // todo - unable to find the conversion path, what to do now? + // throwing the exception as a temporary solution + throw new restore_controller_exception('unable_to_find_conversion_path'); + } // If no exceptions were thrown, then we are in the proper format $this->format = backup::FORMAT_MOODLE; diff --git a/backup/util/converter/base_converter.class.php b/backup/converter/convertlib.php similarity index 80% rename from backup/util/converter/base_converter.class.php rename to backup/converter/convertlib.php index 364dcb2308c6d..73341e4f3bd67 100644 --- a/backup/util/converter/base_converter.class.php +++ b/backup/converter/convertlib.php @@ -16,6 +16,8 @@ // along with Moodle. If not, see . /** + * Provides base converter classes + * * @package core * @subpackage backup-convert * @copyright 2011 Mark Nielsen @@ -24,10 +26,14 @@ defined('MOODLE_INTERNAL') || die(); +require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); + /** - * Base abstract converter + * Base converter class + * + * All Moodle backup converters are supposed to extend this base class. * - * @throws backup_exception|Exception|null + * @throws convert_exception */ abstract class base_converter { @@ -74,9 +80,6 @@ public function get_name() { */ public function convert() { - $e = null; - - // try to execute the converter try { $this->create_workdir(); $this->execute(); @@ -88,7 +91,7 @@ public function convert() { $this->destroy(); // eventually re-throw the execution exception - if ($e instanceof Exception) { + if (isset($e) and ($e instanceof Exception)) { throw $e; } } @@ -139,23 +142,10 @@ public static function description() { ); } - /// end of public API ////////////////////////////////////////////////////// - - /** - * Initialize the instance if needed, called by the constructor - */ - protected function init() { - } - - /** - * Converts the contents of the tempdir into the target format in the workdir - */ - protected abstract function execute(); - /** * @return string the full path to the working directory */ - protected function get_workdir_path() { + public function get_workdir_path() { global $CFG; return "$CFG->dataroot/temp/backup/$this->workdir"; @@ -164,12 +154,25 @@ protected function get_workdir_path() { /** * @return string the full path to the directory with the source backup */ - protected function get_tempdir_path() { + public function get_tempdir_path() { global $CFG; return "$CFG->dataroot/temp/backup/$this->tempdir"; } + /// end of public API ////////////////////////////////////////////////////// + + /** + * Initialize the instance if needed, called by the constructor + */ + protected function init() { + } + + /** + * Converts the contents of the tempdir into the target format in the workdir + */ + protected abstract function execute(); + /** * Prepares a new empty working directory */ @@ -177,7 +180,7 @@ protected function create_workdir() { fulldelete($this->get_workdir_path()); if (!check_dir_exists($this->get_workdir_path())) { - throw new backup_exception('failedtocreateworkdir'); + throw new convert_exception('failed_create_workdir'); } } @@ -191,15 +194,15 @@ protected function replace_tempdir() { global $CFG; if (empty($CFG->keeptempdirectoriesonbackup)) { - fulldelete($this->get_tempdir_path); + fulldelete($this->get_tempdir_path()); } else { - if (!rename($this->get_tempdir_path, $this->get_tempdir_path . '_' . $this->get_name() . '_' . $this->id . '_source')) { - throw new backup_exception('failedrenamesourcetempdir'); + if (!rename($this->get_tempdir_path(), $this->get_tempdir_path() . '_' . $this->get_name() . '_' . $this->id . '_source')) { + throw new convert_exception('failed_rename_source_tempdir'); } } if (!rename($this->get_workdir_path(), $this->get_tempdir_path())) { - throw new backup_exception('failedmoveconvertedintoplace'); + throw new convert_exception('failed_move_converted_into_place'); } } @@ -213,7 +216,26 @@ protected function destroy() { global $CFG; if (empty($CFG->keeptempdirectoriesonbackup)) { - fulldelete($this->get_workdir_path); + fulldelete($this->get_workdir_path()); } } } + +/** + * General convert-related exception + * + * @author David Mudrak + */ +class convert_exception extends moodle_exception { + + /** + * Constructor + * + * @param string $errorcode key for the corresponding error string + * @param object $a extra words and phrases that might be required in the error string + * @param string $debuginfo optional debugging information + */ + public function __construct($errorcode, $a = null, $debuginfo = null) { + parent::__construct($errorcode, '', '', $a, $debuginfo); + } +} diff --git a/backup/converter/moodle1/converter.class.php b/backup/converter/moodle1/converter.class.php deleted file mode 100644 index 4395c10361336..0000000000000 --- a/backup/converter/moodle1/converter.class.php +++ /dev/null @@ -1,132 +0,0 @@ -. - -/** - * @package core - * @subpackage backup-convert - * @copyright 2011 Mark Nielsen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/backup/converter/moodle1/taskslib.php'); -require_once($CFG->dirroot . '/backup/converter/moodle1/stepslib.php'); - -/** - * Converter of Moodle 1.9 backup into Moodle 2.x format - */ -class moodle1_converter extends plan_converter { - - /** @var string the current module being processed */ - protected $currentmod = ''; - - /** @var string the current block being processed */ - protected $currentblock = ''; - - /** - * Detects the Moodle 1.9 format of the backup directory - * - * @param string $tempdir the name of the backup directory - * @return null|string backup::FORMAT_MOODLE1 if the Moodle 1.9 is detected, null otherwise - */ - public static function detect_format($tempdir) { - global $CFG; - - $filepath = $CFG->dataroot . '/temp/backup/' . $tempdir . '/moodle.xml'; - if (file_exists($filepath)) { - // looks promising, lets load some information - $handle = fopen($filepath, 'r'); - $first_chars = fread($handle, 200); - fclose($handle); - - // check if it has the required strings - if (strpos($first_chars,'') !== false and - strpos($first_chars,'') !== false and - strpos($first_chars,'') !== false) { - - return backup::FORMAT_MOODLE1; - } - } - - return null; - } - - /** - * Path transformation for modules and blocks. Here we - * are collapsing paths that use the plugin's name. - */ - public function add_structures($processingobject, array $structures) { - parent::add_structures($processingobject, $structures); - - foreach ($structures as $element) { - $path = $element->get_path(); - - // @todo Add same for blocks - $path = preg_replace('/^\/MOODLE_BACKUP\/COURSE\/MODULES\/MOD\/(\w+)\//', '/MOODLE_BACKUP/COURSE/MODULES/MOD/', $path); - if (!empty($path) and $path != $element->get_path()) { - $this->xmlprocessor->add_path($path, false); - } - } - } - - /** - * Path transformation for modules and blocks. Here we - * are expanding paths to include the plugin's name. - */ - public function process($data) { - $path = $data['path']; - - // @todo Same path manipulation for blocks - if ($path == '/MOODLE_BACKUP/COURSE/MODULES/MOD') { - $this->currentmod = strtoupper($data['tags']['MODTYPE']); - $path = '/MOODLE_BACKUP/COURSE/MODULES/MOD/'.$this->currentmod; - - } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) { - $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/'.$this->currentmod, $path); - } - if ($path != $data['path']) { - // Have relaxed error handling on path transformations... - if (!array_key_exists($path, $this->pathelements)) { - debugging("Path transformation error, $path is not registered, probably similar to another plugin"); - return; - } - $data['path'] = $path; - } - parent::process($data); - } - - public function build_plan() { - $this->xmlparser = new progressive_parser(); - $this->xmlparser->set_file($this->get_tempdir_path() . '/moodle.xml'); - $this->xmlprocessor = new convert_structure_parser_processor($this); // @todo Probably move this - $this->xmlparser->set_processor($this->xmlprocessor); - - // These paths are dispatched by the converter through path transformation - $this->xmlprocessor->add_path('/MOODLE_BACKUP/COURSE/MODULES/MOD', false); - // @todo Add the same for blocks - - $this->get_plan()->add_task(new moodle1_root_task('root_task')); - $this->get_plan()->add_task(new moodle1_course_task('courseinfo')); - - // Build plugin tasks - convert_factory::build_plugin_tasks($this, 'mod', 'activity'); - convert_factory::build_plugin_tasks($this, 'block'); - - $this->get_plan()->add_task(new moodle1_final_task('final_task')); - } -} diff --git a/backup/converter/moodle1/handlerlib.php b/backup/converter/moodle1/handlerlib.php new file mode 100644 index 0000000000000..a8945ac8ec394 --- /dev/null +++ b/backup/converter/moodle1/handlerlib.php @@ -0,0 +1,245 @@ +. + +/** + * Defines Moodle 1.9 backup conversion handlers + * + * Handlers are classes responsible for the actual conversion work. Their logic + * is similar to the functionality provided by steps in plan based restore process. + * + * @package backup-convert + * @subpackage moodle1 + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php'); +require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); +require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php'); +require_once($CFG->dirroot . '/backup/util/dbops/backup_dbops.class.php'); +require_once($CFG->dirroot . '/backup/util/dbops/backup_controller_dbops.class.php'); + +/** + * Handlers factory class + */ +abstract class moodle1_handlers_factory { + + /** + * @param moodle1_converter the converter requesting the converters + * @return list of all available conversion handlers + */ + public static function get_handlers(moodle1_converter $converter) { + + $handlers = array(); + $handlers[] = new moodle1_root_handler($converter); + + $handlers = array_merge($handlers, self::get_plugin_handlers('mod', $converter)); + $handlers = array_merge($handlers, self::get_plugin_handlers('block', $converter)); + + return $handlers; + } + + /// public API ends here /////////////////////////////////////////////////// + + /** + * Runs through all plugins of a specific type and instantiates their handlers + * + * @todo ask mod's subplugins + * @param string $type the plugin type + * @param moodle1_converter $converter the converter requesting the handler + * @throws convert_exception + * @return array of {@link moodle1_handler} instances + */ + public static function get_plugin_handlers($type, moodle1_converter $converter) { + global $CFG; + + $handlers = array(); + $plugins = get_plugin_list($type); + foreach ($plugins as $name => $dir) { + $handlerfile = $dir . '/backup/moodle1/lib.php'; + $handlerclass = "moodle1_{$type}_{$name}_handler"; + if (!file_exists($handlerfile)) { + continue; + } + require_once($handlerfile); + + if (!class_exists($handlerclass)) { + throw new convert_exception('missing_handler_class', $handlerclass); + } + $handlers[] = new $handlerclass($converter, $type, $name); + } + return $handlers; + } +} + +/** + * General backup conversion handler + */ +abstract class moodle1_handler { + + /** @var moodle1_converter */ + protected $converter; + + /** @var xml_writer */ + protected $xmlwriter; + + /** + * @param moodle1_converter $converter the converter that requires us + */ + public function __construct(moodle1_converter $converter) { + + $this->converter = $converter; + } + + /** + * Opens the XML writer - after calling, one is free to use $xmlwriter + * + * @return void + */ + public function open_xml_writer() { + + if (is_null($this->get_xml_filename())) { + throw new convert_exception('handler_not_expected_to_write_xml'); + } + + if (!$this->xmlwriter instanceof xml_writer) { + $fullpath = $this->converter->get_workdir_path() . '/' . $this->get_xml_filename(); + $directory = pathinfo($fullpath, PATHINFO_DIRNAME); + + if (!check_dir_exists($directory)) { + throw new convert_exception('unable_create_target_directory', $directory); + } + $this->xmlwriter = new xml_writer(new file_xml_output($fullpath)); + $this->xmlwriter->start(); + } + } + + /** + * Close the XML writer + * + * At the moment, the caller must close all tags before calling + * + * @return void + */ + public function close_xml_writer() { + if ($this->xmlwriter instanceof xml_writer) { + $this->xmlwriter->stop(); + unset($this->xmlwriter); + $this->xmlwriter = null; + } + } + + /** + * @return string the file name of the target XML file to write into + */ + abstract protected function get_xml_filename(); +} + + +/** + * Shared base class for activity modules and blocks handlers + */ +abstract class moodle1_plugin_handler extends moodle1_handler { + + /** @var string */ + protected $plugintype; + + /** @var string */ + protected $pluginname; + + /** + * @param moodle1_converter $converter the converter that requires us + * @param string plugintype + * @param string pluginname + */ + public function __construct(moodle1_converter $converter, $plugintype, $pluginname) { + + parent::__construct($converter); + $this->plugintype = $plugintype; + $this->pluginname = $pluginname; + } +} + + +/** + * Base class for activity module handlers + */ +abstract class moodle1_mod_handler extends moodle1_plugin_handler { + + /** @var int module id */ + protected $moduleid; + + /** + * Return the relative path to the XML file that + * this step writes out to. Example: course/course.xml + * + * @return string + */ + public function get_xml_filename() { + return "activities/{$this->pluginname}_{$this->moduleid}/module.xml"; + } +} + + +/** + * Base class for activity module handlers + */ +abstract class moodle1_block_handler extends moodle1_handler { + +} + + +/** + * Process the root element of the backup file + */ +class moodle1_root_handler extends moodle1_handler { + + public function get_paths() { + return array(new convert_path('root_element', '/MOODLE_BACKUP')); + } + + public function process_root_element($data) { + // no data available - nothing to do + } + + /** + * This is executed at the very start of the moodle.xml parsing + */ + public function on_root_element_start() { + + // create ids temp table + backup_controller_dbops::create_backup_ids_temp_table($this->converter->get_id()); + } + + /** + * This is executed at the end of the moodle.xml parsing + */ + public function on_root_element_end() { + + // drop the ids temp table + backup_controller_dbops::drop_backup_ids_temp_table($this->converter->get_id()); + } + + /** + * This handler does not actually produces any output + */ + protected function get_xml_filename() { + return null; + } +} diff --git a/backup/converter/moodle1/lib.php b/backup/converter/moodle1/lib.php new file mode 100644 index 0000000000000..4d0964f176298 --- /dev/null +++ b/backup/converter/moodle1/lib.php @@ -0,0 +1,672 @@ +. + +/** + * Provides classes used by the moodle1 converter + * + * @package backup-convert + * @subpackage moodle1 + * @copyright 2011 Mark Nielsen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/backup/converter/convertlib.php'); +require_once($CFG->dirroot . '/backup/util/xml/parser/progressive_parser.class.php'); +require_once($CFG->dirroot . '/backup/util/xml/parser/processors/grouped_parser_processor.class.php'); +require_once(dirname(__FILE__) . '/handlerlib.php'); + +/** + * Converter of Moodle 1.9 backup into Moodle 2.x format + */ +class moodle1_converter extends base_converter { + + /** @var progressive_parser moodle.xml file parser */ + protected $xmlparser; + + /** @var moodle1_parser_processor */ + protected $xmlprocessor; + + /** @var array of {@link convert_path} to process */ + protected $pathelements = array(); + + /** @var string the current module being processed */ + protected $currentmod = ''; + + /** @var string the current block being processed */ + protected $currentblock = ''; + + /** @var string path currently locking processing of children */ + protected $pathlock; + + /** + * Instructs the dispatcher to ignore all children below path processor returning it + */ + const SKIP_ALL_CHILDREN = -991399; + + /** + * Detects the Moodle 1.9 format of the backup directory + * + * @param string $tempdir the name of the backup directory + * @return null|string backup::FORMAT_MOODLE1 if the Moodle 1.9 is detected, null otherwise + */ + public static function detect_format($tempdir) { + global $CFG; + + $filepath = $CFG->dataroot . '/temp/backup/' . $tempdir . '/moodle.xml'; + if (file_exists($filepath)) { + // looks promising, lets load some information + $handle = fopen($filepath, 'r'); + $first_chars = fread($handle, 200); + fclose($handle); + + // check if it has the required strings + if (strpos($first_chars,'') !== false and + strpos($first_chars,'') !== false and + strpos($first_chars,'') !== false) { + + return backup::FORMAT_MOODLE1; + } + } + + return null; + } + + /** + * Initialize the instance if needed, called by the constructor + * + * Here we create objects we need before the execution. + */ + protected function init() { + + // ask your mother first before going out playing with toys + parent::init(); + + // good boy, prepare XML parser and processor + $this->xmlparser = new progressive_parser(); + $this->xmlparser->set_file($this->get_tempdir_path() . '/moodle.xml'); + $this->xmlprocessor = new moodle1_parser_processor($this); + $this->xmlparser->set_processor($this->xmlprocessor); + + // make sure that MOD and BLOCK paths are visited + $this->xmlprocessor->add_path('/MOODLE_BACKUP/COURSE/MODULES/MOD'); + $this->xmlprocessor->add_path('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK'); + + // register the conversion handlers + foreach (moodle1_handlers_factory::get_handlers($this) as $handler) { + if (!$handler instanceof moodle1_handler) { + throw new convert_exception('wrong_handler_class', get_class($handler)); + } + $this->register_handler($handler, $handler->get_paths()); + } + } + + /** + * Converts the contents of the tempdir into the target format in the workdir + */ + protected function execute() { + $this->xmlparser->process(); + } + + /** + * Register a handler for the given path elements + */ + protected function register_handler(moodle1_handler $handler, array $elements) { + + // first iteration, push them to new array, indexed by name + // to detect duplicates in names or paths + $names = array(); + $paths = array(); + foreach($elements as $element) { + if (!$element instanceof convert_path) { + throw new convert_exception('path_element_wrong_class', get_class($element)); + } + if (array_key_exists($element->get_name(), $names)) { + throw new convert_exception('path_element_name_alreadyexists', $element->get_name()); + } + if (array_key_exists($element->get_path(), $paths)) { + throw new convert_exception('path_element_path_alreadyexists', $element->get_path()); + } + $names[$element->get_name()] = true; + $paths[$element->get_path()] = $element; + } + + // now, for each element not having a processing object yet, assign the handler + // if the element is not a memeber of a group + foreach($paths as $key => $element) { + if (is_null($element->get_processing_object()) and !$this->grouped_parent_exists($element, $paths)) { + $paths[$key]->set_processing_object($handler); + } + // add the element path to the processor + $this->xmlprocessor->add_path($element->get_path(), $element->is_grouped()); + } + + // done, store the paths (duplicates by path are discarded) + $this->pathelements = array_merge($this->pathelements, $paths); + + // remove the injected plugin name element from the MOD and BLOCK paths + // and register such collapsed path, too + foreach ($elements as $element) { + $path = $element->get_path(); + $path = preg_replace('/^\/MOODLE_BACKUP\/COURSE\/MODULES\/MOD\/(\w+)\//', '/MOODLE_BACKUP/COURSE/MODULES/MOD/', $path); + $path = preg_replace('/^\/MOODLE_BACKUP\/COURSE\/BLOCKS\/BLOCK\/(\w+)\//', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/', $path); + if (!empty($path) and $path != $element->get_path()) { + $this->xmlprocessor->add_path($path, false); + } + } + } + + /** + * Helper method used by {@link self::register_handler()} + * + * @param convert_path $pelement path element + * @param array of convert_path instances + * @return bool true if grouped parent was found, false otherwise + */ + protected function grouped_parent_exists($pelement, $elements) { + + foreach ($elements as $element) { + if ($pelement->get_path() == $element->get_path()) { + // don't compare against itself + continue; + } + // if the element is grouped and it is a parent of pelement, return true + if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) { + return true; + } + } + + // no grouped parent found + return false; + } + + /** + * Process the data obtained from the XML parser processor + * + * This methods receives one chunk of information from the XML parser + * processor and dispatches it, following the naming rules. + * We are expanding the modules and blocks paths here to include the plugin's name. + * + * @param array $data + */ + public function process_chunk($data) { + + $path = $data['path']; + + // expand the MOD paths so that they contain the module name + if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') { + $this->currentmod = strtoupper($data['tags']['MODTYPE']); + $path = '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod; + + } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) { + $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path); + } + + // expand the BLOCK paths so that they contain the module name + if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') { + $this->currentblock = strtoupper($data['tags']['NAME']); + $path = '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentblock; + + } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) { + $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentmod, $path); + } + + if ($path !== $data['path']) { + if (!array_key_exists($path, $this->pathelements)) { + // no handler registered for the transformed MOD or BLOCK path + // todo add this event to the convert log instead of debugging + //debugging('No handler registered for the path ' . $path); + return; + + } else { + // pretend as if the original $data contained the tranformed path + $data['path'] = $path; + } + } + + if (!array_key_exists($data['path'], $this->pathelements)) { + // path added to the processor without the handler + throw new convert_exception('missing_path_handler', $data['path']); + } + + $element = $this->pathelements[$data['path']]; + $object = $element->get_processing_object(); + $method = $element->get_processing_method(); + $rdata = null; // data returned by the processing method, if any + + if (empty($object)) { + throw new convert_exception('missing_processing_object', $object); + } + + // release the lock if we aren't anymore within children of it + if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) { + $this->pathlock = null; + } + + // if the path is not locked, apply the element's recipes and dispatch + // the cooked tags to the processing method + if (is_null($this->pathlock)) { + $data['tags'] = $element->apply_recipes($data['tags']); + $rdata = $object->$method($data['tags']); + } + + // if the dispatched method returned SKIP_ALL_CHILDREN, remember the current path + // and lock it so that its children are not dispatched + if ($rdata === self::SKIP_ALL_CHILDREN) { + // check we haven't any previous lock + if (!is_null($this->pathlock)) { + throw new convert_exception('already_locked_path', $data['path']); + } + // set the lock - nothing below the current path will be dispatched + $this->pathlock = $data['path'] . '/'; + + // if the method has returned any info, set element data to it + } else if (!is_null($rdata)) { + $element->set_data($rdata); + + // use just the cooked parsed data otherwise + } else { + $element->set_data($data); + } + } + + /** + * Executes operations required at the start of a watched path + * + * Note that this is called before the MOD and BLOCK paths are expanded + * so the current plugin is not known yet. + * + * @todo dispatch the message to the interested handlers + * @param string $path in the original file + */ + public function path_start_reached($path) { + print_object("start reached: $path"); // DONOTCOMMIT + } + + /** + * Executes operations required at the end of a watched path + * + * @todo dispatch the message to the interested handlers + * @param string $path in the original file + */ + public function path_end_reached($path) { + print_object("end reached: $path"); // DONOTCOMMIT + } +} + + +/** + * XML parser processor + */ +class moodle1_parser_processor extends grouped_parser_processor { + + /** @var moodle1_converter */ + protected $converter; + + public function __construct(moodle1_converter $converter) { + $this->converter = $converter; + parent::__construct(); + } + + /** + * Provide NULL and legacy file.php uses decoding + */ + public function process_cdata($cdata) { + global $CFG; + + if ($cdata === '$@NULL@$') { // Some cases we know we can skip complete processing + return null; + } else if ($cdata === '') { + return ''; + } else if (is_numeric($cdata)) { + return $cdata; + } else if (strlen($cdata) < 32) { // Impossible to have one link in 32cc + return $cdata; // (http://10.0.0.1/file.php/1/1.jpg, http://10.0.0.1/mod/url/view.php?id=) + } else if (strpos($cdata, '$@FILEPHP@$') === false) { // No $@FILEPHP@$, nothing to convert + return $cdata; + } + // Decode file.php calls + $search = array ("$@FILEPHP@$"); + $replace = array(get_file_url($this->courseid)); + $result = str_replace($search, $replace, $cdata); + // Now $@SLASH@$ and $@FORCEDOWNLOAD@$ MDL-18799 + $search = array('$@SLASH@$', '$@FORCEDOWNLOAD@$'); + if ($CFG->slasharguments) { + $replace = array('/', '?forcedownload=1'); + } else { + $replace = array('%2F', '&forcedownload=1'); + } + return str_replace($search, $replace, $result); + } + + /** + * Override this method so we'll be able to skip + * dispatching some well-known chunks, like the + * ones being 100% part of subplugins stuff. Useful + * for allowing development without having all the + * possible restore subplugins defined + */ + protected function postprocess_chunk($data) { + + // Iterate over all the data tags, if any of them is + // not 'subplugin_XXXX' or has value, then it's a valid chunk, + // pass it to standard (parent) processing of chunks. + foreach ($data['tags'] as $key => $value) { + if (trim($value) !== '' || strpos($key, 'subplugin_') !== 0) { + parent::postprocess_chunk($data); + return; + } + } + // Arrived here, all the tags correspond to sublplugins and are empty, + // skip the chunk, and debug_developer notice + $this->chunks--; // not counted + debugging('Missing support on restore for ' . clean_param($data['path'], PARAM_PATH) . + ' subplugin (' . implode(', ', array_keys($data['tags'])) .')', DEBUG_DEVELOPER); + } + + /** + * Dispatches the data chunk to the converter class + * + * @param array $data the chunk of parsed data + */ + protected function dispatch_chunk($data) { + $this->converter->process_chunk($data); + } + + /** + * Informs the converter at the start of a watched path + * + * @param string $path + */ + protected function notify_path_start($path) { + $this->converter->path_start_reached($path); + } + + /** + * Informs the converter at the end of a watched path + * + * @param string $path + */ + protected function notify_path_end($path) { + $this->converter->path_end_reached($path); + } +} + + +/** + * Class representing a path to be converted from XML file + * + * This was created as a copy of {@link restore_path_element} and should be refactored + * probably. + */ +class convert_path { + + /** @var string name of the element */ + protected $name; + + /** @var string path within the XML file this element will handle */ + protected $path; + + /** @var bool flag to define if this element will get child ones grouped or no */ + protected $grouped; + + /** @var object object instance in charge of processing this element. */ + protected $pobject = null; + + /** @var string the name of the processing method */ + protected $pmethod = null; + + /** @var mixed last data read for this element or returned data by processing method */ + protected $data = null; + + /** @var array of deprecated fields that are skipped and not converted */ + protected $skipfields = array(); + + /** @var array of fields renaming */ + protected $renamefields = array(); + + /** @var array of new fields to add and their initial values */ + protected $newfields = array(); + + /** + * Constructor + * + * @param string $name name of the element + * @param string $path path of the element + * @param array $recipe basic description of the structure conversion + * @param bool $grouped to gather information in grouped mode or no + */ + public function __construct($name, $path, array $recipe = array(), $grouped = false) { + + $this->validate_name($name); + + $this->name = $name; + $this->path = $path; + $this->grouped = $grouped; + + // set the default processing method name + $this->set_processing_method('process_' . $name); + + if (isset($recipe['skipfields']) and is_array($recipe['skipfields'])) { + $this->set_skipped_fields($recipe['skipfields']); + } + if (isset($recipe['renamefields']) and is_array($recipe['renamefields'])) { + $this->set_renamed_fields($recipe['renamefields']); + } + if (isset($recipe['newfields']) and is_array($recipe['newfields'])) { + $this->set_new_fields($recipe['newfields']); + } + } + + /** + * Validates and sets the given processing object + * + * @param object $pobject processing object, must provide a method to be called + */ + public function set_processing_object($pobject) { + $this->validate_pobject($pobject); + $this->pobject = $pobject; + } + + /** + * Sets the name of the processing method + * + * @param string $pmethod + */ + public function set_processing_method($pmethod) { + $this->pmethod = $pmethod; + } + + /** + * Sets the element data + * + * @param mixed + */ + public function set_data($data) { + $this->data = $data; + } + + /** + * Sets the list of deprecated fields to skip + * + * @param array $fields + */ + public function set_skipped_fields(array $fields) { + $this->skipfields = $fields; + } + + /** + * Sets the required new names of the current fields + * + * @param array $fields (string)$currentname => (string)$newname + */ + public function set_renamed_fields(array $fields) { + $this->renamefields = $fields; + } + + /** + * Sets the new fields and their values + * + * @param array $fields (string)$field => (mixed)value + */ + public function set_new_fields(array $fields) { + $this->newfields = $fields; + } + + /** + * Cooks the parsed tags data by applying known recipes + * + * Recipes are used for common trivial operations like adding new fields + * or renaming fields. The handler's processing method receives cooked + * data. + * + * @param array $data the contents of the element + * @return array + */ + public function apply_recipes(array $data) { + + $cooked = array(); + + foreach ($data as $name => $value) { + // lower case rocks! + $name = strtolower($name); + + // fields renaming + if (array_key_exists($name, $this->renamefields)) { + $name = $this->renamefields[$name]; + } + + $cooked[$name] = $value; + } + + // adding new fields + foreach ($this->newfields as $name => $value) { + $cooked[$name] = $value; + } + + return $cooked; + } + + /** + * @return string the element given name + */ + public function get_name() { + return $this->name; + } + + /** + * @return string the path to the element + */ + public function get_path() { + return $this->path; + } + + /** + * @return bool flag to define if this element will get child ones grouped or no + */ + public function is_grouped() { + return $this->grouped; + } + + /** + * @return object the processing object providing the processing method + */ + public function get_processing_object() { + return $this->pobject; + } + + /** + * @return string the name of the method to call to process the element + */ + public function get_processing_method() { + return $this->pmethod; + } + + /** + * @return mixed the element data + */ + public function get_data() { + return $this->data; + } + + + /// end of public API ////////////////////////////////////////////////////// + + /** + * Makes sure the given name is a valid element name + * + * Note it may look as if we used exceptions for code flow control here. That's not the case + * as we actually validate the code, not the user data. And the code is supposed to be + * correct. + * + * @param string @name the element given name + * @throws convert_path_exception + * @return void + */ + protected function validate_name($name) { + // Validate various name constraints, throwing exception if needed + if (empty($name)) { + throw new convert_path_exception('convert_path_emptyname', $name); + } + if (preg_replace('/\s/', '', $name) != $name) { + throw new convert_path_exception('convert_path_whitespace', $name); + } + if (preg_replace('/[^\x30-\x39\x41-\x5a\x5f\x61-\x7a]/', '', $name) != $name) { + throw new convert_path_exception('convert_path_notasciiname', $name); + } + } + + /** + * Makes sure that the given object is a valid processing object + * + * The processing object must be an object providing the element's processing method. + * Note it may look as if we used exceptions for code flow control here. That's not the case + * as we actually validate the code, not the user data. And the code is supposed to be + * correct. + * + * @param object $pobject + * @throws convert_path_exception + * @return void + */ + protected function validate_pobject($pobject) { + if (!is_object($pobject)) { + throw new convert_path_exception('convert_path_no_object', $pobject); + } + if (!method_exists($pobject, $this->get_processing_method())) { + throw new convert_path_exception('convert_path_missingmethod', $this->get_processing_method()); + } + } +} + + +/** + * Exception being thrown by {@link convert_path} methods + */ +class convert_path_exception extends moodle_exception { + + /** + * Constructor + * + * @param string $errorcode key for the corresponding error string + * @param mixed $a extra words and phrases that might be required by the error string + * @param string $debuginfo optional debugging information + */ + public function __construct($errorcode, $a = null, $debuginfo = null) { + parent::__construct($errorcode, '', '', $a, $debuginfo); + } +} diff --git a/backup/converter/moodle1/simpletest/testconverter.php b/backup/converter/moodle1/simpletest/testlib.php similarity index 94% rename from backup/converter/moodle1/simpletest/testconverter.php rename to backup/converter/moodle1/simpletest/testlib.php index 7c07530d83fce..526c5a12d72f3 100644 --- a/backup/converter/moodle1/simpletest/testconverter.php +++ b/backup/converter/moodle1/simpletest/testlib.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/backup/converter/moodle1/converter.class.php'); +require_once($CFG->dirroot . '/backup/converter/moodle1/lib.php'); class moodle1_converter_test extends UnitTestCase { @@ -58,7 +58,7 @@ public function test_detect_format() { $this->assertEqual(backup::FORMAT_MOODLE1, $detected); } - public function test_convert() { + public function test_convert_factory() { $converter = convert_factory::converter('moodle1', $this->tempdir); $this->assertIsA($converter, 'moodle1_converter'); $converter->convert(); diff --git a/backup/converter/moodle1/stepslib.php b/backup/converter/moodle1/stepslib.php index 0d4b11b2ba25a..ec7fc96c6cdcc 100644 --- a/backup/converter/moodle1/stepslib.php +++ b/backup/converter/moodle1/stepslib.php @@ -50,7 +50,7 @@ abstract public function get_xml_filename(); */ public function open_xml_writer() { if (!$this->xmlwriter instanceof xml_writer) { - $fullpath = $this->get_basepath().'/'.$this->get_xml_filename(); + $fullpath = $this->get_basepath() . '/' . $this->get_xml_filename(); $directory = pathinfo($fullpath, PATHINFO_DIRNAME); if (!check_dir_exists($directory)) { diff --git a/backup/converter/moodle1/taskslib.php b/backup/converter/moodle1/taskslib.php deleted file mode 100644 index a6dc80d9050c2..0000000000000 --- a/backup/converter/moodle1/taskslib.php +++ /dev/null @@ -1,107 +0,0 @@ -. - -/** - * @package core - * @subpackage backup-convert - * @copyright 2011 Mark Nielsen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); - -class moodle1_root_task extends convert_task { - /** - * Function responsible for building the steps of any task - * (must set the $built property to true) - */ - public function build() { - $this->add_step(new convert_create_and_clean_temp_stuff('create_and_clean_temp_stuff')); - - // At the end, mark it as built - $this->built = true; - } - -} - -/** - * @todo Not used at the moment - */ -class moodle1_final_task extends convert_task { - /** - * Function responsible for building the steps of any task - * (must set the $built property to true) - */ - public function build() { - $this->add_step(new convert_drop_and_clean_temp_stuff('drop_and_clean_temp_stuff')); - - // At the end, mark it as built - $this->built = true; - } -} - -class moodle1_course_task extends convert_task { - /** - * Create all the steps that will be part of this task - */ - public function build() { - - $this->add_step(new moodle1_course_structure_step('course')); - $this->add_step(new moodle1_section_structure_step('course_section')); - $this->add_step(new moodle1_block_structure_step('course_blocks')); - $this->add_step(new moodle1_info_structure_step('info')); - - // At the end, mark it as built - $this->built = true; - } -} - -abstract class moodle1_plugin_task extends convert_task { - /** - * Plugin specific steps - */ - abstract protected function define_my_steps(); -} - -abstract class moodle1_activity_task extends moodle1_plugin_task { - /** - * Function responsible for building the steps of any task - * (must set the $built property to true) - */ - public function build() { - $this->define_my_steps(); - - // @todo Risky? - list($plugin, $name) = explode('_', $this->name); - - $this->add_step(new moodle1_module_structure_step("{$this->name}_module", $name)); - $this->built = true; - } -} - -abstract class moodle1_block_task extends moodle1_plugin_task { - /** - * Function responsible for building the steps of any task - * (must set the $built property to true) - */ - public function build() { - $this->define_my_steps(); - $this->built = true; - } -} diff --git a/backup/moodle2/convert_stepslib.php b/backup/moodle2/convert_stepslib.php deleted file mode 100644 index 7ba711077dfc8..0000000000000 --- a/backup/moodle2/convert_stepslib.php +++ /dev/null @@ -1,51 +0,0 @@ -. - -/** - * @package core - * @subpackage backup-convert - * @copyright 2011 Mark Nielsen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -/** - * Do convert plan related set up - */ -class convert_create_and_clean_temp_stuff extends convert_execution_step { - - protected function define_execution() { - backup_controller_dbops::create_backup_ids_temp_table($this->get_convertid()); // Create ids temp table - } -} - -/** - * Do convert plan related tear down - */ -class convert_drop_and_clean_temp_stuff extends convert_execution_step { - - protected function define_execution() { - // We want to run after execution - } - - public function execute_after_convert() { - backup_controller_dbops::drop_backup_ids_temp_table($this->get_convertid()); // Drop ids temp table - } - - -} diff --git a/backup/util/converter/plan_converter.class.php b/backup/util/converter/plan_converter.class.php deleted file mode 100644 index 3301cc690b73b..0000000000000 --- a/backup/util/converter/plan_converter.class.php +++ /dev/null @@ -1,165 +0,0 @@ -. - -/** - * @package core - * @subpackage backup-convert - * @copyright 2011 Mark Nielsen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -/** - * Base class for all converters using plan/tasks/steps pattern - * - * All converters that use {@link convert_plan} must extend this class. - */ -abstract class planned_converter extends base_converter { - - /** @var convert_plan */ - protected $plan; - /** @var progressive_parser */ - protected $xmlparser; - /** @var convert_structure_parser_processor */ - protected $xmlprocessor; - /** @var array path elements to process */ - protected $pathelements = array(); - /** @todo needed? redo? path currently locking processing of children */ - protected $pathlock; - - /** - * Instructs the dispatcher to ignore all children below path processor returning it - */ - const SKIP_ALL_CHILDREN = -991399; - - /** - * Return the plan instance, instatinate it if it does not exist yet - * - * @return convert_plan - */ - public function get_plan() { - if (!$this->plan instanceof convert_plan) { - $this->plan = new convert_plan($this); - } - return $this->plan; - } - - abstract public function build_plan(); - - public function execute() { - $this->get_plan()->build(); // Ends up calling $this->build_plan() - $this->get_plan()->execute(); - $this->xmlparser->process(); // @todo When to really do this? - $this->get_plan()->execute_after_convert(); - } - - public function destroy() { - parent::destroy(); - $this->get_plan()->destroy(); - } - - // @todo Validation here is weak, probably should validate against a data members that keep track of all names/paths - public function add_structures($processingobject, array $structures) { - // First iteration, push them to new array, indexed by name - // detecting duplicates in names or paths - $names = array(); - $paths = array(); - foreach($structures as $element) { - if (!$element instanceof convert_path_element) { - throw new restore_step_exception('restore_path_element_wrong_class', get_class($element)); // @todo Change exception - } - if (array_key_exists($element->get_name(), $names)) { - throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name()); // @todo Change exception - } - if (array_key_exists($element->get_path(), $paths)) { - throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path()); // @todo Change exception - } - $names[$element->get_name()] = true; - $paths[$element->get_path()] = $element; - } - // Now, for each element not having one processing object, if - // not child of grouped element, assign $this (the step itself) as processing element - // Note method must exist or we'll get one @restore_path_element_exception - foreach($paths as $key => $element) { - if ($element->get_processing_object() === null && !$this->grouped_parent_exists($element, $paths)) { - $paths[$key]->set_processing_object($processingobject); - } - // Add element path to the processor - $this->xmlprocessor->add_path($element->get_path(), $element->is_grouped()); - } - // Done, add them to pathelements (dupes by key - path - are discarded) - $this->pathelements = array_merge($this->pathelements, $paths); - } - - /** - * Given one pathelement, return true if grouped parent was found - */ - protected function grouped_parent_exists($pelement, $elements) { - foreach ($elements as $element) { - if ($pelement->get_path() == $element->get_path()) { - continue; // Don't compare against itself - } - // If element is grouped and parent of pelement, return true - if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) { - return true; - } - } - return false; // no grouped parent found - } - - /** - * Receive one chunk of information form the xml parser processor and - * dispatch it, following the naming rules - */ - public function process($data) { - if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen - throw new restore_step_exception('restore_structure_step_missing_path', $data['path']); // @todo Change exception - } - $element = $this->pathelements[$data['path']]; - $object = $element->get_processing_object(); - $method = $element->get_processing_method(); - $rdata = null; - if (empty($object)) { // No processing object defined - throw new restore_step_exception('restore_structure_step_missing_pobject', $object); // @todo Change exception - } - // Release the lock if we aren't anymore within children of it - if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) { - $this->pathlock = null; - } - if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock - $rdata = $object->$method($data['tags']); // Dispatch to proper object/method - } - - // If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to - // lock dispatching to any children - if ($rdata === self::SKIP_ALL_CHILDREN) { - // Check we haven't any previous lock - if (!is_null($this->pathlock)) { - throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']); // @todo Change exception - } - // Set the lock - $this->pathlock = $data['path'] . '/'; // Lock everything below current path - - // Continue with normal processing of return values - } else if ($rdata !== null) { // If the method has returned any info, set element data to it - $element->set_data($rdata); - } else { // Else, put the original parsed data - $element->set_data($data); - } - } -} diff --git a/backup/util/factories/convert_factory.class.php b/backup/util/factories/convert_factory.class.php index 6dd6bbab19c18..c02825b04a17c 100644 --- a/backup/util/factories/convert_factory.class.php +++ b/backup/util/factories/convert_factory.class.php @@ -35,14 +35,14 @@ abstract class convert_factory { * @throws coding_exception * @param $name The converter name * @param $tempdir The temp directory to operate on - * @return base_converter|plan_converter + * @return base_converter */ public static function converter($name, $tempdir) { global $CFG; $name = clean_param($name, PARAM_SAFEDIR); - $classfile = "$CFG->dirroot/backup/converter/$name/converter.class.php"; + $classfile = "$CFG->dirroot/backup/converter/$name/lib.php"; $classname = "{$name}_converter"; if (!file_exists($classfile)) { @@ -55,52 +55,4 @@ public static function converter($name, $tempdir) { } return new $classname($tempdir); } - - /** - * Runs through all plugins of a specific type and instantiates their task class - * - * @throws coding_exception - * @param string $type The plugin type - * @param string $format The convert format - * @param string $extra Extra naming structure - * @return array - */ - public static function get_plugin_tasks($type, $format, $extra = NULL) { - global $CFG; // REQUIRED by task file includes - - if (is_null($extra)) { - $extra = $type; - } - $tasks = array(); - $plugins = get_plugin_list($type); - foreach ($plugins as $name => $dir) { - $taskfile = "$dir/backup/$format/convert_{$name}_{$extra}_task.class.php"; - $taskclass = "{$format}_{$name}_{$extra}_task"; - if (!file_exists($taskfile)) { - continue; - } - require_once($taskfile); - - if (!class_exists($taskclass)) { - throw new coding_exception("The class name should be $taskclass in $taskfile"); - } - $tasks[] = new $taskclass("{$type}_$name"); - } - return $tasks; - } - - /** - * Adds all of the plugin tasks to the given converter's plan - * - * @param plan_converter $converter The converter to add the plugin tasks to - * @param string $type The plugin type - * @param string $extra Extra naming structure - * @return void - */ - public static function build_plugin_tasks(plan_converter $converter, $type, $extra = NULL) { - $tasks = self::get_plugin_tasks($type, $converter->get_name(), $extra); - foreach ($tasks as $task) { - $converter->get_plan()->add_task($task); - } - } } diff --git a/backup/util/helper/backup_general_helper.class.php b/backup/util/helper/backup_general_helper.class.php index 4fd2d082e6b91..f0891f67c1a8c 100644 --- a/backup/util/helper/backup_general_helper.class.php +++ b/backup/util/helper/backup_general_helper.class.php @@ -235,7 +235,8 @@ public static function backup_is_samesite($info) { */ public static function detect_backup_format($tempdir) { global $CFG; - require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); + require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php'); + require_once($CFG->dirroot . '/backup/util/factories/convert_factory.class.php'); if (convert_helper::detect_moodle2_format($tempdir)) { return backup::FORMAT_MOODLE; diff --git a/backup/util/helper/convert_helper.class.php b/backup/util/helper/convert_helper.class.php index 86184618cdf6f..8e6347a8fdfd5 100644 --- a/backup/util/helper/convert_helper.class.php +++ b/backup/util/helper/convert_helper.class.php @@ -16,7 +16,7 @@ // along with Moodle. If not, see . /** - * Provides {@link convert_helper} class + * Provides {@link convert_helper} and {@link convert_helper_exception} classes * * @package core * @subpackage backup-convert @@ -26,6 +26,8 @@ defined('MOODLE_INTERNAL') || die(); +require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); + /** * Provides various functionality via its static methods */ @@ -54,16 +56,16 @@ public static function available_converters() { $converters = array(); $plugins = get_list_of_plugins('backup/converter'); foreach ($plugins as $name) { - $classfile = "$CFG->dirroot/backup/converter/$name/converter.class.php"; + $classfile = "$CFG->dirroot/backup/converter/$name/lib.php"; $classname = "{$name}_converter"; if (!file_exists($classfile)) { - throw new coding_exception("Converter factory error: class file not found $classfile"); + throw new convert_helper_exception('converter_classfile_not_found', $classfile); } require_once($classfile); if (!class_exists($classname)) { - throw new coding_exception("Converter factory error: class not found $classname"); + throw new convert_helper_exception('converter_classname_not_found', $classname); } if (call_user_func($classname .'::is_available')) { @@ -87,7 +89,7 @@ public static function detect_moodle2_format($tempdir) { $filepath = $dirpath . '/moodle_backup.xml'; if (!is_dir($dirpath)) { - throw new backup_helper_exception('tmp_backup_directory_not_found', $dirpath); + throw new converter_helper_exception('tmp_backup_directory_not_found', $dirpath); } if (!file_exists($filepath)) { @@ -110,10 +112,10 @@ public static function detect_moodle2_format($tempdir) { /** * Converts the given directory with the backup into moodle2 format * - * @throws coding_exception|restore_controller_exception * @param string $tempdir The directory to convert * @param string $format The current format, if already detected - * @return void + * @throws convert_helper_exception + * @return bool false if unable to find the conversion path, true otherwise */ public static function to_moodle2_format($tempdir, $format = null) { @@ -127,8 +129,7 @@ public static function to_moodle2_format($tempdir, $format = null) { foreach ($converters as $name) { $classname = "{$name}_converter"; if (!class_exists($classname)) { - throw new coding_exception("available_converters() is supposed to load - converter classes but class $classname not found"); + throw new convert_helper_exception('class_not_loaded', $classname); } $descriptions[$name] = call_user_func($classname .'::description'); } @@ -138,8 +139,7 @@ public static function to_moodle2_format($tempdir, $format = null) { if (empty($path)) { // unable to convert - // todo throwing exception is not a good way to control the flow here - throw new coding_exception('Unable to find conversion path'); + return false; } foreach ($path as $name) { @@ -149,8 +149,10 @@ public static function to_moodle2_format($tempdir, $format = null) { // make sure we ended with moodle2 format if (!self::detect_moodle2_format($tempdir)) { - throw new coding_exception('Conversion failed'); + throw new convert_helper_exception('conversion_failed'); } + + return true; } /** @@ -177,8 +179,6 @@ public static function obj_to_readable($obj) { /** * Generate an artificial context ID * - * @static - * @throws Exception * @param int $instance The moodle component instance ID, same value used for get_context_instance() * @param string $component The moodle component, like block_html, mod_quiz, etc * @param string $converterid The converter ID @@ -191,15 +191,14 @@ public static function get_contextid($instance, $component = 'moodle', $converte // Attempt to retrieve the contextid $contextid = $DB->get_field_select('backup_ids_temp', 'id', - $DB->sql_compare_text('info', 100).' = ? AND itemid = ? AND itemname = ?', - array($component, $instance, 'context') - ); + $DB->sql_compare_text('info', 100).' = '.$DB->sql_compare_text('?', 100).' AND itemid = ? AND itemname = ?', + array($component, $instance, 'context')); if (!empty($contextid)) { return $contextid; } - $context = new stdClass; + $context = new stdClass(); $context->itemid = $instance; $context->itemname = 'context'; $context->info = $component; @@ -211,7 +210,7 @@ public static function get_contextid($instance, $component = 'moodle', $converte return $id; } else { $msg = self::obj_to_readable($context); - throw new Exception(sprintf("Could not insert context record into temp table: %s", $msg)); + throw new convert_helper_exception('failed_insert_record', $msg); } } @@ -229,6 +228,7 @@ public static function get_contextid($instance, $component = 'moodle', $converte * the oriented graph. * * @see http://en.wikipedia.org/wiki/Dijkstra's_algorithm + * @author David Mudrak * @param string $format the source backup format, one of backup::FORMAT_xxx * @param array $descriptions list of {@link base_converter::description()} indexed by the converter name * @return array ordered list of converter names to call (may be empty if not reachable) @@ -246,7 +246,7 @@ protected static function choose_conversion_path($format, array $descriptions) { if (is_null($from) or $from === backup::FORMAT_UNKNOWN or is_null($to) or $to === backup::FORMAT_UNKNOWN or is_null($cost) or $cost <= 0) { - throw new coding_exception('Invalid converter description:' . $converter); + throw new convert_helper_exception('invalid_converter_description', $converter); } if (!isset($paths[$from][$to])) { @@ -354,3 +354,22 @@ protected static function choose_conversion_path($format, array $descriptions) { return $conversionpath; } } + +/** + * General convert_helper related exception + * + * @author David Mudrak + */ +class convert_helper_exception extends moodle_exception { + + /** + * Constructor + * + * @param string $errorcode key for the corresponding error string + * @param object $a extra words and phrases that might be required in the error string + * @param string $debuginfo optional debugging information + */ + public function __construct($errorcode, $a = null, $debuginfo = null) { + parent::__construct($errorcode, '', '', $a, $debuginfo); + } +} diff --git a/backup/util/helper/convert_structure_parser_processor.class.php b/backup/util/helper/convert_structure_parser_processor.class.php deleted file mode 100644 index 9d4f5ced5f2af..0000000000000 --- a/backup/util/helper/convert_structure_parser_processor.class.php +++ /dev/null @@ -1,104 +0,0 @@ -. - -/** - * @package core - * @subpackage backup-convert - * @copyright 2011 Mark Nielsen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -class convert_structure_parser_processor extends grouped_parser_processor { - /** - * @var plan_converter - */ - protected $converter; - - public function __construct(plan_converter $converter) { - $this->converter = $converter; - parent::__construct(); - } - - /** - * Provide NULL and legacy file.php uses decoding - */ - public function process_cdata($cdata) { - global $CFG; - if ($cdata === '$@NULL@$') { // Some cases we know we can skip complete processing - return null; - } else if ($cdata === '') { - return ''; - } else if (is_numeric($cdata)) { - return $cdata; - } else if (strlen($cdata) < 32) { // Impossible to have one link in 32cc - return $cdata; // (http://10.0.0.1/file.php/1/1.jpg, http://10.0.0.1/mod/url/view.php?id=) - } else if (strpos($cdata, '$@FILEPHP@$') === false) { // No $@FILEPHP@$, nothing to convert - return $cdata; - } - // Decode file.php calls - $search = array ("$@FILEPHP@$"); - $replace = array(get_file_url($this->courseid)); - $result = str_replace($search, $replace, $cdata); - // Now $@SLASH@$ and $@FORCEDOWNLOAD@$ MDL-18799 - $search = array('$@SLASH@$', '$@FORCEDOWNLOAD@$'); - if ($CFG->slasharguments) { - $replace = array('/', '?forcedownload=1'); - } else { - $replace = array('%2F', '&forcedownload=1'); - } - return str_replace($search, $replace, $result); - } - - /** - * Override this method so we'll be able to skip - * dispatching some well-known chunks, like the - * ones being 100% part of subplugins stuff. Useful - * for allowing development without having all the - * possible restore subplugins defined - */ - protected function postprocess_chunk($data) { - - // Iterate over all the data tags, if any of them is - // not 'subplugin_XXXX' or has value, then it's a valid chunk, - // pass it to standard (parent) processing of chunks. - foreach ($data['tags'] as $key => $value) { - if (trim($value) !== '' || strpos($key, 'subplugin_') !== 0) { - parent::postprocess_chunk($data); - return; - } - } - // Arrived here, all the tags correspond to sublplugins and are empty, - // skip the chunk, and debug_developer notice - $this->chunks--; // not counted - debugging('Missing support on restore for ' . clean_param($data['path'], PARAM_PATH) . - ' subplugin (' . implode(', ', array_keys($data['tags'])) .')', DEBUG_DEVELOPER); - } - - protected function dispatch_chunk($data) { - $this->converter->process($data); - } - - protected function notify_path_start($path) { - // Do nothing - } - - protected function notify_path_end($path) { - // Do nothing - } -} diff --git a/backup/util/helper/simpletest/testconverthelper.php b/backup/util/helper/simpletest/testconverthelper.php index 90b83d57a230f..c198239b33f60 100644 --- a/backup/util/helper/simpletest/testconverthelper.php +++ b/backup/util/helper/simpletest/testconverthelper.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot . '/backup/util/includes/convert_includes.php'); +require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php'); /** * Provides access to the protected methods we need to test diff --git a/backup/util/includes/convert_includes.php b/backup/util/includes/convert_includes.php index 6f84ec861089f..5bc430f67e005 100644 --- a/backup/util/includes/convert_includes.php +++ b/backup/util/includes/convert_includes.php @@ -16,6 +16,8 @@ // along with Moodle. If not, see . /** + * Makes sure that all general code needed by backup-convert code is included + * * @package core * @subpackage backup-convert * @copyright 2011 Mark Nielsen @@ -24,35 +26,8 @@ defined('MOODLE_INTERNAL') || die(); -// Include all the convert stuff needed -require_once($CFG->dirroot.'/backup/util/interfaces/checksumable.class.php'); -require_once($CFG->dirroot.'/backup/util/interfaces/executable.class.php'); -require_once($CFG->dirroot.'/backup/util/interfaces/loggable.class.php'); -require_once($CFG->dirroot.'/backup/backup.class.php'); -require_once($CFG->dirroot.'/backup/util/xml/xml_writer.class.php'); -require_once($CFG->dirroot.'/backup/util/xml/output/xml_output.class.php'); -require_once($CFG->dirroot.'/backup/util/xml/output/file_xml_output.class.php'); -require_once($CFG->dirroot.'/backup/util/dbops/backup_dbops.class.php'); -require_once($CFG->dirroot.'/backup/util/dbops/backup_controller_dbops.class.php'); -require_once($CFG->dirroot.'/backup/util/factories/convert_factory.class.php'); -require_once($CFG->dirroot.'/backup/util/converter/base_converter.class.php'); -require_once($CFG->dirroot.'/backup/util/converter/plan_converter.class.php'); -require_once($CFG->dirroot.'/backup/util/helper/convert_helper.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/base_plan.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/base_step.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/base_task.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_plan.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_step.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_task.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_structure_step.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_execution_step.class.php'); -require_once($CFG->dirroot.'/backup/util/structure/restore_path_element.class.php'); -require_once($CFG->dirroot.'/backup/util/structure/convert_path_element.class.php'); -require_once($CFG->dirroot.'/backup/util/plan/convert_execution_step.class.php'); -require_once($CFG->dirroot.'/backup/util/xml/parser/processors/grouped_parser_processor.class.php'); -require_once($CFG->dirroot.'/backup/util/helper/convert_structure_parser_processor.class.php'); -require_once($CFG->dirroot.'/backup/moodle2/convert_stepslib.php'); -require_once($CFG->dirroot.'/backup/util/xml/parser/progressive_parser.class.php'); - -// And some moodle stuff too -require_once($CFG->libdir.'/filelib.php'); +require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php'); // req by backup.class.php +require_once($CFG->dirroot . '/backup/backup.class.php'); // provides backup::FORMAT_xxx constants +require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php'); +require_once($CFG->dirroot . '/backup/util/factories/convert_factory.class.php'); +require_once($CFG->libdir . '/filelib.php'); diff --git a/backup/util/structure/convert_path_element.class.php b/backup/util/structure/convert_path_element.class.php deleted file mode 100644 index dec3ef3699138..0000000000000 --- a/backup/util/structure/convert_path_element.class.php +++ /dev/null @@ -1,31 +0,0 @@ -. - -/** - * @package core - * @subpackage backup-convert - * @copyright 2011 Mark Nielsen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -class convert_path_element extends restore_path_element { - public function get_processing_method() { - return 'convert_' . $this->get_name(); - } -} diff --git a/mod/forum/backup/moodle1/convert_forum_activity_task.class.php b/mod/forum/backup/moodle1/convert_forum_activity_task.class.php deleted file mode 100644 index 0cebed6752508..0000000000000 --- a/mod/forum/backup/moodle1/convert_forum_activity_task.class.php +++ /dev/null @@ -1,42 +0,0 @@ -. - -/** - * Provides support for the conversion of moodle1 backup to the moodle2 format - * - * @package mod - * @subpackage forum - * @copyright 2011 Mark Nielsen - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot.'/mod/forum/backup/moodle1/convert_forum_stepslib.php'); - -/** - * Convert from Moodle 1 forum task - */ -class moodle1_forum_activity_task extends moodle1_activity_task { - /** - * Function responsible for building the steps of any task - * (must set the $built property to true) - */ - public function define_my_steps() { - $this->add_step(new moodle1_forum_activity_structure_step('forum')); - } -} diff --git a/mod/forum/backup/moodle1/convert_forum_stepslib.php b/mod/forum/backup/moodle1/lib.php similarity index 54% rename from mod/forum/backup/moodle1/convert_forum_stepslib.php rename to mod/forum/backup/moodle1/lib.php index acc2acaba1515..989284b0adcfc 100644 --- a/mod/forum/backup/moodle1/convert_forum_stepslib.php +++ b/mod/forum/backup/moodle1/lib.php @@ -27,29 +27,38 @@ defined('MOODLE_INTERNAL') || die(); /** - * Convert forum + * Forum conversion handler */ -class moodle1_forum_activity_structure_step extends convert_structure_step { +class moodle1_mod_forum_handler extends moodle1_mod_handler { /** - * Function that will return the structure to be processed by this convert_step. - * Must return one array of @convert_path_element elements + * Declare the paths in moodle.xml we are able to convert * - * NOTE: /MOD/ACTIVITYNAME XML path does not actually exist. The moodle1_converter - * class automatically transforms the /MOD path to include the activity name. + * The method returns list of {@link convert_path} instances. + * For each path returned, the corresponding conversion method must be + * defined. + * + * Note that the paths /MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM do not + * actually exist in the file. The last element with the module name was + * appended by the moodle1_converter class. + * + * @return array of {@link convert_path} instances */ - protected function define_structure() { + public function get_paths() { return array( - new convert_path_element('forum', '/MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM'), - // new convert_path_element('foo', '/MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM/FOO'), // Example of sub-path + new convert_path('forum', '/MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM'), + // new convert_path('foo', '/MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM/FOO'), // Example of sub-path ); } - public function convert_forum($data) { + /** + * Converts /MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM data + */ + public function process_forum($data) { print_object($data); } - public function convert_foo($data) { + public function process_foo($data) { print_object($data); } }