Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?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),new moodle1_roles_definition_handler($converter),new moodle1_question_bank_handler($converter),new moodle1_scales_handler($converter),new moodle1_outcomes_handler($converter),new moodle1_gradebook_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 classforeach ($handlers as $handler) {if (!$handler instanceof moodle1_handler) {throw new moodle1_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 moodle1_convert_exception* @return array of {@link moodle1_handler} instances*/protected static function get_plugin_handlers($type, moodle1_converter $converter) {global $CFG;$handlers = array();$plugins = core_component::get_plugin_list($type);foreach ($plugins as $name => $dir) {$handlerfile = $dir . '/backup/moodle1/lib.php';$handlerclass = "moodle1_{$type}_{$name}_handler";if (file_exists($handlerfile)) {require_once($handlerfile);} elseif ($type == 'block') {$handlerclass = "moodle1_block_generic_handler";} else {continue;}if (!class_exists($handlerclass)) {throw new moodle1_convert_exception('missing_handler_class', $handlerclass);}$handlers[] = new $handlerclass($converter, $type, $name);}return $handlers;}}/*** Base backup conversion handler*/abstract class moodle1_handler implements loggable {/** @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;}/*** Log a message using the converter's logging mechanism** @param string $message message text* @param int $level message level {@example backup::LOG_WARNING}* @param null|mixed $a additional information* @param null|int $depth the message depth* @param bool $display whether the message should be sent to the output, too*/public function log($message, $level, $a = null, $depth = null, $display = false) {$this->converter->log($message, $level, $a, $depth, $display);}}/*** 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*/protected function open_xml_writer($filename) {if (!is_null($this->xmlfilename) and $filename !== $this->xmlfilename) {throw new moodle1_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 moodle1_convert_exception('unable_create_target_directory', $directory);}$this->xmlwriter = new xml_writer(new file_xml_output($fullpath), new moodle1_xml_transformer());$this->xmlwriter->start();}}/*** Close the XML writer** At the moment, the caller must close all tags before calling** @return void*/protected function close_xml_writer() {if ($this->xmlwriter instanceof xml_writer) {$this->xmlwriter->stop();}unset($this->xmlwriter);$this->xmlwriter = null;$this->xmlfilename = null;}/*** Checks if the XML writer has been opened by {@link self::open_xml_writer()}** @return bool*/protected function has_xml_writer() {if ($this->xmlwriter instanceof xml_writer) {return true;} else {return false;}}/*** 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* @param string $parent used internally during the recursion, do not set yourself*/protected function write_xml($element, array $data, array $attribs = array(), $parent = '/') {if (!$this->has_xml_writer()) {throw new moodle1_convert_exception('write_xml_without_writer');}$mypath = $parent . $element;$myattribs = array();// detect properties that should be rendered as element's attributes instead of childrenforeach ($data as $name => $value) {if (!is_array($value)) {if (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);}/*** Makes sure that a new XML file exists, or creates it itself** This is here so we can check that all XML files that the restore process relies on have* been created by an executed handler. If the file is not found, this method can create it* using the given $rootelement as an empty root container in the file.** @param string $filename relative file name like 'course/course.xml'* @param string|bool $rootelement root element to use, false to not create the file* @param array $content content of the root element* @return bool true is the file existed, false if it did not*/protected function make_sure_xml_exists($filename, $rootelement = false, $content = array()) {$existed = file_exists($this->converter->get_workdir_path().'/'.$filename);if ($existed) {return true;}if ($rootelement !== false) {$this->open_xml_writer($filename);$this->write_xml($rootelement, $content);$this->close_xml_writer();}return false;}}/*** Process the root element of the backup file*/class moodle1_root_handler extends moodle1_xml_handler {public function get_paths() {return array(new convert_path('root_element', '/MOODLE_BACKUP'));}/*** Converts course_files and site_files*/public function on_root_element_start() {// convert course files$fileshandler = new moodle1_files_handler($this->converter);$fileshandler->process();}/*** This is executed at the end of the moodle.xml parsing*/public function on_root_element_end() {global $CFG;// restore the stashes prepared by other handlers for us$backupinfo = $this->converter->get_stash('backup_info');$originalcourseinfo = $this->converter->get_stash('original_course_info');////////////////////////////////////////////////////////////////////////// write moodle_backup.xml////////////////////////////////////////////////////////////////////////$this->open_xml_writer('moodle_backup.xml');$this->xmlwriter->begin_tag('moodle_backup');$this->xmlwriter->begin_tag('information');// moodle_backup/information$this->xmlwriter->full_tag('name', $backupinfo['name']);$this->xmlwriter->full_tag('moodle_version', $backupinfo['moodle_version']);$this->xmlwriter->full_tag('moodle_release', $backupinfo['moodle_release']);$this->xmlwriter->full_tag('backup_version', $CFG->backup_version); // {@see restore_prechecks_helper::execute_prechecks}$this->xmlwriter->full_tag('backup_release', $CFG->backup_release);$this->xmlwriter->full_tag('backup_date', $backupinfo['date']);// see the commit c0543b - all backups created in 1.9 and later declare the// information or it is considered as falseif (isset($backupinfo['mnet_remoteusers'])) {$this->xmlwriter->full_tag('mnet_remoteusers', $backupinfo['mnet_remoteusers']);} else {$this->xmlwriter->full_tag('mnet_remoteusers', false);}$this->xmlwriter->full_tag('original_wwwroot', $backupinfo['original_wwwroot']);// {@see backup_general_helper::backup_is_samesite()}if (isset($backupinfo['original_site_identifier_hash'])) {$this->xmlwriter->full_tag('original_site_identifier_hash', $backupinfo['original_site_identifier_hash']);} else {$this->xmlwriter->full_tag('original_site_identifier_hash', null);}$this->xmlwriter->full_tag('original_course_id', $originalcourseinfo['original_course_id']);$this->xmlwriter->full_tag('original_course_fullname', $originalcourseinfo['original_course_fullname']);$this->xmlwriter->full_tag('original_course_shortname', $originalcourseinfo['original_course_shortname']);$this->xmlwriter->full_tag('original_course_startdate', $originalcourseinfo['original_course_startdate']);$this->xmlwriter->full_tag('original_system_contextid', $this->converter->get_contextid(CONTEXT_SYSTEM));// note that even though we have original_course_contextid available, we regenerate the// original course contextid using our helper method to be sure that the data are consistent// within the MBZ file$this->xmlwriter->full_tag('original_course_contextid', $this->converter->get_contextid(CONTEXT_COURSE));// moodle_backup/information/details$this->xmlwriter->begin_tag('details');$this->write_xml('detail', array('backup_id' => $this->converter->get_id(),'type' => backup::TYPE_1COURSE,'format' => backup::FORMAT_MOODLE,'interactive' => backup::INTERACTIVE_YES,'mode' => backup::MODE_CONVERTED,'execution' => backup::EXECUTION_INMEDIATE,'executiontime' => 0,), array('/detail/backup_id'));$this->xmlwriter->end_tag('details');// moodle_backup/information/contents$this->xmlwriter->begin_tag('contents');// moodle_backup/information/contents/activities$this->xmlwriter->begin_tag('activities');$activitysettings = array();foreach ($this->converter->get_stash('coursecontents') as $activity) {$modinfo = $this->converter->get_stash('modinfo_'.$activity['modulename']);$modinstance = $modinfo['instances'][$activity['instanceid']];$this->write_xml('activity', array('moduleid' => $activity['cmid'],'sectionid' => $activity['sectionid'],'modulename' => $activity['modulename'],'title' => $modinstance['name'],'directory' => 'activities/'.$activity['modulename'].'_'.$activity['cmid']));$activitysettings[] = array('level' => 'activity','activity' => $activity['modulename'].'_'.$activity['cmid'],'name' => $activity['modulename'].'_'.$activity['cmid'].'_included','value' => (($modinfo['included'] === 'true' and $modinstance['included'] === 'true') ? 1 : 0));$activitysettings[] = array('level' => 'activity','activity' => $activity['modulename'].'_'.$activity['cmid'],'name' => $activity['modulename'].'_'.$activity['cmid'].'_userinfo',//'value' => (($modinfo['userinfo'] === 'true' and $modinstance['userinfo'] === 'true') ? 1 : 0));'value' => 0); // todo hardcoded non-userinfo for now}$this->xmlwriter->end_tag('activities');// moodle_backup/information/contents/sections$this->xmlwriter->begin_tag('sections');$sectionsettings = array();foreach ($this->converter->get_stash_itemids('sectioninfo') as $sectionid) {$sectioninfo = $this->converter->get_stash('sectioninfo', $sectionid);$sectionsettings[] = array('level' => 'section','section' => 'section_'.$sectionid,'name' => 'section_'.$sectionid.'_included','value' => 1);$sectionsettings[] = array('level' => 'section','section' => 'section_'.$sectionid,'name' => 'section_'.$sectionid.'_userinfo','value' => 0); // @todo how to detect this from moodle.xml?$this->write_xml('section', array('sectionid' => $sectionid,'title' => $sectioninfo['number'], // because the title is not available'directory' => 'sections/section_'.$sectionid));}$this->xmlwriter->end_tag('sections');// moodle_backup/information/contents/course$this->write_xml('course', array('courseid' => $originalcourseinfo['original_course_id'],'title' => $originalcourseinfo['original_course_shortname'],'directory' => 'course'));unset($originalcourseinfo);$this->xmlwriter->end_tag('contents');// moodle_backup/information/settings$this->xmlwriter->begin_tag('settings');// fake backup root seetings$rootsettings = array('filename' => $backupinfo['name'],'users' => 0, // @todo how to detect this from moodle.xml?'anonymize' => 0,'role_assignments' => 0,'activities' => 1,'blocks' => 1,'filters' => 0,'comments' => 0,'userscompletion' => 0,'logs' => 0,'grade_histories' => 0,);unset($backupinfo);foreach ($rootsettings as $name => $value) {$this->write_xml('setting', array('level' => 'root','name' => $name,'value' => $value));}unset($rootsettings);// activity settings populated aboveforeach ($activitysettings as $activitysetting) {$this->write_xml('setting', $activitysetting);}unset($activitysettings);// section settings populated aboveforeach ($sectionsettings as $sectionsetting) {$this->write_xml('setting', $sectionsetting);}unset($sectionsettings);$this->xmlwriter->end_tag('settings');$this->xmlwriter->end_tag('information');$this->xmlwriter->end_tag('moodle_backup');$this->close_xml_writer();////////////////////////////////////////////////////////////////////////// write files.xml////////////////////////////////////////////////////////////////////////$this->open_xml_writer('files.xml');$this->xmlwriter->begin_tag('files');foreach ($this->converter->get_stash_itemids('files') as $fileid) {$this->write_xml('file', $this->converter->get_stash('files', $fileid), array('/file/id'));}$this->xmlwriter->end_tag('files');$this->close_xml_writer('files.xml');////////////////////////////////////////////////////////////////////////// write scales.xml////////////////////////////////////////////////////////////////////////$this->open_xml_writer('scales.xml');$this->xmlwriter->begin_tag('scales_definition');foreach ($this->converter->get_stash_itemids('scales') as $scaleid) {$this->write_xml('scale', $this->converter->get_stash('scales', $scaleid), array('/scale/id'));}$this->xmlwriter->end_tag('scales_definition');$this->close_xml_writer('scales.xml');////////////////////////////////////////////////////////////////////////// write course/inforef.xml////////////////////////////////////////////////////////////////////////$this->open_xml_writer('course/inforef.xml');$this->xmlwriter->begin_tag('inforef');$this->xmlwriter->begin_tag('fileref');// legacy course files$fileids = $this->converter->get_stash('course_files_ids');if (is_array($fileids)) {foreach ($fileids as $fileid) {$this->write_xml('file', array('id' => $fileid));}}// todo site files// course summary files$fileids = $this->converter->get_stash('course_summary_files_ids');if (is_array($fileids)) {foreach ($fileids as $fileid) {$this->write_xml('file', array('id' => $fileid));}}$this->xmlwriter->end_tag('fileref');$this->xmlwriter->begin_tag('question_categoryref');foreach ($this->converter->get_stash_itemids('question_categories') as $questioncategoryid) {$this->write_xml('question_category', array('id' => $questioncategoryid));}$this->xmlwriter->end_tag('question_categoryref');$this->xmlwriter->end_tag('inforef');$this->close_xml_writer();// make sure that the files required by the restore process have been generated.// missing file may happen if the watched tag is not present in moodle.xml (for example// QUESTION_CATEGORIES is optional in moodle.xml but questions.xml must exist in// moodle2 format) or the handler has not been implemented yet.// apparently this must be called after the handler had a chance to create the file.$this->make_sure_xml_exists('questions.xml', 'question_categories');$this->make_sure_xml_exists('groups.xml', 'groups');$this->make_sure_xml_exists('outcomes.xml', 'outcomes_definition');$this->make_sure_xml_exists('users.xml', 'users');$this->make_sure_xml_exists('course/roles.xml', 'roles',array('role_assignments' => array(), 'role_overrides' => array()));$this->make_sure_xml_exists('course/enrolments.xml', 'enrolments',array('enrols' => array()));}}/*** The class responsible for course and site files migration** @todo migrate site_files*/class moodle1_files_handler extends moodle1_xml_handler {/*** Migrates course_files and site_files in the converter workdir*/public function process() {$this->migrate_course_files();// todo $this->migrate_site_files();}/*** Migrates course_files in the converter workdir*/protected function migrate_course_files() {$ids = array();$fileman = $this->converter->get_file_manager($this->converter->get_contextid(CONTEXT_COURSE), 'course', 'legacy');$this->converter->set_stash('course_files_ids', array());if (file_exists($this->converter->get_tempdir_path().'/course_files')) {$ids = $fileman->migrate_directory('course_files');$this->converter->set_stash('course_files_ids', $ids);}$this->log('course files migrated', backup::LOG_INFO, count($ids));}}/*** 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 {/** @var array list of mod names included in info_details */protected $modnames = array();/** @var array the in-memory cache of the currently parsed info_details_mod element */protected $currentmod;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'),);}/*** Stashes the backup info for later processing by {@link moodle1_root_handler}*/public function process_info($data) {$this->converter->set_stash('backup_info', $data);}/*** Initializes the in-memory cache for the current mod*/public function process_info_details_mod($data) {$this->currentmod = $data;$this->currentmod['instances'] = array();}/*** Appends the current instance data to the temporary in-memory cache*/public function process_info_details_mod_instance($data) {$this->currentmod['instances'][$data['id']] = $data;}/*** Stashes the backup info for later processing by {@link moodle1_root_handler}*/public function on_info_details_mod_end($data) {global $CFG;// keep only such modules that seem to have the support for moodle1 implemented$modname = $this->currentmod['name'];if (file_exists($CFG->dirroot.'/mod/'.$modname.'/backup/moodle1/lib.php')) {$this->converter->set_stash('modinfo_'.$modname, $this->currentmod);$this->modnames[] = $modname;} else {$this->log('unsupported activity module', backup::LOG_WARNING, $modname);}$this->currentmod = array();}/*** Stashes the list of activity module types for later processing by {@link moodle1_root_handler}*/public function on_info_details_end() {$this->converter->set_stash('modnameslist', $this->modnames);}}/*** 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' => 2,'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);// 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;// migrate files embedded into the course summary and stash their ids$fileman = $this->converter->get_file_manager($contextid, 'course', 'summary');$this->course['summary'] = moodle1_converter::migrate_referenced_files($this->course['summary'], $fileman);$this->converter->set_stash('course_summary_files_ids', $fileman->get_fileids());// write course.xml$this->open_xml_writer('course/course.xml');$this->write_xml('course', $this->course, array('/course/id', '/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 ordered list of the course contents */protected $coursecontents = array();/** @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_sections', '/MOODLE_BACKUP/COURSE/SECTIONS'),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,'availability' => null,'visibleold' => 1,'showdescription' => 0,),'dropfields' => array('instance','roles_overrides','roles_assignments',),'renamefields' => array('type' => 'modulename',),)),new convert_path('course_modules', '/MOODLE_BACKUP/COURSE/MODULES'),// todo new convert_path('course_module_roles_overrides', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'),// todo new convert_path('course_module_roles_assignments', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_ASSIGNMENTS'),);}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;// check that this type of module should be included in the mbz$modinfo = $this->converter->get_stash_itemids('modinfo_'.$data['modulename']);if (empty($modinfo)) {return;}// add the course module into the course contents list$this->coursecontents[$data['id']] = array('cmid' => $data['id'],'instanceid' => $raw['INSTANCE'],'sectionid' => $this->currentsection['id'],'modulename' => $data['modulename'],'title' => null);// add the course module id into the section's sequenceif (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)) {$plugin = new stdClass();$plugin->version = null;$module = $plugin;include($versionfile);// Have to hardcode - since quiz uses some hardcoded version numbers when restoring.// This is the lowest number used minus one.$data['version'] = 2011010099;} 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 and dump it into// the module.xml file$this->converter->set_stash('cminfo_'.$data['modulename'], $data, $raw['INSTANCE']);}/*** Writes sections/section_xxx/section.xml file and stashes it, too*/public function on_course_section_end() {// migrate files embedded into the section summary field$contextid = $this->converter->get_contextid(CONTEXT_COURSE);$fileman = $this->converter->get_file_manager($contextid, 'course', 'section', $this->currentsection['id']);$this->currentsection['summary'] = moodle1_converter::migrate_referenced_files($this->currentsection['summary'], $fileman);// write section's inforef.xml with the file references$this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/inforef.xml');$this->xmlwriter->begin_tag('inforef');$this->xmlwriter->begin_tag('fileref');$fileids = $fileman->get_fileids();if (is_array($fileids)) {foreach ($fileids as $fileid) {$this->write_xml('file', array('id' => $fileid));}}$this->xmlwriter->end_tag('fileref');$this->xmlwriter->end_tag('inforef');$this->close_xml_writer();// stash the section info and write section.xml$this->converter->set_stash('sectioninfo', $this->currentsection, $this->currentsection['id']);$this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/section.xml');$this->write_xml('section', $this->currentsection);$this->close_xml_writer();unset($this->currentsection);}/*** Stashes the course contents*/public function on_course_sections_end() {$this->converter->set_stash('coursecontents', $this->coursecontents);}/*** Writes the information collected by mod handlers*/public function on_course_modules_end() {foreach ($this->converter->get_stash('modnameslist') as $modname) {$modinfo = $this->converter->get_stash('modinfo_'.$modname);foreach ($modinfo['instances'] as $modinstanceid => $modinstance) {$cminfo = $this->converter->get_stash('cminfo_'.$modname, $modinstanceid);$directory = 'activities/'.$modname.'_'.$cminfo['id'];// write module.xml$this->open_xml_writer($directory.'/module.xml');$this->write_xml('module', $cminfo, array('/module/id', '/module/version'));$this->close_xml_writer();// write grades.xml$this->open_xml_writer($directory.'/grades.xml');$this->xmlwriter->begin_tag('activity_gradebook');$gradeitems = $this->converter->get_stash_or_default('gradebook_modgradeitem_'.$modname, $modinstanceid, array());if (!empty($gradeitems)) {$this->xmlwriter->begin_tag('grade_items');foreach ($gradeitems as $gradeitem) {$this->write_xml('grade_item', $gradeitem, array('/grade_item/id'));}$this->xmlwriter->end_tag('grade_items');}$this->write_xml('grade_letters', array()); // no grade_letters in module context in Moodle 1.9$this->xmlwriter->end_tag('activity_gradebook');$this->close_xml_writer();// todo: write proper roles.xml, for now we just make sure the file is present$this->make_sure_xml_exists($directory.'/roles.xml', 'roles');}}}}/*** Handles the conversion of the defined roles*/class moodle1_roles_definition_handler extends moodle1_xml_handler {/*** Where the roles are defined in the source moodle.xml*/public function get_paths() {return array(new convert_path('roles', '/MOODLE_BACKUP/ROLES'),new convert_path('roles_role', '/MOODLE_BACKUP/ROLES/ROLE',array('newfields' => array('description' => '','sortorder' => 0,'archetype' => ''))));}/*** If there are any roles defined in moodle.xml, convert them to roles.xml*/public function process_roles_role($data) {if (!$this->has_xml_writer()) {$this->open_xml_writer('roles.xml');$this->xmlwriter->begin_tag('roles_definition');}if (!isset($data['nameincourse'])) {$data['nameincourse'] = null;}$this->write_xml('role', $data, array('role/id'));}/*** Finishes writing roles.xml*/public function on_roles_end() {if (!$this->has_xml_writer()) {// no roles defined in moodle.xml so {link self::process_roles_role()}// was never executed$this->open_xml_writer('roles.xml');$this->write_xml('roles_definition', array());} else {// some roles were dumped into the file, let us close their wrapper now$this->xmlwriter->end_tag('roles_definition');}$this->close_xml_writer();}}/*** Handles the conversion of the question bank included in the moodle.xml file*/class moodle1_question_bank_handler extends moodle1_xml_handler {/** @var array the current question category being parsed */protected $currentcategory = null;/** @var array of the raw data for the current category */protected $currentcategoryraw = null;/** @var moodle1_file_manager instance used to convert question images */protected $fileman = null;/** @var bool are the currentcategory data already written (this is a work around MDL-27693) */private $currentcategorywritten = false;/** @var bool was the <questions> tag already written (work around MDL-27693) */private $questionswrapperwritten = false;/** @var array holds the instances of qtype specific conversion handlers */private $qtypehandlers = null;/*** Return the file manager instance used.** @return moodle1_file_manager*/public function get_file_manager() {return $this->fileman;}/*** Returns the information about the question category context being currently parsed** @return array with keys contextid, contextlevel and contextinstanceid*/public function get_current_category_context() {return $this->currentcategory;}/*** Registers path that are not qtype-specific*/public function get_paths() {$paths = array(new convert_path('question_categories', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES'),new convert_path('question_category', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY',array('newfields' => array('infoformat' => 0))),new convert_path('question_category_context', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/CONTEXT'),new convert_path('questions', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS'),// the question element must be grouped so we can re-dispatch it to the qtype handler as a wholenew convert_path('question', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION', array(), true),);// annotate all question subpaths required by the qtypes subplugins$subpaths = array();foreach ($this->get_qtype_handler('*') as $qtypehandler) {foreach ($qtypehandler->get_question_subpaths() as $subpath) {$subpaths[$subpath] = true;}}foreach (array_keys($subpaths) as $subpath) {$name = 'subquestion_'.strtolower(str_replace('/', '_', $subpath));$path = '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION/'.$subpath;$paths[] = new convert_path($name, $path);}return $paths;}/*** Starts writing questions.xml and prepares the file manager instance*/public function on_question_categories_start() {$this->open_xml_writer('questions.xml');$this->xmlwriter->begin_tag('question_categories');if (is_null($this->fileman)) {$this->fileman = $this->converter->get_file_manager();}}/*** Initializes the current category cache*/public function on_question_category_start() {$this->currentcategory = array();$this->currentcategoryraw = array();$this->currentcategorywritten = false;$this->questionswrapperwritten = false;}/*** Populates the current question category data** Bacuse of the known subpath-in-the-middle problem (CONTEXT in this case), this is actually* called twice for both halves of the data. We merge them here into the currentcategory array.*/public function process_question_category($data, $raw) {$this->currentcategory = array_merge($this->currentcategory, $data);$this->currentcategoryraw = array_merge($this->currentcategoryraw, $raw);}/*** Inject the context related information into the current category*/public function process_question_category_context($data) {switch ($data['level']) {case 'module':$this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_MODULE, $data['instance']);$this->currentcategory['contextlevel'] = CONTEXT_MODULE;$this->currentcategory['contextinstanceid'] = $data['instance'];break;case 'course':$originalcourseinfo = $this->converter->get_stash('original_course_info');$originalcourseid = $originalcourseinfo['original_course_id'];$this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSE);$this->currentcategory['contextlevel'] = CONTEXT_COURSE;$this->currentcategory['contextinstanceid'] = $originalcourseid;break;case 'coursecategory':// this is a bit hacky. the source moodle.xml defines COURSECATEGORYLEVEL as a distance// of the course category (1 = parent category, 2 = grand-parent category etc). We pretend// that this level*10 is the id of that category and create an artifical contextid for it$this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSECAT, $data['coursecategorylevel'] * 10);$this->currentcategory['contextlevel'] = CONTEXT_COURSECAT;$this->currentcategory['contextinstanceid'] = $data['coursecategorylevel'] * 10;break;case 'system':$this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_SYSTEM);$this->currentcategory['contextlevel'] = CONTEXT_SYSTEM;$this->currentcategory['contextinstanceid'] = 0;break;}}/*** Writes the common <question> data and re-dispateches the whole grouped* <QUESTION> data to the qtype for appending its qtype specific data processing** @param array $data* @param array $raw* @return array*/public function process_question(array $data, array $raw) {global $CFG;// firstly make sure that the category data and the <questions> wrapper are written// note that because of MDL-27693 we can't use {@link self::process_question_category()}// and {@link self::on_questions_start()} to do soif (empty($this->currentcategorywritten)) {$this->xmlwriter->begin_tag('question_category', array('id' => $this->currentcategory['id']));foreach ($this->currentcategory as $name => $value) {if ($name === 'id') {continue;}$this->xmlwriter->full_tag($name, $value);}$this->currentcategorywritten = true;}if (empty($this->questionswrapperwritten)) {$this->xmlwriter->begin_tag('questions');$this->questionswrapperwritten = true;}$qtype = $data['qtype'];// replay the upgrade step 2008050700 {@see question_fix_random_question_parents()}if ($qtype == 'random' and $data['parent'] <> $data['id']) {$data['parent'] = $data['id'];}// replay the upgrade step 2010080900 and part of 2010080901$data['generalfeedbackformat'] = $data['questiontextformat'];$data['oldquestiontextformat'] = $data['questiontextformat'];if ($CFG->texteditors !== 'textarea') {$data['questiontext'] = text_to_html($data['questiontext'], false, false, true);$data['questiontextformat'] = FORMAT_HTML;$data['generalfeedback'] = text_to_html($data['generalfeedback'], false, false, true);$data['generalfeedbackformat'] = FORMAT_HTML;}// Migrate files in questiontext.$this->fileman->contextid = $this->currentcategory['contextid'];$this->fileman->component = 'question';$this->fileman->filearea = 'questiontext';$this->fileman->itemid = $data['id'];$data['questiontext'] = moodle1_converter::migrate_referenced_files($data['questiontext'], $this->fileman);// Migrate files in generalfeedback.$this->fileman->filearea = 'generalfeedback';$data['generalfeedback'] = moodle1_converter::migrate_referenced_files($data['generalfeedback'], $this->fileman);// replay the upgrade step 2010080901 - updating question imageif (!empty($data['image'])) {if (core_text::substr(core_text::strtolower($data['image']), 0, 7) == 'http://') {// it is a link, appending to existing question text$data['questiontext'] .= ' <img src="' . $data['image'] . '" />';} else {// it is a file in course_files$filename = basename($data['image']);$filepath = dirname($data['image']);if (empty($filepath) or $filepath == '.' or $filepath == '/') {$filepath = '/';} else {// append /$filepath = '/'.trim($filepath, './@#$ ').'/';}if (file_exists($this->converter->get_tempdir_path().'/course_files'.$filepath.$filename)) {$this->fileman->contextid = $this->currentcategory['contextid'];$this->fileman->component = 'question';$this->fileman->filearea = 'questiontext';$this->fileman->itemid = $data['id'];$this->fileman->migrate_file('course_files'.$filepath.$filename, '/', $filename);// note this is slightly different from the upgrade code as we put the file into the// root folder here. this makes our life easier as we do not need to create all the// directories within the specified filearea/itemid$data['questiontext'] .= ' <img src="@@PLUGINFILE@@/' . $filename . '" />';} else {$this->log('question file not found', backup::LOG_WARNING, array($data['id'], $filepath.$filename));}}}unset($data['image']);// replay the upgrade step 2011060301 - Rename field defaultgrade on table question to defaultmark$data['defaultmark'] = $data['defaultgrade'];// write the common question data$this->xmlwriter->begin_tag('question', array('id' => $data['id']));foreach (array('parent', 'name', 'questiontext', 'questiontextformat','generalfeedback', 'generalfeedbackformat', 'defaultmark','penalty', 'qtype', 'length', 'stamp', 'version', 'hidden','timecreated', 'timemodified', 'createdby', 'modifiedby') as $fieldname) {if (!array_key_exists($fieldname, $data)) {throw new moodle1_convert_exception('missing_common_question_field', $fieldname);}$this->xmlwriter->full_tag($fieldname, $data[$fieldname]);}// unless we know that the given qtype does not append any own structures,// give the handler a chance to do so nowif (!in_array($qtype, array('description', 'random'))) {$handler = $this->get_qtype_handler($qtype);if ($handler === false) {$this->log('question type converter not found', backup::LOG_ERROR, $qtype);} else {$this->xmlwriter->begin_tag('plugin_qtype_'.$qtype.'_question');$handler->use_xml_writer($this->xmlwriter);$handler->process_question($data, $raw);$this->xmlwriter->end_tag('plugin_qtype_'.$qtype.'_question');}}$this->xmlwriter->end_tag('question');}/*** Closes the questions wrapper*/public function on_questions_end() {if ($this->questionswrapperwritten) {$this->xmlwriter->end_tag('questions');}}/*** Closes the question_category and annotates the category id* so that it can be dumped into course/inforef.xml*/public function on_question_category_end() {// make sure that the category data were written by {@link self::process_question()}// if not, write it now. this may happen when the current category does not contain any// questions so the subpaths is missing completelyif (empty($this->currentcategorywritten)) {$this->write_xml('question_category', $this->currentcategory, array('/question_category/id'));} else {$this->xmlwriter->end_tag('question_category');}$this->converter->set_stash('question_categories', $this->currentcategory, $this->currentcategory['id']);}/*** Stops writing questions.xml*/public function on_question_categories_end() {$this->xmlwriter->end_tag('question_categories');$this->close_xml_writer();}/*** Provides access to the qtype handlers** Returns either list of all qtype handler instances (if passed '*') or a particular handler* for the given qtype or false if the qtype is not supported.** @throws moodle1_convert_exception* @param string $qtype the name of the question type or '*' for returning all* @return array|moodle1_qtype_handler|bool*/protected function get_qtype_handler($qtype) {if (is_null($this->qtypehandlers)) {// initialize the list of qtype handler instances$this->qtypehandlers = array();foreach (core_component::get_plugin_list('qtype') as $qtypename => $qtypelocation) {$filename = $qtypelocation.'/backup/moodle1/lib.php';if (file_exists($filename)) {$classname = 'moodle1_qtype_'.$qtypename.'_handler';require_once($filename);if (!class_exists($classname)) {throw new moodle1_convert_exception('missing_handler_class', $classname);}$this->log('registering handler', backup::LOG_DEBUG, $classname, 2);$this->qtypehandlers[$qtypename] = new $classname($this, $qtypename);}}}if ($qtype === '*') {return $this->qtypehandlers;} else if (isset($this->qtypehandlers[$qtype])) {return $this->qtypehandlers[$qtype];} else {return false;}}}/*** Handles the conversion of the scales included in the moodle.xml file*/class moodle1_scales_handler extends moodle1_handler {/** @var moodle1_file_manager instance used to convert question images */protected $fileman = null;/*** Registers paths*/public function get_paths() {return array(new convert_path('scales', '/MOODLE_BACKUP/COURSE/SCALES'),new convert_path('scale', '/MOODLE_BACKUP/COURSE/SCALES/SCALE',array('renamefields' => array('scaletext' => 'scale',),'addfields' => array('descriptionformat' => 0,))),);}/*** Prepare the file manager for the files embedded in the scale description field*/public function on_scales_start() {$syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM);$this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'scale');}/*** This is executed every time we have one <SCALE> data available** @param array $data* @param array $raw* @return array*/public function process_scale(array $data, array $raw) {global $CFG;// replay upgrade step 2009110400if ($CFG->texteditors !== 'textarea') {$data['description'] = text_to_html($data['description'], false, false, true);$data['descriptionformat'] = FORMAT_HTML;}// convert course files embedded into the scale description field$this->fileman->itemid = $data['id'];$data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman);// stash the scale$this->converter->set_stash('scales', $data, $data['id']);}}/*** Handles the conversion of the outcomes*/class moodle1_outcomes_handler extends moodle1_xml_handler {/** @var moodle1_file_manager instance used to convert images embedded into outcome descriptions */protected $fileman = null;/*** Registers paths*/public function get_paths() {return array(new convert_path('gradebook_grade_outcomes', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES'),new convert_path('gradebook_grade_outcome', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES/GRADE_OUTCOME',array('addfields' => array('descriptionformat' => FORMAT_MOODLE,),)),);}/*** Prepares the file manager and starts writing outcomes.xml*/public function on_gradebook_grade_outcomes_start() {$syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM);$this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'outcome');$this->open_xml_writer('outcomes.xml');$this->xmlwriter->begin_tag('outcomes_definition');}/*** Processes GRADE_OUTCOME tags progressively*/public function process_gradebook_grade_outcome(array $data, array $raw) {global $CFG;// replay the upgrade step 2009110400if ($CFG->texteditors !== 'textarea') {$data['description'] = text_to_html($data['description'], false, false, true);$data['descriptionformat'] = FORMAT_HTML;}// convert course files embedded into the outcome description field$this->fileman->itemid = $data['id'];$data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman);// write the outcome data$this->write_xml('outcome', $data, array('/outcome/id'));return $data;}/*** Closes outcomes.xml*/public function on_gradebook_grade_outcomes_end() {$this->xmlwriter->end_tag('outcomes_definition');$this->close_xml_writer();}}/*** Handles the conversion of the gradebook structures in the moodle.xml file*/class moodle1_gradebook_handler extends moodle1_xml_handler {/** @var array of (int)gradecategoryid => (int|null)parentcategoryid */protected $categoryparent = array();/*** Registers paths*/public function get_paths() {return array(new convert_path('gradebook', '/MOODLE_BACKUP/COURSE/GRADEBOOK'),new convert_path('gradebook_grade_letter', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_LETTERS/GRADE_LETTER'),new convert_path('gradebook_grade_category', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_CATEGORIES/GRADE_CATEGORY',array('addfields' => array('hidden' => 0, // upgrade step 2010011200),)),new convert_path('gradebook_grade_item', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM'),new convert_path('gradebook_grade_item_grades', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM/GRADE_GRADES'),);}/*** Initializes the in-memory structures** This should not be needed actually as the moodle.xml contains just one GRADEBOOK* element. But who knows - maybe someone will want to write a mass conversion* tool in the future (not me definitely ;-)*/public function on_gradebook_start() {$this->categoryparent = array();}/*** Processes one GRADE_LETTER data** In Moodle 1.9, all grade_letters are from course context only. Therefore* we put them here.*/public function process_gradebook_grade_letter(array $data, array $raw) {$this->converter->set_stash('gradebook_gradeletter', $data, $data['id']);}/*** Processes one GRADE_CATEGORY data*/public function process_gradebook_grade_category(array $data, array $raw) {$this->categoryparent[$data['id']] = $data['parent'];$this->converter->set_stash('gradebook_gradecategory', $data, $data['id']);}/*** Processes one GRADE_ITEM data*/public function process_gradebook_grade_item(array $data, array $raw) {// here we use get_nextid() to get a nondecreasing sequence$data['sortorder'] = $this->converter->get_nextid();if ($data['itemtype'] === 'mod') {return $this->process_mod_grade_item($data, $raw);} else if (in_array($data['itemtype'], array('manual', 'course', 'category'))) {return $this->process_nonmod_grade_item($data, $raw);} else {$this->log('unsupported grade_item type', backup::LOG_ERROR, $data['itemtype']);}}/*** Processes one GRADE_ITEM of the type 'mod'*/protected function process_mod_grade_item(array $data, array $raw) {$stashname = 'gradebook_modgradeitem_'.$data['itemmodule'];$stashitemid = $data['iteminstance'];$gradeitems = $this->converter->get_stash_or_default($stashname, $stashitemid, array());// typically there will be single item with itemnumber 0$gradeitems[$data['itemnumber']] = $data;$this->converter->set_stash($stashname, $gradeitems, $stashitemid);return $data;}/*** Processes one GRADE_ITEM of te type 'manual' or 'course' or 'category'*/protected function process_nonmod_grade_item(array $data, array $raw) {$stashname = 'gradebook_nonmodgradeitem';$stashitemid = $data['id'];$this->converter->set_stash($stashname, $data, $stashitemid);return $data;}/*** @todo*/public function on_gradebook_grade_item_grades_start() {}/*** Writes the collected information into gradebook.xml*/public function on_gradebook_end() {$this->open_xml_writer('gradebook.xml');$this->xmlwriter->begin_tag('gradebook');$this->write_grade_categories();$this->write_grade_items();$this->write_grade_letters();$this->xmlwriter->end_tag('gradebook');$this->close_xml_writer();}/*** Writes grade_categories*/protected function write_grade_categories() {$this->xmlwriter->begin_tag('grade_categories');foreach ($this->converter->get_stash_itemids('gradebook_gradecategory') as $gradecategoryid) {$gradecategory = $this->converter->get_stash('gradebook_gradecategory', $gradecategoryid);$path = $this->calculate_category_path($gradecategoryid);$gradecategory['depth'] = count($path);$gradecategory['path'] = '/'.implode('/', $path).'/';$this->write_xml('grade_category', $gradecategory, array('/grade_category/id'));}$this->xmlwriter->end_tag('grade_categories');}/*** Calculates the path to the grade_category** Moodle 1.9 backup does not store the grade_category's depth and path. This method is used* to repopulate this information using the $this->categoryparent values.** @param int $categoryid* @return array of ids including the categoryid*/protected function calculate_category_path($categoryid) {if (!array_key_exists($categoryid, $this->categoryparent)) {throw new moodle1_convert_exception('gradebook_unknown_categoryid', null, $categoryid);}$path = array($categoryid);$parent = $this->categoryparent[$categoryid];while (!is_null($parent)) {array_unshift($path, $parent);$parent = $this->categoryparent[$parent];if (in_array($parent, $path)) {throw new moodle1_convert_exception('circular_reference_in_categories_tree');}}return $path;}/*** Writes grade_items*/protected function write_grade_items() {$this->xmlwriter->begin_tag('grade_items');foreach ($this->converter->get_stash_itemids('gradebook_nonmodgradeitem') as $gradeitemid) {$gradeitem = $this->converter->get_stash('gradebook_nonmodgradeitem', $gradeitemid);$this->write_xml('grade_item', $gradeitem, array('/grade_item/id'));}$this->xmlwriter->end_tag('grade_items');}/*** Writes grade_letters*/protected function write_grade_letters() {$this->xmlwriter->begin_tag('grade_letters');foreach ($this->converter->get_stash_itemids('gradebook_gradeletter') as $gradeletterid) {$gradeletter = $this->converter->get_stash('gradebook_gradeletter', $gradeletterid);$this->write_xml('grade_letter', $gradeletter, array('/grade_letter/id'));}$this->xmlwriter->end_tag('grade_letters');}}/*** Shared base class for activity modules, blocks and qtype 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;}/*** Returns the normalized name of the plugin, eg mod_workshop** @return string*/public function get_component_name() {return $this->plugintype.'_'.$this->pluginname;}}/*** Base class for all question type handlers*/abstract class moodle1_qtype_handler extends moodle1_plugin_handler {/** @var moodle1_question_bank_handler */protected $qbankhandler;/*** Returns the list of paths within one <QUESTION> that this qtype needs to have included* in the grouped question structure** @return array of strings*/public function get_question_subpaths() {return array();}/*** Gives the qtype handler a chance to write converted data into questions.xml** @param array $data grouped question data* @param array $raw grouped raw QUESTION data*/public function process_question(array $data, array $raw) {}/*** Converts the answers and writes them into the questions.xml** The structure "answers" is used by several qtypes. It contains data from {question_answers} table.** @param array $answers as parsed by the grouped parser in moodle.xml* @param string $qtype containing the answers*/protected function write_answers(array $answers, $qtype) {$this->xmlwriter->begin_tag('answers');foreach ($answers as $elementname => $elements) {foreach ($elements as $element) {$answer = $this->convert_answer($element, $qtype);// Migrate images in answertext.if ($answer['answerformat'] == FORMAT_HTML) {$answer['answertext'] = $this->migrate_files($answer['answertext'], 'question', 'answer', $answer['id']);}// Migrate images in feedback.if ($answer['feedbackformat'] == FORMAT_HTML) {$answer['feedback'] = $this->migrate_files($answer['feedback'], 'question', 'answerfeedback', $answer['id']);}$this->write_xml('answer', $answer, array('/answer/id'));}}$this->xmlwriter->end_tag('answers');}/*** Migrate files belonging to one qtype plugin text field.** @param array $text the html fragment containing references to files* @param string $component the component for restored files* @param string $filearea the file area for restored files* @param int $itemid the itemid for restored files** @return string the text for this field, after files references have been processed*/protected function migrate_files($text, $component, $filearea, $itemid) {$context = $this->qbankhandler->get_current_category_context();$fileman = $this->qbankhandler->get_file_manager();$fileman->contextid = $context['contextid'];$fileman->component = $component;$fileman->filearea = $filearea;$fileman->itemid = $itemid;$text = moodle1_converter::migrate_referenced_files($text, $fileman);return $text;}/*** Writes the grouped numerical_units structure** @param array $numericalunits*/protected function write_numerical_units(array $numericalunits) {$this->xmlwriter->begin_tag('numerical_units');foreach ($numericalunits as $elementname => $elements) {foreach ($elements as $element) {$element['id'] = $this->converter->get_nextid();$this->write_xml('numerical_unit', $element, array('/numerical_unit/id'));}}$this->xmlwriter->end_tag('numerical_units');}/*** Writes the numerical_options structure** @see get_default_numerical_options()* @param array $numericaloption*/protected function write_numerical_options(array $numericaloption) {$this->xmlwriter->begin_tag('numerical_options');if (!empty($numericaloption)) {$this->write_xml('numerical_option', $numericaloption, array('/numerical_option/id'));}$this->xmlwriter->end_tag('numerical_options');}/*** Returns default numerical_option structure** This structure is not present in moodle.xml, we create a new artificial one here.** @see write_numerical_options()* @param int $oldquestiontextformat* @return array*/protected function get_default_numerical_options($oldquestiontextformat, $units) {global $CFG;// replay the upgrade step 2009100100 - new table$options = array('id' => $this->converter->get_nextid(),'instructions' => null,'instructionsformat' => 0,'showunits' => 0,'unitsleft' => 0,'unitgradingtype' => 0,'unitpenalty' => 0.1);// replay the upgrade step 2009100101if ($CFG->texteditors !== 'textarea' and $oldquestiontextformat == FORMAT_MOODLE) {$options['instructionsformat'] = FORMAT_HTML;} else {$options['instructionsformat'] = $oldquestiontextformat;}// Set a good default, depending on whether there are any units defined.if (empty($units)) {$options['showunits'] = 3;}return $options;}/*** Writes the dataset_definitions structure** @param array $datasetdefinitions array of dataset_definition structures*/protected function write_dataset_definitions(array $datasetdefinitions) {$this->xmlwriter->begin_tag('dataset_definitions');foreach ($datasetdefinitions as $datasetdefinition) {$this->xmlwriter->begin_tag('dataset_definition', array('id' => $this->converter->get_nextid()));foreach (array('category', 'name', 'type', 'options', 'itemcount') as $element) {$this->xmlwriter->full_tag($element, $datasetdefinition[$element]);}$this->xmlwriter->begin_tag('dataset_items');if (!empty($datasetdefinition['dataset_items']['dataset_item'])) {foreach ($datasetdefinition['dataset_items']['dataset_item'] as $datasetitem) {$datasetitem['id'] = $this->converter->get_nextid();$this->write_xml('dataset_item', $datasetitem, array('/dataset_item/id'));}}$this->xmlwriter->end_tag('dataset_items');$this->xmlwriter->end_tag('dataset_definition');}$this->xmlwriter->end_tag('dataset_definitions');}/// implementation details follow //////////////////////////////////////////public function __construct(moodle1_question_bank_handler $qbankhandler, $qtype) {parent::__construct($qbankhandler->get_converter(), 'qtype', $qtype);$this->qbankhandler = $qbankhandler;}/*** @see self::get_question_subpaths()*/final public function get_paths() {throw new moodle1_convert_exception('qtype_handler_get_paths');}/*** Question type handlers cannot open the xml_writer*/final protected function open_xml_writer($filename) {throw new moodle1_convert_exception('opening_xml_writer_forbidden');}/*** Question type handlers cannot close the xml_writer*/final protected function close_xml_writer() {throw new moodle1_convert_exception('opening_xml_writer_forbidden');}/*** Provides a xml_writer instance to this qtype converter** @param xml_writer $xmlwriter*/public function use_xml_writer(xml_writer $xmlwriter) {$this->xmlwriter = $xmlwriter;}/*** Converts <ANSWER> structure into the new <answer> one** See question_backup_answers() in 1.9 and add_question_question_answers() in 2.0** @param array $old the parsed answer array in moodle.xml* @param string $qtype the question type the answer is part of* @return array*/private function convert_answer(array $old, $qtype) {global $CFG;$new = array();$new['id'] = $old['id'];$new['answertext'] = $old['answer_text'];$new['answerformat'] = 0; // upgrade step 2010080900$new['fraction'] = $old['fraction'];$new['feedback'] = $old['feedback'];$new['feedbackformat'] = 0; // upgrade step 2010080900// replay upgrade step 2010080901if ($qtype !== 'multichoice') {$new['answerformat'] = FORMAT_PLAIN;} else {$new['answertext'] = text_to_html($new['answertext'], false, false, true);$new['answerformat'] = FORMAT_HTML;}if ($CFG->texteditors !== 'textarea') {if ($qtype == 'essay') {$new['feedback'] = text_to_html($new['feedback'], false, false, true);}$new['feedbackformat'] = FORMAT_HTML;} else {$new['feedbackformat'] = FORMAT_MOODLE;}return $new;}}/*** Base class for activity module handlers*/abstract class moodle1_mod_handler extends moodle1_plugin_handler {/*** Returns the name of the module, eg. 'forum'** @return string*/public function get_modname() {return $this->pluginname;}/*** Returns course module information for the given instance id** The information for this instance id has been stashed by* {@link moodle1_course_outline_handler::process_course_module()}** @param int $instance the module instance id* @param string $modname the module type, defaults to $this->pluginname* @return int*/protected function get_cminfo($instance, $modname = null) {if (is_null($modname)) {$modname = $this->pluginname;}return $this->converter->get_stash('cminfo_'.$modname, $instance);}}/*** Base class for all modules that are successors of the 1.9 resource module*/abstract class moodle1_resource_successor_handler extends moodle1_mod_handler {/*** Resource successors do not attach to paths themselves, they are called explicitely* by moodle1_mod_resource_handler** @return array*/final public function get_paths() {return array();}/*** Converts /MOODLE_BACKUP/COURSE/MODULES/MOD/RESOURCE data** Called by {@link moodle1_mod_resource_handler::process_resource()}** @param array $data pre-cooked legacy resource data* @param array $raw raw legacy resource data*/public function process_legacy_resource(array $data, array $raw = null) {}/*** Called when the parses reaches the end </MOD> resource tag** @param array $data the data returned by {@link self::process_resource} or just pre-cooked*/public function on_legacy_resource_end(array $data) {}}/*** Base class for block handlers*/abstract class moodle1_block_handler extends moodle1_plugin_handler {public function get_paths() {$blockname = strtoupper($this->pluginname);return array(new convert_path('block', "/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/{$blockname}"),);}public function process_block(array $data) {$newdata = $this->convert_common_block_data($data);$this->write_block_xml($newdata, $data);$this->write_inforef_xml($newdata, $data);$this->write_roles_xml($newdata, $data);return $data;}protected function convert_common_block_data(array $olddata) {$newdata = array();$newdata['blockname'] = $olddata['name'];$newdata['parentcontextid'] = $this->converter->get_contextid(CONTEXT_COURSE, 0);$newdata['showinsubcontexts'] = 0;$newdata['pagetypepattern'] = $olddata['pagetype'].='-*';$newdata['subpagepattern'] = null;$newdata['defaultregion'] = ($olddata['position']=='l')?'side-pre':'side-post';$newdata['defaultweight'] = $olddata['weight'];$newdata['configdata'] = $this->convert_configdata($olddata);return $newdata;}protected function convert_configdata(array $olddata) {return $olddata['configdata'];}protected function write_block_xml($newdata, $data) {$contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']);$this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/block.xml");$this->xmlwriter->begin_tag('block', array('id' => $data['id'], 'contextid' => $contextid));foreach ($newdata as $field => $value) {$this->xmlwriter->full_tag($field, $value);}$this->xmlwriter->begin_tag('block_positions');$this->xmlwriter->begin_tag('block_position', array('id' => 1));$this->xmlwriter->full_tag('contextid', $newdata['parentcontextid']);$this->xmlwriter->full_tag('pagetype', $data['pagetype']);$this->xmlwriter->full_tag('subpage', '');$this->xmlwriter->full_tag('visible', $data['visible']);$this->xmlwriter->full_tag('region', $newdata['defaultregion']);$this->xmlwriter->full_tag('weight', $newdata['defaultweight']);$this->xmlwriter->end_tag('block_position');$this->xmlwriter->end_tag('block_positions');$this->xmlwriter->end_tag('block');$this->close_xml_writer();}protected function write_inforef_xml($newdata, $data) {$this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml");$this->xmlwriter->begin_tag('inforef');// Subclasses may provide inforef contents if needed$this->xmlwriter->end_tag('inforef');$this->close_xml_writer();}protected function write_roles_xml($newdata, $data) {// This is an empty shell, as the moodle1 converter doesn't handle user data.$this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/roles.xml");$this->xmlwriter->begin_tag('roles');$this->xmlwriter->full_tag('role_overrides', '');$this->xmlwriter->full_tag('role_assignments', '');$this->xmlwriter->end_tag('roles');$this->close_xml_writer();}}/*** Base class for block generic handler*/class moodle1_block_generic_handler extends moodle1_block_handler {}/*** Base class for the activity modules' subplugins*/abstract class moodle1_submod_handler extends moodle1_plugin_handler {/** @var moodle1_mod_handler */protected $parenthandler;/*** @param moodle1_mod_handler $parenthandler the handler of a module we are subplugin of* @param string $subplugintype the type of the subplugin* @param string $subpluginname the name of the subplugin*/public function __construct(moodle1_mod_handler $parenthandler, $subplugintype, $subpluginname) {$this->parenthandler = $parenthandler;parent::__construct($parenthandler->converter, $subplugintype, $subpluginname);}/*** Activity module subplugins can't declare any paths to handle** The paths must be registered by the parent module and then re-dispatched to the* relevant subplugins for eventual processing.** @return array empty array*/final public function get_paths() {return array();}}