}
/**
- * convert from current format to backup::MOODLE format
+ * Converts from current format to backup::MOODLE format
*/
public function convert() {
+ global $CFG;
+ 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');
}
- if ($this->format == backup::FORMAT_UNKNOWN) {
- throw new restore_controller_exception('cannot_convert_from_unknown_format');
- }
- if ($this->format == backup::FORMAT_MOODLE1) {
- // TODO: Implement moodle1 => moodle2 conversion
- throw new restore_controller_exception('cannot_convert_yet_from_moodle1_format');
+
+ // Run conversion to the proper 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');
}
- // Once conversions have finished, we check again the format
- $newformat = backup_general_helper::detect_backup_format($tempdir);
+ // If no exceptions were thrown, then we are in the proper format
+ $this->format = backup::FORMAT_MOODLE;
- // If format is moodle2, load plan, apply security and set status based on interactivity
- if ($newformat === backup::FORMAT_MOODLE) {
- // Load plan
- $this->load_plan();
+ // Load plan, apply security and set status based on interactivity
+ $this->load_plan();
- // Perform all initial security checks and apply (2nd param) them to settings automatically
- restore_check::check_security($this, true);
+ // Perform all initial security checks and apply (2nd param) them to settings automatically
+ restore_check::check_security($this, true);
- if ($this->interactive == backup::INTERACTIVE_YES) {
- $this->set_status(backup::STATUS_SETTING_UI);
- } else {
- $this->set_status(backup::STATUS_NEED_PRECHECK);
- }
+ if ($this->interactive == backup::INTERACTIVE_YES) {
+ $this->set_status(backup::STATUS_SETTING_UI);
} else {
- throw new restore_controller_exception('conversion_ended_with_wrong_format', $newformat);
+ $this->set_status(backup::STATUS_NEED_PRECHECK);
}
}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides base converter classes
+ *
+ * @package core
+ * @subpackage backup-convert
+ * @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
+ * @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');
+
+/**
+ * Base converter class
+ *
+ * All Moodle backup converters are supposed to extend this base class.
+ *
+ * @throws convert_exception
+ */
+abstract class base_converter {
+
+ /** @var string unique identifier of this converter instance */
+ protected $id;
+ /** @var string the name of the directory containing the unpacked backup being converted */
+ protected $tempdir;
+ /** @var string the name of the directory where the backup is converted to */
+ protected $workdir;
+
+ /**
+ * Constructor
+ *
+ * @param string $tempdir the relative path to the directory containing the unpacked backup to convert
+ */
+ public function __construct($tempdir) {
+
+ $this->tempdir = $tempdir;
+ $this->id = convert_helper::generate_id($this->workdir);
+ $this->workdir = $tempdir . '_' . $this->get_name() . '_' . $this->id;
+ $this->init();
+ }
+
+ /**
+ * Get instance identifier
+ *
+ * @return string the unique identifier of this converter instance
+ */
+ public function get_id() {
+ return $this->id;
+ }
+
+ /**
+ * Get converter name
+ *
+ * @return string the system name of the converter
+ */
+ public function get_name() {
+ return array_shift(explode('_', get_class($this)));
+ }
+
+ /**
+ * Converts the backup directory
+ */
+ public function convert() {
+
+ try {
+ $this->create_workdir();
+ $this->execute();
+ $this->replace_tempdir();
+ } catch (Exception $e) {
+ }
+
+ // clean-up stuff if needed
+ $this->destroy();
+
+ // eventually re-throw the execution exception
+ if (isset($e) and ($e instanceof Exception)) {
+ throw $e;
+ }
+ }
+
+ /// public static methods //////////////////////////////////////////////////
+
+ /**
+ * Makes sure that this converter is available at this site
+ *
+ * This is intended for eventual PHP extensions check, environment check etc.
+ * All checks that do not depend on actual backup data should be done here.
+ *
+ * @return boolean true if this converter should be considered as available
+ */
+ public static function is_available() {
+ return true;
+ }
+
+ /**
+ * Detects the format of the backup directory
+ *
+ * Moodle 2.x format is being detected by the core itself. The converters are
+ * therefore supposed to detect the source format. Eventually, if the target
+ * format os not {@link backup::FORMAT_MOODLE} then they should be able to
+ * detect both source and target formats.
+ *
+ * @param string $tempdir the name of the backup directory
+ * @return null|string null if not recognized, backup::FORMAT_xxx otherwise
+ */
+ public static function detect_format($tempdir) {
+ return null;
+ }
+
+ /**
+ * Returns the basic information about the converter
+ *
+ * The returned array must contain the following keys:
+ * 'from' - the supported source format, eg. backup::FORMAT_MOODLE1
+ * 'to' - the supported target format, eg. backup::FORMAT_MOODLE
+ * 'cost' - the cost of the conversion, non-negative non-zero integer
+ */
+ public static function description() {
+
+ return array(
+ 'from' => null,
+ 'to' => null,
+ 'cost' => null,
+ );
+ }
+
+ /**
+ * @return string the full path to the working directory
+ */
+ public function get_workdir_path() {
+ global $CFG;
+
+ return "$CFG->dataroot/temp/backup/$this->workdir";
+ }
+
+ /**
+ * @return string the full path to the directory with the source backup
+ */
+ 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
+ */
+ protected function create_workdir() {
+
+ fulldelete($this->get_workdir_path());
+ if (!check_dir_exists($this->get_workdir_path())) {
+ throw new convert_exception('failed_create_workdir');
+ }
+ }
+
+ /**
+ * Replaces the source backup directory with the converted version
+ *
+ * If $CFG->keeptempdirectoriesonbackup is defined, the original source
+ * source backup directory is kept for debugging purposes.
+ */
+ protected function replace_tempdir() {
+ global $CFG;
+
+ if (empty($CFG->keeptempdirectoriesonbackup)) {
+ fulldelete($this->get_tempdir_path());
+ } else {
+ 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 convert_exception('failed_move_converted_into_place');
+ }
+ }
+
+ /**
+ * Cleans up stuff after the execution
+ *
+ * Note that we do not know if the execution was successful or not.
+ * An exception might have been thrown.
+ */
+ protected function destroy() {
+ global $CFG;
+
+ if (empty($CFG->keeptempdirectoriesonbackup)) {
+ fulldelete($this->get_workdir_path());
+ }
+ }
+}
+
+/**
+ * General convert-related exception
+ *
+ * @author David Mudrak <david@moodle.com>
+ */
+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);
+ }
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 <david@moodle.com>
+ * @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');
+
+/**
+ * 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(
+ new moodle1_root_handler($converter),
+ new moodle1_info_handler($converter),
+ new moodle1_course_header_handler($converter),
+ new moodle1_course_outline_handler($converter),
+ );
+
+ $handlers = array_merge($handlers, self::get_plugin_handlers('mod', $converter));
+ $handlers = array_merge($handlers, self::get_plugin_handlers('block', $converter));
+
+ // make sure that all handlers have expected class
+ foreach ($handlers as $handler) {
+ if (!$handler instanceof moodle1_handler) {
+ throw new convert_exception('wrong_handler_class', get_class($handler));
+ }
+ }
+
+ 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
+ */
+ protected 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;
+ }
+}
+
+
+/**
+ * Base backup conversion handler
+ */
+abstract class moodle1_handler {
+
+ /** @var moodle1_converter */
+ protected $converter;
+
+ /**
+ * @param moodle1_converter $converter the converter that requires us
+ */
+ public function __construct(moodle1_converter $converter) {
+ $this->converter = $converter;
+ }
+
+ /**
+ * @return moodle1_converter the converter that required this handler
+ */
+ public function get_converter() {
+ return $this->converter;
+ }
+}
+
+
+/**
+ * Base backup conversion handler that generates an XML file
+ */
+abstract class moodle1_xml_handler extends moodle1_handler {
+
+ /** @var null|string the name of file we are writing to */
+ protected $xmlfilename;
+
+ /** @var null|xml_writer */
+ protected $xmlwriter;
+
+ /**
+ * Opens the XML writer - after calling, one is free to use $xmlwriter
+ *
+ * @param string $filename XML file name to write into
+ * @return void
+ */
+ public function open_xml_writer($filename) {
+
+ if (!is_null($this->xmlfilename) and $filename !== $this->xmlfilename) {
+ throw new convert_exception('xml_writer_already_opened_for_other_file', $this->xmlfilename);
+ }
+
+ if (!$this->xmlwriter instanceof xml_writer) {
+ $this->xmlfilename = $filename;
+ $fullpath = $this->converter->get_workdir_path() . '/' . $this->xmlfilename;
+ $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;
+ $this->xmlfilename = null;
+ }
+
+ /**
+ * Writes the given XML tree data into the currently opened file
+ *
+ * @param string $element the name of the root element of the tree
+ * @param array $data the associative array of data to write
+ * @param array $attribs list of additional fields written as attributes instead of nested elements (all 'id' are there automatically)
+ * @param string $parent used internally during the recursion, do not set yourself
+ */
+ public function write_xml($element, array $data, array $attribs = array(), $parent = '/') {
+
+ $mypath = $parent . $element;
+ $myattribs = array();
+
+ // detect properties that should be rendered as element's attributes instead of children
+ foreach ($data as $name => $value) {
+ if (!is_array($value)) {
+ if ($name === 'id' or in_array($mypath . '/' . $name, $attribs)) {
+ $myattribs[$name] = $value;
+ unset($data[$name]);
+ }
+ }
+ }
+
+ // reorder the $data so that all sub-branches are at the end (needed by our parser)
+ $leaves = array();
+ $branches = array();
+ foreach ($data as $name => $value) {
+ if (is_array($value)) {
+ $branches[$name] = $value;
+ } else {
+ $leaves[$name] = $value;
+ }
+ }
+ $data = array_merge($leaves, $branches);
+
+ $this->xmlwriter->begin_tag($element, $myattribs);
+
+ foreach ($data as $name => $value) {
+ if (is_array($value)) {
+ // recursively call self
+ $this->write_xml($name, $value, $attribs, $mypath);
+ } else {
+ $this->xmlwriter->full_tag($name, $value);
+ }
+ }
+
+ $this->xmlwriter->end_tag($element);
+ }
+}
+
+
+/**
+ * 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) {
+ }
+
+ /**
+ * This is executed at the very start of the moodle.xml parsing
+ */
+ public function on_root_element_start() {
+ }
+
+ /**
+ * This is executed at the end of the moodle.xml parsing
+ */
+ public function on_root_element_end() {
+ }
+}
+
+
+/**
+ * Handles the conversion of /MOODLE_BACKUP/INFO paths
+ *
+ * We do not produce any XML file here, just storing the data in the temp
+ * table so thay can be used by a later handler.
+ */
+class moodle1_info_handler extends moodle1_handler {
+
+ public function get_paths() {
+ return array(
+ new convert_path('info', '/MOODLE_BACKUP/INFO'),
+ new convert_path('info_details', '/MOODLE_BACKUP/INFO/DETAILS'),
+ new convert_path('info_details_mod', '/MOODLE_BACKUP/INFO/DETAILS/MOD'),
+ new convert_path('info_details_mod_instance', '/MOODLE_BACKUP/INFO/DETAILS/MOD/INSTANCES/INSTANCE'),
+ );
+ }
+
+ public function process_info($data) {
+ }
+
+ public function process_info_details($data) {
+ }
+
+ public function process_info_details_mod($data) {
+ }
+
+ public function process_info_details_mod_instance($data) {
+ }
+}
+
+
+/**
+ * Handles the conversion of /MOODLE_BACKUP/COURSE/HEADER paths
+ */
+class moodle1_course_header_handler extends moodle1_xml_handler {
+
+ /** @var array we need to merge course information because it is dispatched twice */
+ protected $course = array();
+
+ /** @var array we need to merge course information because it is dispatched twice */
+ protected $courseraw = array();
+
+ /** @var array */
+ protected $category;
+
+ public function get_paths() {
+ return array(
+ new convert_path(
+ 'course_header', '/MOODLE_BACKUP/COURSE/HEADER',
+ array(
+ 'newfields' => array(
+ 'summaryformat' => 1,
+ 'legacyfiles' => 1, // @todo is this correct?
+ 'requested' => 0, // @todo not really new, but maybe never backed up?
+ 'restrictmodules' => 0,
+ 'enablecompletion' => 0,
+ 'completionstartonenrol' => 0,
+ 'completionnotify' => 0,
+ 'tags' => array(),
+ 'allowed_modules' => array(),
+ ),
+ 'dropfields' => array(
+ 'roles_overrides',
+ 'roles_assignments',
+ 'cost',
+ 'currancy',
+ 'defaultrole',
+ 'enrol',
+ 'enrolenddate',
+ 'enrollable',
+ 'enrolperiod',
+ 'enrolstartdate',
+ 'expirynotify',
+ 'expirythreshold',
+ 'guest',
+ 'notifystudents',
+ 'password',
+ 'student',
+ 'students',
+ 'teacher',
+ 'teachers',
+ 'metacourse',
+ )
+ )
+ ),
+ new convert_path(
+ 'course_header_category', '/MOODLE_BACKUP/COURSE/HEADER/CATEGORY',
+ array(
+ 'newfields' => array(
+ 'description' => null,
+ )
+ )
+ ),
+ );
+ }
+
+ /**
+ * Because there is the CATEGORY branch in the middle of the COURSE/HEADER
+ * branch, this is dispatched twice. We use $this->coursecooked to merge
+ * the result. Once the parser is fixed, it can be refactored.
+ */
+ public function process_course_header($data, $raw) {
+ $this->course = array_merge($this->course, $data);
+ $this->courseraw = array_merge($this->courseraw, $raw);
+ }
+
+ public function process_course_header_category($data) {
+ $this->category = $data;
+ }
+
+ public function on_course_header_end() {
+
+ $contextid = $this->converter->get_contextid(CONTEXT_COURSE, $this->course['id']);
+
+ // stash the information needed by other handlers
+ $info = array(
+ 'original_course_id' => $this->course['id'],
+ 'original_course_fullname' => $this->course['fullname'],
+ 'original_course_shortname' => $this->course['shortname'],
+ 'original_course_startdate' => $this->course['startdate'],
+ 'original_course_contextid' => $contextid
+ );
+ $this->converter->set_stash('original_course_info', $info);
+
+ $this->course['contextid'] = $contextid;
+ $this->course['category'] = $this->category;
+
+ $this->open_xml_writer('course/course.xml');
+ $this->write_xml('course', $this->course, array('/course/contextid'));
+ $this->close_xml_writer();
+ }
+}
+
+
+/**
+ * Handles the conversion of course sections and course modules
+ */
+class moodle1_course_outline_handler extends moodle1_xml_handler {
+
+ /** @var array current section data */
+ protected $currentsection;
+
+ /**
+ * This handler is interested in course sections and course modules within them
+ */
+ public function get_paths() {
+ return array(
+ new convert_path(
+ 'course_section', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION',
+ array(
+ 'newfields' => array(
+ 'name' => null,
+ 'summaryformat' => 1,
+ 'sequence' => null,
+ ),
+ )
+ ),
+ new convert_path(
+ 'course_module', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD',
+ array(
+ 'newfields' => array(
+ 'completion' => 0,
+ 'completiongradeitemnumber' => null,
+ 'completionview' => 0,
+ 'completionexpected' => 0,
+ 'availablefrom' => 0,
+ 'availableuntil' => 0,
+ 'showavailability' => 0,
+ 'availability_info' => array(),
+ 'visibleold' => 1,
+ ),
+ 'dropfields' => array(
+ 'instance',
+ 'roles_overrides',
+ 'roles_assignments',
+ ),
+ 'renamefields' => array(
+ 'type' => 'modulename',
+ ),
+ )
+ )
+ );
+ }
+
+ public function process_course_section($data) {
+ $this->currentsection = $data;
+ }
+
+ /**
+ * Populates the section sequence field (order of course modules) and stashes the
+ * course module info so that is can be dumped to activities/xxxx_x/module.xml later
+ */
+ public function process_course_module($data, $raw) {
+ global $CFG;
+
+ // add the course module id into the section's sequence
+ if (is_null($this->currentsection['sequence'])) {
+ $this->currentsection['sequence'] = $data['id'];
+ } else {
+ $this->currentsection['sequence'] .= ',' . $data['id'];
+ }
+
+ // add the sectionid and sectionnumber
+ $data['sectionid'] = $this->currentsection['id'];
+ $data['sectionnumber'] = $this->currentsection['number'];
+
+ // generate the module version - this is a bit tricky as this information
+ // is not present in 1.9 backups. we will use the currently installed version
+ // whenever we can but that might not be accurate for some modules.
+ // also there might be problem with modules that are not present at the target
+ // host...
+ $versionfile = $CFG->dirroot.'/mod/'.$data['modulename'].'/version.php';
+ if (file_exists($versionfile)) {
+ include($versionfile);
+ $data['version'] = $module->version;
+ } else {
+ $data['version'] = null;
+ }
+
+ // stash the course module info in stashes like 'cminfo_forum' with
+ // itemid set to the instance id. this is needed so that module handlers
+ // can later obtain information about the course module.
+ $this->converter->set_stash('cminfo_'.$data['modulename'], $data, $raw['INSTANCE']);
+
+ // write the module.xml file
+ $this->open_xml_writer('activities/'.$data['modulename'].'_'.$data['id'].'/module.xml');
+ $this->write_xml('module', $data, array('/module/version'));
+ $this->close_xml_writer();
+ }
+
+ /**
+ * Writes sections/section_xxx/section.xml file
+ */
+ public function on_course_section_end() {
+
+ $this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/section.xml');
+ $this->write_xml('section', $this->currentsection);
+ $this->close_xml_writer();
+ unset($this->currentsection);
+ }
+}
+
+
+/**
+ * Shared base class for activity modules and blocks handlers
+ */
+abstract class moodle1_plugin_handler extends moodle1_xml_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 {
+
+ /**
+ * Returns course module id for the given instance id
+ *
+ * The mapping from instance id to course module id has been stashed by
+ * {@link moodle1_course_outline_handler::process_course_module()}
+ *
+ * @param int $instance the module instance id
+ * @return int
+ */
+ protected function get_moduleid($instance) {
+
+ $stashed = $this->converter->get_stash('cminfo_'.$this->pluginname, $instance);
+ return $stashed['id'];
+ }
+}
+
+
+/**
+ * Base class for activity module handlers
+ */
+abstract class moodle1_block_handler extends moodle1_plugin_handler {
+
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides classes used by the moodle1 converter
+ *
+ * @package backup-convert
+ * @subpackage moodle1
+ * @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
+ * @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($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/dbops/restore_dbops.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,'<?xml version="1.0" encoding="UTF-8"?>') !== false and
+ strpos($first_chars,'<MOODLE_BACKUP>') !== false and
+ strpos($first_chars,'<INFO>') !== 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) {
+ $this->register_handler($handler, $handler->get_paths());
+ }
+ }
+
+ /**
+ * Converts the contents of the tempdir into the target format in the workdir
+ */
+ protected function execute() {
+ $this->create_stash_storage();
+ $this->xmlparser->process();
+ $this->drop_stash_storage();
+ }
+
+ /**
+ * 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();
+ $returned = 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)) {
+ $rawdatatags = $data['tags'];
+ $data['tags'] = $element->apply_recipes($data['tags']);
+ $returned = $object->$method($data['tags'], $rawdatatags);
+ }
+
+ // if the dispatched method returned SKIP_ALL_CHILDREN, remember the current path
+ // and lock it so that its children are not dispatched
+ if ($returned === 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($returned)) {
+ $element->set_data($returned);
+
+ // 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. Also note that this is
+ * triggered before the previous path is actually dispatched.
+ *
+ * @param string $path in the original file
+ */
+ public function path_start_reached($path) {
+
+ if (empty($this->pathelements[$path])) {
+ return;
+ }
+
+ $element = $this->pathelements[$path];
+ $pobject = $element->get_processing_object();
+ $method = 'on_' . $element->get_name() . '_start';
+
+ if (method_exists($pobject, $method)) {
+ $pobject->$method();
+ }
+ }
+
+ /**
+ * Executes operations required at the end of a watched path
+ *
+ * @param string $path in the original file
+ */
+ public function path_end_reached($path) {
+
+ // expand the MOD paths so that they contain the current module name
+ if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
+ $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') {
+ $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 (empty($this->pathelements[$path])) {
+ return;
+ }
+
+ $element = $this->pathelements[$path];
+ $pobject = $element->get_processing_object();
+ $method = 'on_' . $element->get_name() . '_end';
+
+ if (method_exists($pobject, $method)) {
+ $pobject->$method();
+ }
+ }
+
+ /**
+ * Creates the temporary storage for stashed data
+ *
+ * This implementation uses backup_ids_temp table.
+ */
+ public function create_stash_storage() {
+ backup_controller_dbops::create_backup_ids_temp_table($this->get_id());
+ }
+
+ /**
+ * Drops the temporary storage of stashed data
+ *
+ * This implementation uses backup_ids_temp table.
+ */
+ public function drop_stash_storage() {
+ backup_controller_dbops::drop_backup_ids_temp_table($this->get_id());
+ }
+
+ /**
+ * Stores some information for later processing
+ *
+ * This implementation uses backup_ids_temp table to store data. Make
+ * sure that the $stashname + $itemid combo is unique.
+ *
+ * @param string $stashname name of the stash
+ * @param mixed $info information to stash
+ * @param int $itemid optional id for multiple infos within the same stashname
+ */
+ public function set_stash($stashname, $info, $itemid = 0) {
+ try {
+ restore_dbops::set_backup_ids_record($this->get_id(), $stashname, $itemid, 0, null, $info);
+
+ } catch (dml_exception $e) {
+ throw new moodle1_convert_storage_exception('unable_to_restore_stash', null, $e->getMessage());
+ }
+ }
+
+ /**
+ * Restores a given stash stored previously by {@link self::set_stash()}
+ *
+ * @param string $stashname name of the stash
+ * @param int $itemid optional id for multiple infos within the same stashname
+ * @throws moodle1_convert_empty_storage_exception if the info has not been stashed previously
+ * @return mixed stashed data
+ */
+ public function get_stash($stashname, $itemid = 0) {
+
+ $record = restore_dbops::get_backup_ids_record($this->get_id(), $stashname, $itemid);
+
+ if (empty($record)) {
+ throw new moodle1_convert_empty_storage_exception('required_not_stashed_data');
+ } else {
+ return $record->info;
+ }
+ }
+
+ /**
+ * Generates an artificial context id
+ *
+ * Moodle 1.9 backups do not contain any context information. But we need them
+ * in Moodle 2.x format so here we generate fictive context id for every given
+ * context level + instance combo.
+ *
+ * @see get_context_instance()
+ * @param int $level the context level, like CONTEXT_COURSE or CONTEXT_MODULE
+ * @param int $instance the instance id, for example $course->id for courses or $cm->id for activity modules
+ * @return int the context id
+ */
+ public function get_contextid($level, $instance) {
+ static $autoincrement = 0;
+
+ $stashname = 'context' . $level;
+
+ try {
+ // try the previously stashed id
+ return $this->get_stash($stashname, $instance);
+
+ } catch (moodle1_convert_empty_storage_exception $e) {
+ // this context level + instance is required for the first time
+ $this->set_stash($stashname, ++$autoincrement, $instance);
+ return $autoincrement;
+ }
+ }
+}
+
+
+/**
+ * Exception thrown by this converter
+ */
+class moodle1_convert_exception extends convert_exception {
+}
+
+
+/**
+ * Exception thrown by the temporary storage subsystem of moodle1_converter
+ */
+class moodle1_convert_storage_exception extends moodle1_convert_exception {
+}
+
+
+/**
+ * Exception thrown by the temporary storage subsystem of moodle1_converter
+ */
+class moodle1_convert_empty_storage_exception extends moodle1_convert_exception {
+}
+
+
+/**
+ * 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 dropped */
+ protected $dropfields = 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['dropfields']) and is_array($recipe['dropfields'])) {
+ $this->set_dropped_fields($recipe['dropfields']);
+ }
+ 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 drop
+ *
+ * @param array $fields
+ */
+ public function set_dropped_fields(array $fields) {
+ $this->dropfields = $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);
+
+ // drop legacy fields
+ if (in_array($name, $this->dropfields)) {
+ continue;
+ }
+
+ // 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);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<MOODLE_BACKUP>
+ <INFO>
+ <NAME>restore.zip</NAME>
+ <MOODLE_VERSION>2007101520</MOODLE_VERSION>
+ <MOODLE_RELEASE>1.9.2 (Build: 20080711)</MOODLE_RELEASE>
+ <BACKUP_VERSION>2008030300</BACKUP_VERSION>
+ <BACKUP_RELEASE>1.9</BACKUP_RELEASE>
+ <DATE>1299774017</DATE>
+ <ORIGINAL_WWWROOT>http://elearning-testadam.uss.lsu.edu/moodle</ORIGINAL_WWWROOT>
+ <ZIP_METHOD>internal</ZIP_METHOD>
+ <DETAILS>
+ <MOD>
+ <NAME>forum</NAME>
+ <INCLUDED>true</INCLUDED>
+ <USERINFO>false</USERINFO>
+ <INSTANCES>
+ <INSTANCE>
+ <ID>57</ID>
+ <NAME>News forum</NAME>
+ <INCLUDED>true</INCLUDED>
+ <USERINFO>false</USERINFO>
+ </INSTANCE>
+ </INSTANCES>
+ </MOD>
+ <METACOURSE>false</METACOURSE>
+ <USERS>course</USERS>
+ <LOGS>false</LOGS>
+ <USERFILES>true</USERFILES>
+ <COURSEFILES>true</COURSEFILES>
+ <SITEFILES>true</SITEFILES>
+ <GRADEBOOKHISTORIES>false</GRADEBOOKHISTORIES>
+ <MESSAGES>false</MESSAGES>
+ <BLOGS>false</BLOGS>
+ <BLOCKFORMAT>instances</BLOCKFORMAT>
+ </DETAILS>
+ </INFO>
+ <ROLES>
+ </ROLES>
+ <COURSE>
+ <HEADER>
+ <ID>33</ID>
+ <CATEGORY>
+ <ID>1</ID>
+ <NAME>Miscellaneous</NAME>
+ </CATEGORY>
+ <PASSWORD></PASSWORD>
+ <FULLNAME>Moodle 2.0 Test Restore</FULLNAME>
+ <SHORTNAME>M2TR</SHORTNAME>
+ <IDNUMBER></IDNUMBER>
+ <SUMMARY>Cazzin'</SUMMARY>
+ <FORMAT>weeks</FORMAT>
+ <SHOWGRADES>1</SHOWGRADES>
+ <NEWSITEMS>5</NEWSITEMS>
+ <TEACHER>Teacher</TEACHER>
+ <TEACHERS>Teachers</TEACHERS>
+ <STUDENT>Student</STUDENT>
+ <STUDENTS>Students</STUDENTS>
+ <GUEST>0</GUEST>
+ <STARTDATE>1299823200</STARTDATE>
+ <NUMSECTIONS>10</NUMSECTIONS>
+ <MAXBYTES>52428800</MAXBYTES>
+ <SHOWREPORTS>0</SHOWREPORTS>
+ <GROUPMODE>0</GROUPMODE>
+ <GROUPMODEFORCE>0</GROUPMODEFORCE>
+ <DEFAULTGROUPINGID>0</DEFAULTGROUPINGID>
+ <LANG></LANG>
+ <THEME></THEME>
+ <COST></COST>
+ <CURRENCY>USD</CURRENCY>
+ <MARKER>0</MARKER>
+ <VISIBLE>1</VISIBLE>
+ <HIDDENSECTIONS>0</HIDDENSECTIONS>
+ <TIMECREATED>1299773769</TIMECREATED>
+ <TIMEMODIFIED>1299773769</TIMEMODIFIED>
+ <METACOURSE>0</METACOURSE>
+ <EXPIRENOTIFY>0</EXPIRENOTIFY>
+ <NOTIFYSTUDENTS>0</NOTIFYSTUDENTS>
+ <EXPIRYTHRESHOLD>864000</EXPIRYTHRESHOLD>
+ <ENROLLABLE>1</ENROLLABLE>
+ <ENROLSTARTDATE>0</ENROLSTARTDATE>
+ <ENROLENDDATE>0</ENROLENDDATE>
+ <ENROLPERIOD>0</ENROLPERIOD>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </HEADER>
+ <BLOCKS>
+ <BLOCK>
+ <ID>314</ID>
+ <NAME>participants</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>l</POSITION>
+ <WEIGHT>0</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ <BLOCK>
+ <ID>315</ID>
+ <NAME>activity_modules</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>l</POSITION>
+ <WEIGHT>1</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ <BLOCK>
+ <ID>316</ID>
+ <NAME>search_forums</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>l</POSITION>
+ <WEIGHT>2</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ <BLOCK>
+ <ID>317</ID>
+ <NAME>admin</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>l</POSITION>
+ <WEIGHT>3</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ <BLOCK>
+ <ID>318</ID>
+ <NAME>course_list</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>l</POSITION>
+ <WEIGHT>4</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ <BLOCK>
+ <ID>319</ID>
+ <NAME>news_items</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>r</POSITION>
+ <WEIGHT>0</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ <BLOCK>
+ <ID>320</ID>
+ <NAME>calendar_upcoming</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>r</POSITION>
+ <WEIGHT>1</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ <BLOCK>
+ <ID>321</ID>
+ <NAME>recent_activity</NAME>
+ <PAGEID>33</PAGEID>
+ <PAGETYPE>course-view</PAGETYPE>
+ <POSITION>r</POSITION>
+ <WEIGHT>2</WEIGHT>
+ <VISIBLE>1</VISIBLE>
+ <CONFIGDATA>Tjs=</CONFIGDATA>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </BLOCK>
+ </BLOCKS>
+ <SECTIONS>
+ <SECTION>
+ <ID>436</ID>
+ <NUMBER>0</NUMBER>
+ <SUMMARY>$@NULL@$</SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ <MODS>
+ <MOD>
+ <ID>250</ID>
+ <TYPE>forum</TYPE>
+ <INSTANCE>57</INSTANCE>
+ <ADDED>1299773780</ADDED>
+ <SCORE>0</SCORE>
+ <INDENT>0</INDENT>
+ <VISIBLE>1</VISIBLE>
+ <GROUPMODE>0</GROUPMODE>
+ <GROUPINGID>0</GROUPINGID>
+ <GROUPMEMBERSONLY>0</GROUPMEMBERSONLY>
+ <IDNUMBER>$@NULL@$</IDNUMBER>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </MOD>
+ </MODS>
+ </SECTION>
+ <SECTION>
+ <ID>437</ID>
+ <NUMBER>1</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>438</ID>
+ <NUMBER>2</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>439</ID>
+ <NUMBER>3</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>440</ID>
+ <NUMBER>4</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>441</ID>
+ <NUMBER>5</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>442</ID>
+ <NUMBER>6</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>443</ID>
+ <NUMBER>7</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>444</ID>
+ <NUMBER>8</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>445</ID>
+ <NUMBER>9</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ <SECTION>
+ <ID>446</ID>
+ <NUMBER>10</NUMBER>
+ <SUMMARY></SUMMARY>
+ <VISIBLE>1</VISIBLE>
+ </SECTION>
+ </SECTIONS>
+ <USERS>
+ <USER>
+ <ID>2</ID>
+ <AUTH>manual</AUTH>
+ <CONFIRMED>1</CONFIRMED>
+ <POLICYAGREED>0</POLICYAGREED>
+ <DELETED>0</DELETED>
+ <USERNAME>admin</USERNAME>
+ <PASSWORD>ea557cf33bda866d97b07465f1ea3867</PASSWORD>
+ <IDNUMBER>891220979</IDNUMBER>
+ <FIRSTNAME>Admin</FIRSTNAME>
+ <LASTNAME>User</LASTNAME>
+ <EMAIL>adamzap@gmail.com</EMAIL>
+ <EMAILSTOP>0</EMAILSTOP>
+ <ICQ></ICQ>
+ <SKYPE></SKYPE>
+ <YAHOO></YAHOO>
+ <AIM></AIM>
+ <MSN></MSN>
+ <PHONE1></PHONE1>
+ <PHONE2></PHONE2>
+ <INSTITUTION></INSTITUTION>
+ <DEPARTMENT></DEPARTMENT>
+ <ADDRESS></ADDRESS>
+ <CITY>br</CITY>
+ <COUNTRY>US</COUNTRY>
+ <LANG>en_utf8</LANG>
+ <THEME></THEME>
+ <TIMEZONE>99</TIMEZONE>
+ <FIRSTACCESS>0</FIRSTACCESS>
+ <LASTACCESS>1265755409</LASTACCESS>
+ <LASTLOGIN>1265658108</LASTLOGIN>
+ <CURRENTLOGIN>1265741271</CURRENTLOGIN>
+ <LASTIP>130.39.194.172</LASTIP>
+ <SECRET>lknsJoSw0S6myJA</SECRET>
+ <PICTURE>1</PICTURE>
+ <URL></URL>
+ <DESCRIPTION></DESCRIPTION>
+ <MAILFORMAT>1</MAILFORMAT>
+ <MAILDIGEST>0</MAILDIGEST>
+ <MAILDISPLAY>1</MAILDISPLAY>
+ <HTMLEDITOR>1</HTMLEDITOR>
+ <AJAX>1</AJAX>
+ <AUTOSUBSCRIBE>1</AUTOSUBSCRIBE>
+ <TRACKFORUMS>0</TRACKFORUMS>
+ <TIMEMODIFIED>1265216036</TIMEMODIFIED>
+ <ROLES>
+ <ROLE>
+ <TYPE>needed</TYPE>
+ </ROLE>
+ </ROLES>
+ <USER_PREFERENCES>
+ <USER_PREFERENCE>
+ <NAME>email_bounce_count</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>email_send_count</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>user_editadvanced_form_showadvanced</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ </USER_PREFERENCES>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </USER>
+ <USER>
+ <ID>3</ID>
+ <AUTH>cas</AUTH>
+ <CONFIRMED>1</CONFIRMED>
+ <POLICYAGREED>0</POLICYAGREED>
+ <DELETED>0</DELETED>
+ <USERNAME>azaple1</USERNAME>
+ <PASSWORD>fb005bdc5fb1b23ea95d238fb5ecb9e2</PASSWORD>
+ <IDNUMBER>891111111</IDNUMBER>
+ <FIRSTNAME>Adam</FIRSTNAME>
+ <LASTNAME>Zapletal</LASTNAME>
+ <EMAIL>azaple1@lsu.edu</EMAIL>
+ <EMAILSTOP>0</EMAILSTOP>
+ <ICQ></ICQ>
+ <SKYPE></SKYPE>
+ <YAHOO></YAHOO>
+ <AIM></AIM>
+ <MSN></MSN>
+ <PHONE1></PHONE1>
+ <PHONE2></PHONE2>
+ <INSTITUTION></INSTITUTION>
+ <DEPARTMENT></DEPARTMENT>
+ <ADDRESS></ADDRESS>
+ <CITY>asdf</CITY>
+ <COUNTRY>AL</COUNTRY>
+ <LANG>en_utf8</LANG>
+ <THEME></THEME>
+ <TIMEZONE>99</TIMEZONE>
+ <FIRSTACCESS>0</FIRSTACCESS>
+ <LASTACCESS>1299774054</LASTACCESS>
+ <LASTLOGIN>1297979000</LASTLOGIN>
+ <CURRENTLOGIN>1299773727</CURRENTLOGIN>
+ <LASTIP>173.253.129.223</LASTIP>
+ <SECRET></SECRET>
+ <PICTURE>1</PICTURE>
+ <URL></URL>
+ <DESCRIPTION></DESCRIPTION>
+ <MAILFORMAT>1</MAILFORMAT>
+ <MAILDIGEST>0</MAILDIGEST>
+ <MAILDISPLAY>2</MAILDISPLAY>
+ <HTMLEDITOR>1</HTMLEDITOR>
+ <AJAX>1</AJAX>
+ <AUTOSUBSCRIBE>1</AUTOSUBSCRIBE>
+ <TRACKFORUMS>0</TRACKFORUMS>
+ <TIMEMODIFIED>1288033285</TIMEMODIFIED>
+ <ROLES>
+ <ROLE>
+ <TYPE>needed</TYPE>
+ </ROLE>
+ </ROLES>
+ <USER_PREFERENCES>
+ <USER_PREFERENCE>
+ <NAME>assignment_mailinfo</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>assignment_perpage</NAME>
+ <VALUE>10</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>assignment_quickgrade</NAME>
+ <VALUE>0</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>auth_forcepasswordchange</NAME>
+ <VALUE>0</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>forum_displaymode</NAME>
+ <VALUE>3</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>grader_report_preferences_form_showadvanced</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>grade_report_simple_aggregationview51</NAME>
+ <VALUE>0</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>grade_report_simple_grader_collapsed_categories</NAME>
+ <VALUE>a:2:{s:14:"aggregatesonly";a:4:{i:0;s:2:"70";i:1;s:2:"71";i:2;s:2:"72";i:3;s:2:"73";}s:10:"gradesonly";a:0:{}}</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>grade_report_simple_meanselection</NAME>
+ <VALUE>2</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>grade_report_simple_showranges</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>grade_report_simple_showstickytab</NAME>
+ <VALUE>0</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>lesson_view</NAME>
+ <VALUE>collapsed</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>mod_resource_mod_form_showadvanced</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ <USER_PREFERENCE>
+ <NAME>user_editadvanced_form_showadvanced</NAME>
+ <VALUE>1</VALUE>
+ </USER_PREFERENCE>
+ </USER_PREFERENCES>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </USER>
+ <USER>
+ <ID>6</ID>
+ <AUTH>cas</AUTH>
+ <CONFIRMED>1</CONFIRMED>
+ <POLICYAGREED>0</POLICYAGREED>
+ <DELETED>0</DELETED>
+ <USERNAME>pcali1</USERNAME>
+ <PASSWORD>fb005bdc5fb1b23ea95d238fb5ecb9e2</PASSWORD>
+ <IDNUMBER></IDNUMBER>
+ <FIRSTNAME>Philip</FIRSTNAME>
+ <LASTNAME>Cali</LASTNAME>
+ <EMAIL>pcali1@lsu.edu</EMAIL>
+ <EMAILSTOP>0</EMAILSTOP>
+ <ICQ></ICQ>
+ <SKYPE></SKYPE>
+ <YAHOO></YAHOO>
+ <AIM></AIM>
+ <MSN></MSN>
+ <PHONE1></PHONE1>
+ <PHONE2></PHONE2>
+ <INSTITUTION></INSTITUTION>
+ <DEPARTMENT></DEPARTMENT>
+ <ADDRESS></ADDRESS>
+ <CITY></CITY>
+ <COUNTRY></COUNTRY>
+ <LANG>en_utf8</LANG>
+ <THEME></THEME>
+ <TIMEZONE>99</TIMEZONE>
+ <FIRSTACCESS>0</FIRSTACCESS>
+ <LASTACCESS>1268662392</LASTACCESS>
+ <LASTLOGIN>0</LASTLOGIN>
+ <CURRENTLOGIN>1268662284</CURRENTLOGIN>
+ <LASTIP>68.105.37.237</LASTIP>
+ <SECRET></SECRET>
+ <PICTURE>1</PICTURE>
+ <URL></URL>
+ <DESCRIPTION>$@NULL@$</DESCRIPTION>
+ <MAILFORMAT>1</MAILFORMAT>
+ <MAILDIGEST>0</MAILDIGEST>
+ <MAILDISPLAY>2</MAILDISPLAY>
+ <HTMLEDITOR>1</HTMLEDITOR>
+ <AJAX>1</AJAX>
+ <AUTOSUBSCRIBE>1</AUTOSUBSCRIBE>
+ <TRACKFORUMS>0</TRACKFORUMS>
+ <TIMEMODIFIED>1268662284</TIMEMODIFIED>
+ <ROLES>
+ <ROLE>
+ <TYPE>needed</TYPE>
+ </ROLE>
+ </ROLES>
+ <ROLES_OVERRIDES>
+ </ROLES_OVERRIDES>
+ <ROLES_ASSIGNMENTS>
+ </ROLES_ASSIGNMENTS>
+ </USER>
+ </USERS>
+ <GRADEBOOK>
+ <GRADE_CATEGORIES>
+ <GRADE_CATEGORY>
+ <ID>90</ID>
+ <PARENT>$@NULL@$</PARENT>
+ <FULLNAME>?</FULLNAME>
+ <AGGREGATION>11</AGGREGATION>
+ <KEEPHIGH>0</KEEPHIGH>
+ <DROPLOW>0</DROPLOW>
+ <AGGREGATEONLYGRADED>1</AGGREGATEONLYGRADED>
+ <AGGREGATEOUTCOMES>0</AGGREGATEOUTCOMES>
+ <AGGREGATESUBCATS>0</AGGREGATESUBCATS>
+ <TIMECREATED>1299774068</TIMECREATED>
+ <TIMEMODIFIED>1299774068</TIMEMODIFIED>
+ </GRADE_CATEGORY>
+ </GRADE_CATEGORIES>
+ <GRADE_ITEMS>
+ <GRADE_ITEM>
+ <ID>410</ID>
+ <CATEGORYID>$@NULL@$</CATEGORYID>
+ <ITEMNAME>$@NULL@$</ITEMNAME>
+ <ITEMTYPE>course</ITEMTYPE>
+ <ITEMMODULE>$@NULL@$</ITEMMODULE>
+ <ITEMINSTANCE>90</ITEMINSTANCE>
+ <ITEMNUMBER>$@NULL@$</ITEMNUMBER>
+ <ITEMINFO>$@NULL@$</ITEMINFO>
+ <IDNUMBER>$@NULL@$</IDNUMBER>
+ <CALCULATION>$@NULL@$</CALCULATION>
+ <GRADETYPE>1</GRADETYPE>
+ <GRADEMAX>100.00000</GRADEMAX>
+ <GRADEMIN>0.00000</GRADEMIN>
+ <SCALEID>$@NULL@$</SCALEID>
+ <OUTCOMEID>$@NULL@$</OUTCOMEID>
+ <GRADEPASS>0.00000</GRADEPASS>
+ <MULTFACTOR>1.00000</MULTFACTOR>
+ <PLUSFACTOR>0.00000</PLUSFACTOR>
+ <AGGREGATIONCOEF>1.00000</AGGREGATIONCOEF>
+ <DISPLAY>0</DISPLAY>
+ <DECIMALS>$@NULL@$</DECIMALS>
+ <HIDDEN>0</HIDDEN>
+ <LOCKED>0</LOCKED>
+ <LOCKTIME>0</LOCKTIME>
+ <NEEDSUPDATE>0</NEEDSUPDATE>
+ <TIMECREATED>1299774068</TIMECREATED>
+ <TIMEMODIFIED>1299774068</TIMEMODIFIED>
+ </GRADE_ITEM>
+ </GRADE_ITEMS>
+ </GRADEBOOK>
+ <MODULES>
+ <MOD>
+ <ID>57</ID>
+ <MODTYPE>forum</MODTYPE>
+ <TYPE>news</TYPE>
+ <NAME>News forum</NAME>
+ <INTRO>General news and announcements</INTRO>
+ <ASSESSED>0</ASSESSED>
+ <ASSESSTIMESTART>0</ASSESSTIMESTART>
+ <ASSESSTIMEFINISH>0</ASSESSTIMEFINISH>
+ <MAXBYTES>0</MAXBYTES>
+ <SCALE>0</SCALE>
+ <FORCESUBSCRIBE>1</FORCESUBSCRIBE>
+ <TRACKINGTYPE>1</TRACKINGTYPE>
+ <RSSTYPE>0</RSSTYPE>
+ <RSSARTICLES>0</RSSARTICLES>
+ <TIMEMODIFIED>1299773780</TIMEMODIFIED>
+ <WARNAFTER>0</WARNAFTER>
+ <BLOCKAFTER>0</BLOCKAFTER>
+ <BLOCKPERIOD>0</BLOCKPERIOD>
+ </MOD>
+ </MODULES>
+ <FORMATDATA>
+ </FORMATDATA>
+ </COURSE>
+</MOODLE_BACKUP>
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the moodle1 converter
+ *
+ * @package core
+ * @subpackage backup-convert
+ * @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/backup/converter/moodle1/lib.php');
+
+class moodle1_converter_test extends UnitTestCase {
+
+ public static $includecoverage = array();
+
+ /** @var string the name of the directory containing the unpacked Moodle 1.9 backup */
+ protected $tempdir;
+
+ public function setUp() {
+ global $CFG;
+
+ $this->tempdir = convert_helper::generate_id('simpletest');
+ check_dir_exists("$CFG->dataroot/temp/backup/$this->tempdir");
+ copy(
+ "$CFG->dirroot/backup/converter/moodle1/simpletest/files/moodle.xml",
+ "$CFG->dataroot/temp/backup/$this->tempdir/moodle.xml"
+ );
+ }
+
+ public function tearDown() {
+ global $CFG;
+ if (empty($CFG->keeptempdirectoriesonbackup)) {
+ fulldelete("$CFG->dataroot/temp/backup/$this->tempdir");
+ }
+ }
+
+ public function test_detect_format() {
+ $detected = moodle1_converter::detect_format($this->tempdir);
+ $this->assertEqual(backup::FORMAT_MOODLE1, $detected);
+ }
+
+ public function test_convert_factory() {
+ $converter = convert_factory::converter('moodle1', $this->tempdir);
+ $this->assertIsA($converter, 'moodle1_converter');
+ }
+
+ public function test_stash_storage_not_created() {
+ $converter = convert_factory::converter('moodle1', $this->tempdir);
+ $this->expectException('moodle1_convert_storage_exception');
+ $converter->set_stash('tempinfo', 12);
+ }
+
+ public function test_stash_requiring_empty_stash() {
+ $converter = convert_factory::converter('moodle1', $this->tempdir);
+ $converter->create_stash_storage();
+ $converter->set_stash('tempinfo', 12);
+ $this->expectException('moodle1_convert_empty_storage_exception');
+ try {
+ $converter->get_stash('anothertempinfo');
+
+ } catch (moodle1_convert_empty_storage_exception $e) {
+ // we must drop the storage here so we are able to re-create it in the next test
+ $converter->drop_stash_storage();
+ throw new moodle1_convert_empty_storage_exception('rethrowing');
+ }
+ }
+
+ public function test_stash_storage() {
+ $converter = convert_factory::converter('moodle1', $this->tempdir);
+ $converter->create_stash_storage();
+
+ // test stashes without itemid
+ $converter->set_stash('tempinfo1', 12);
+ $converter->set_stash('tempinfo2', array('a' => 2, 'b' => 3));
+ $this->assertIdentical(12, $converter->get_stash('tempinfo1'));
+ $this->assertIdentical(array('a' => 2, 'b' => 3), $converter->get_stash('tempinfo2'));
+
+ // overwriting a stashed value is allowed
+ $converter->set_stash('tempinfo1', '13');
+ $this->assertNotIdentical(13, $converter->get_stash('tempinfo1'));
+ $this->assertIdentical('13', $converter->get_stash('tempinfo1'));
+
+ // repeated reading is allowed
+ $this->assertIdentical('13', $converter->get_stash('tempinfo1'));
+
+ // test stashes with itemid
+ $converter->set_stash('tempinfo', 'Hello', 1);
+ $converter->set_stash('tempinfo', 'World', 2);
+ $this->assertIdentical('Hello', $converter->get_stash('tempinfo', 1));
+ $this->assertIdentical('World', $converter->get_stash('tempinfo', 2));
+
+ $converter->drop_stash_storage();
+ }
+
+ public function test_get_contextid() {
+ $converter = convert_factory::converter('moodle1', $this->tempdir);
+
+ // stash storage must be created in advance
+ $converter->create_stash_storage();
+
+ // ids are generated on the first call
+ $id1 = $converter->get_contextid(CONTEXT_COURSE, 10);
+ $id2 = $converter->get_contextid(CONTEXT_COURSE, 11);
+ $id3 = $converter->get_contextid(CONTEXT_MODULE, 10);
+
+ $this->assertNotEqual($id1, $id2);
+ $this->assertNotEqual($id1, $id3);
+ $this->assertNotEqual($id2, $id3);
+
+ // and then re-used if called with the same params
+ $this->assertEqual($id1, $converter->get_contextid(CONTEXT_COURSE, 10));
+ $this->assertEqual($id2, $converter->get_contextid(CONTEXT_COURSE, 11));
+ $this->assertEqual($id3, $converter->get_contextid(CONTEXT_MODULE, 10));
+
+ $converter->drop_stash_storage();
+ }
+
+ public function test_convert_run_convert() {
+ $converter = convert_factory::converter('moodle1', $this->tempdir);
+ $converter->convert();
+ }
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package core
+ * @subpackage backup-convert
+ * @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Factory class to create new instances of backup converters
+ */
+abstract class convert_factory {
+
+ /**
+ * Instantinates the given converter operating on a given directory
+ *
+ * @throws coding_exception
+ * @param $name The converter name
+ * @param $tempdir The temp directory to operate on
+ * @return base_converter
+ */
+ public static function converter($name, $tempdir) {
+ global $CFG;
+
+ $name = clean_param($name, PARAM_SAFEDIR);
+
+ $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");
+ }
+ require_once($classfile);
+
+ if (!class_exists($classname)) {
+ throw new coding_exception("Converter factory error: class not found $classname");
+ }
+ return new $classname($tempdir);
+ }
+}
}
/**
- * Given one temp/backup/xxx dir, detect its format
+ * Detects the format of the given unpacked backup directory
*
- * TODO: Move harcoded detection here to delegated classes under backup/format (moodle1, imscc..)
- * conversion code will be there too.
+ * @param string $tempdir the name of the backup directory
+ * @return string one of backup::FORMAT_xxx constants
*/
public static function detect_backup_format($tempdir) {
global $CFG;
- $filepath = $CFG->dataroot . '/temp/backup/' . $tempdir . '/moodle_backup.xml';
+ require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php');
+ require_once($CFG->dirroot . '/backup/util/factories/convert_factory.class.php');
- // Does tempdir exist and is dir
- if (!is_dir(dirname($filepath))) {
- throw new backup_helper_exception('tmp_backup_directory_not_found', dirname($filepath));
+ if (convert_helper::detect_moodle2_format($tempdir)) {
+ return backup::FORMAT_MOODLE;
}
- // First look for MOODLE (moodle2) format
- if (file_exists($filepath)) { // Looks promising, lets load some information
- $handle = fopen ($filepath, "r");
- $first_chars = fread($handle,200);
- $status = fclose ($handle);
- // Check if it has the required strings
- if (strpos($first_chars,'<?xml version="1.0" encoding="UTF-8"?>') !== false &&
- strpos($first_chars,'<moodle_backup>') !== false &&
- strpos($first_chars,'<information>') !== false) {
- return backup::FORMAT_MOODLE;
+ // see if a converter can identify the format
+ $converters = convert_factory::available_converters();
+ 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");
}
- }
- // Then look for MOODLE1 (moodle1) format
- $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);
- $status = fclose ($handle);
- // Check if it has the required strings
- if (strpos($first_chars,'<?xml version="1.0" encoding="UTF-8"?>') !== false &&
- strpos($first_chars,'<MOODLE_BACKUP>') !== false &&
- strpos($first_chars,'<INFO>') !== false) {
- return backup::FORMAT_MOODLE1;
+ $detected = call_user_func($classname .'::detect_format', $tempdir);
+ if (!empty($detected)) {
+ return $detected;
}
}
- // Other formats
-
- // Arrived here, unknown format
return backup::FORMAT_UNKNOWN;
}
}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides {@link convert_helper} and {@link convert_helper_exception} classes
+ *
+ * @package core
+ * @subpackage backup-convert
+ * @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
+ * @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');
+
+/**
+ * Provides various functionality via its static methods
+ */
+abstract class convert_helper {
+
+ /**
+ * @param string $entropy
+ * @return string random identifier
+ */
+ public static function generate_id($entropy) {
+ return md5(time() . '-' . $entropy . '-' . random_string(20));
+ }
+
+ /**
+ * Returns the list of all available converters and loads their classes
+ *
+ * Converter must be installed as a directory in backup/converter/ and its
+ * method is_available() must return true to get to the list.
+ *
+ * @see base_converter::is_available()
+ * @return array of strings
+ */
+ public static function available_converters() {
+ global $CFG;
+
+ $converters = array();
+ $plugins = get_list_of_plugins('backup/converter');
+ foreach ($plugins as $name) {
+ $classfile = "$CFG->dirroot/backup/converter/$name/lib.php";
+ $classname = "{$name}_converter";
+
+ if (!file_exists($classfile)) {
+ throw new convert_helper_exception('converter_classfile_not_found', $classfile);
+ }
+ require_once($classfile);
+
+ if (!class_exists($classname)) {
+ throw new convert_helper_exception('converter_classname_not_found', $classname);
+ }
+
+ if (call_user_func($classname .'::is_available')) {
+ $converters[] = $name;
+ }
+ }
+
+ return $converters;
+ }
+
+ /**
+ * Detects if the given folder contains an unpacked moodle2 backup
+ *
+ * @param string $tempdir the name of the backup directory
+ * @return boolean true if moodle2 format detected, false otherwise
+ */
+ public static function detect_moodle2_format($tempdir) {
+ global $CFG;
+
+ $dirpath = $CFG->dataroot . '/temp/backup/' . $tempdir;
+ $filepath = $dirpath . '/moodle_backup.xml';
+
+ if (!is_dir($dirpath)) {
+ throw new converter_helper_exception('tmp_backup_directory_not_found', $dirpath);
+ }
+
+ if (!file_exists($filepath)) {
+ return false;
+ }
+
+ $handle = fopen($filepath, 'r');
+ $firstchars = fread($handle, 200);
+ $status = fclose($handle);
+
+ if (strpos($firstchars,'<?xml version="1.0" encoding="UTF-8"?>') !== false and
+ strpos($firstchars,'<moodle_backup>') !== false and
+ strpos($firstchars,'<information>') !== false) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Converts the given directory with the backup into moodle2 format
+ *
+ * @param string $tempdir The directory to convert
+ * @param string $format The current format, if already detected
+ * @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) {
+
+ if (is_null($format)) {
+ $format = backup_general_helper::detect_backup_format($tempdir);
+ }
+
+ // get the supported conversion paths from all available converters
+ $converters = convert_factory::available_converters();
+ $descriptions = array();
+ foreach ($converters as $name) {
+ $classname = "{$name}_converter";
+ if (!class_exists($classname)) {
+ throw new convert_helper_exception('class_not_loaded', $classname);
+ }
+ $descriptions[$name] = call_user_func($classname .'::description');
+ }
+
+ // choose the best conversion path for the given format
+ $path = self::choose_conversion_path($format, $descriptions);
+
+ if (empty($path)) {
+ // unable to convert
+ return false;
+ }
+
+ foreach ($path as $name) {
+ $converter = convert_factory::converter($name, $tempdir);
+ $converter->convert();
+ }
+
+ // make sure we ended with moodle2 format
+ if (!self::detect_moodle2_format($tempdir)) {
+ throw new convert_helper_exception('conversion_failed');
+ }
+
+ return true;
+ }
+
+ /**
+ * Inserts an inforef into the conversion temp table
+ */
+ public static function set_inforef($contextid) {
+ global $DB;
+ }
+
+ public static function get_inforef($contextid) {
+ }
+
+ /**
+ * Converts a plain old php object (popo?) into a string...
+ * Useful for debuging failed db inserts, or anything like that
+ */
+ public static function obj_to_readable($obj) {
+ $mapper = function($field, $value) { return "$field=$value"; };
+ $fields = get_object_vars($obj);
+
+ return implode(", ", array_map($mapper, array_keys($fields), array_values($fields)));
+ }
+
+ /// end of public API //////////////////////////////////////////////////////
+
+ /**
+ * Choose the best conversion path for the given format
+ *
+ * Given the source format and the list of available converters and their properties,
+ * this methods picks the most effective way how to convert the source format into
+ * the target moodle2 format. The method returns a list of converters that should be
+ * called, in order.
+ *
+ * This implementation uses Dijkstra's algorithm to find the shortest way through
+ * the oriented graph.
+ *
+ * @see http://en.wikipedia.org/wiki/Dijkstra's_algorithm
+ * @author David Mudrak <david@moodle.com>
+ * @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)
+ */
+ protected static function choose_conversion_path($format, array $descriptions) {
+
+ // construct an oriented graph of conversion paths. backup formats are nodes
+ // and the the converters are edges of the graph.
+ $paths = array(); // [fromnode][tonode] => converter
+ foreach ($descriptions as $converter => $description) {
+ $from = $description['from'];
+ $to = $description['to'];
+ $cost = $description['cost'];
+
+ 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 convert_helper_exception('invalid_converter_description', $converter);
+ }
+
+ if (!isset($paths[$from][$to])) {
+ $paths[$from][$to] = $converter;
+ } else {
+ // if there are two converters available for the same conversion
+ // path, choose the one with the lowest cost. if there are more
+ // available converters with the same cost, the chosen one is
+ // undefined (depends on the order of processing)
+ if ($descriptions[$paths[$from][$to]]['cost'] > $cost) {
+ $paths[$from][$to] = $converter;
+ }
+ }
+ }
+
+ if (empty($paths)) {
+ // no conversion paths available
+ return array();
+ }
+
+ // now use Dijkstra's algorithm and find the shortest conversion path
+
+ $dist = array(); // list of nodes and their distances from the source format
+ $prev = array(); // list of previous nodes in optimal path from the source format
+ foreach ($paths as $fromnode => $tonodes) {
+ $dist[$fromnode] = null; // infinitive distance, can't be reached
+ $prev[$fromnode] = null; // unknown
+ foreach ($tonodes as $tonode => $converter) {
+ $dist[$tonode] = null; // infinitive distance, can't be reached
+ $prev[$tonode] = null; // unknown
+ }
+ }
+
+ if (!array_key_exists($format, $dist)) {
+ return array();
+ } else {
+ $dist[$format] = 0;
+ }
+
+ $queue = array_flip(array_keys($dist));
+ while (!empty($queue)) {
+ // find the node with the smallest distance from the source in the queue
+ // in the first iteration, this will find the original format node itself
+ $closest = null;
+ foreach ($queue as $node => $undefined) {
+ if (is_null($dist[$node])) {
+ continue;
+ }
+ if (is_null($closest) or ($dist[$node] < $dist[$closest])) {
+ $closest = $node;
+ }
+ }
+
+ if (is_null($closest) or is_null($dist[$closest])) {
+ // all remaining nodes are inaccessible from source
+ break;
+ }
+
+ if ($closest === backup::FORMAT_MOODLE) {
+ // bingo we can break now
+ break;
+ }
+
+ unset($queue[$closest]);
+
+ // visit all neighbors and update distances to them eventually
+
+ if (!isset($paths[$closest])) {
+ continue;
+ }
+ $neighbors = array_keys($paths[$closest]);
+ // keep just neighbors that are in the queue yet
+ foreach ($neighbors as $ix => $neighbor) {
+ if (!array_key_exists($neighbor, $queue)) {
+ unset($neighbors[$ix]);
+ }
+ }
+
+ foreach ($neighbors as $neighbor) {
+ // the alternative distance to the neighbor if we went thru the
+ // current $closest node
+ $alt = $dist[$closest] + $descriptions[$paths[$closest][$neighbor]]['cost'];
+
+ if (is_null($dist[$neighbor]) or $alt < $dist[$neighbor]) {
+ // we found a shorter way to the $neighbor, remember it
+ $dist[$neighbor] = $alt;
+ $prev[$neighbor] = $closest;
+ }
+ }
+ }
+
+ if (is_null($dist[backup::FORMAT_MOODLE])) {
+ // unable to find a conversion path, the target format not reachable
+ return array();
+ }
+
+ // reconstruct the optimal path from the source format to the target one
+ $conversionpath = array();
+ $target = backup::FORMAT_MOODLE;
+ while (isset($prev[$target])) {
+ array_unshift($conversionpath, $paths[$prev[$target]][$target]);
+ $target = $prev[$target];
+ }
+
+ return $conversionpath;
+ }
+}
+
+/**
+ * General convert_helper related exception
+ *
+ * @author David Mudrak <david@moodle.com>
+ */
+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);
+ }
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for backup/util/helper/convert_helper.class.php
+ *
+ * @package core
+ * @subpackage backup-convert
+ * @copyright 2011 David Mudrak <david@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/backup/util/helper/convert_helper.class.php');
+
+/**
+ * Provides access to the protected methods we need to test
+ */
+class testable_convert_helper extends convert_helper {
+
+ public static function choose_conversion_path($format, array $descriptions) {
+ return parent::choose_conversion_path($format, $descriptions);
+ }
+}
+
+/**
+ * Defines the test methods
+ */
+class convert_helper_test extends UnitTestCase {
+
+ public static $includecoverage = array();
+
+ public function test_choose_conversion_path() {
+
+ // no converters available
+ $descriptions = array();
+ $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+ $this->assertEqual($path, array());
+
+ // missing source and/or targets
+ $descriptions = array(
+ // some custom converter
+ 'exporter' => array(
+ 'from' => backup::FORMAT_MOODLE1,
+ 'to' => 'some_custom_format',
+ 'cost' => 10,
+ ),
+ // another custom converter
+ 'converter' => array(
+ 'from' => 'yet_another_crazy_custom_format',
+ 'to' => backup::FORMAT_MOODLE,
+ 'cost' => 10,
+ ),
+ );
+ $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+ $this->assertEqual($path, array());
+
+ $path = testable_convert_helper::choose_conversion_path('some_other_custom_format', $descriptions);
+ $this->assertEqual($path, array());
+
+ // single step conversion
+ $path = testable_convert_helper::choose_conversion_path('yet_another_crazy_custom_format', $descriptions);
+ $this->assertEqual($path, array('converter'));
+
+ // no conversion needed - this is supposed to be detected by the caller
+ $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE, $descriptions);
+ $this->assertEqual($path, array());
+
+ // two alternatives
+ $descriptions = array(
+ // standard moodle 1.9 -> 2.x converter
+ 'moodle1' => array(
+ 'from' => backup::FORMAT_MOODLE1,
+ 'to' => backup::FORMAT_MOODLE,
+ 'cost' => 10,
+ ),
+ // alternative moodle 1.9 -> 2.x converter
+ 'alternative' => array(
+ 'from' => backup::FORMAT_MOODLE1,
+ 'to' => backup::FORMAT_MOODLE,
+ 'cost' => 8,
+ )
+ );
+ $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+ $this->assertEqual($path, array('alternative'));
+
+ // complex case
+ $descriptions = array(
+ // standard moodle 1.9 -> 2.x converter
+ 'moodle1' => array(
+ 'from' => backup::FORMAT_MOODLE1,
+ 'to' => backup::FORMAT_MOODLE,
+ 'cost' => 10,
+ ),
+ // alternative moodle 1.9 -> 2.x converter
+ 'alternative' => array(
+ 'from' => backup::FORMAT_MOODLE1,
+ 'to' => backup::FORMAT_MOODLE,
+ 'cost' => 8,
+ ),
+ // custom converter from 1.9 -> custom 'CFv1' format
+ 'cc1' => array(
+ 'from' => backup::FORMAT_MOODLE1,
+ 'to' => 'CFv1',
+ 'cost' => 2,
+ ),
+ // custom converter from custom 'CFv1' format -> moodle 2.0 format
+ 'cc2' => array(
+ 'from' => 'CFv1',
+ 'to' => backup::FORMAT_MOODLE,
+ 'cost' => 5,
+ ),
+ // custom converter from CFv1 -> CFv2 format
+ 'cc3' => array(
+ 'from' => 'CFv1',
+ 'to' => 'CFv2',
+ 'cost' => 2,
+ ),
+ // custom converter from CFv2 -> moodle 2.0 format
+ 'cc4' => array(
+ 'from' => 'CFv2',
+ 'to' => backup::FORMAT_MOODLE,
+ 'cost' => 2,
+ ),
+ );
+
+ // ask the helper to find the most effective way
+ $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+ $this->assertEqual($path, array('cc1', 'cc3', 'cc4'));
+ }
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Makes sure that all general code needed by backup-convert code is included
+ *
+ * @package core
+ * @subpackage backup-convert
+ * @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+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');
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ *
+ * @package mod
+ * @subpackage choice
+ * @copyright 2011 David Mudrak <david@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Choice conversion handler
+ */
+class moodle1_mod_choice_handler extends moodle1_mod_handler {
+
+ /**
+ * Declare the paths in moodle.xml we are able to convert
+ *
+ * The method returns list of {@link convert_path} instances.
+ * For each path returned, the corresponding conversion method must be
+ * defined.
+ *
+ * Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/CHOICE does 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
+ */
+ public function get_paths() {
+ return array(
+ new convert_path(
+ 'choice', '/MOODLE_BACKUP/COURSE/MODULES/MOD/CHOICE',
+ array(
+ 'renamefields' => array(
+ 'text' => 'intro',
+ 'format' => 'introformat',
+ ),
+ 'newfields' => array(
+ 'completionsubmit' => 0,
+ ),
+ 'dropfields' => array(
+ 'modtype'
+ ),
+ )
+ ),
+ new convert_path('choice_option', '/MOODLE_BACKUP/COURSE/MODULES/MOD/CHOICE/OPTIONS/OPTION'),
+ );
+ }
+
+ /**
+ * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/CHOICE
+ * data available
+ */
+ public function process_choice($data) {
+
+ // get the course module id and context id
+ $instanceid = $data['id'];
+ $moduleid = $this->get_moduleid($instanceid);
+ $contextid = $this->converter->get_contextid(CONTEXT_MODULE, $moduleid);
+
+ // we now have all information needed to start writing into the file
+ $this->open_xml_writer("activities/choice_{$moduleid}/choice.xml");
+ $this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $moduleid,
+ 'modulename' => 'choice', 'contextid' => $contextid));
+ $this->xmlwriter->begin_tag('choice', array('id' => $instanceid));
+
+ unset($data['id']); // we already write it as attribute, do not repeat it as child element
+ foreach ($data as $field => $value) {
+ $this->xmlwriter->full_tag($field, $value);
+ }
+
+ $this->xmlwriter->begin_tag('options');
+ }
+
+ /**
+ * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/CHOICE/OPTIONS/OPTION
+ * data available
+ */
+ public function process_choice_option($data) {
+ $this->write_xml('option', $data, array('/option/id'));
+ }
+
+ /**
+ * This is executed when we reach the closing </MOD> tag of our 'choice' path
+ */
+ public function on_choice_end() {
+
+ $this->xmlwriter->end_tag('options');
+ $this->xmlwriter->end_tag('choice');
+ $this->xmlwriter->end_tag('activity');
+ $this->close_xml_writer();
+ }
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ *
+ * @package mod
+ * @subpackage forum
+ * @copyright 2011 Mark Nielsen <mark@moodlerooms.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Forum conversion handler
+ */
+class moodle1_mod_forum_handler extends moodle1_mod_handler {
+
+ /**
+ * Declare the paths in moodle.xml we are able to convert
+ *
+ * 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
+ */
+ public function get_paths() {
+ return array(
+ new convert_path('forum', '/MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM'),
+ );
+ }
+
+ /**
+ * Converts /MOODLE_BACKUP/COURSE/MODULES/MOD/FORUM data
+ */
+ public function process_forum($data) {
+ }
+}