|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00030 defined('MOODLE_INTERNAL') || die(); 00031 00032 require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php'); 00033 require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); 00034 require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php'); 00035 00039 abstract class moodle1_handlers_factory { 00040 00045 public static function get_handlers(moodle1_converter $converter) { 00046 00047 $handlers = array( 00048 new moodle1_root_handler($converter), 00049 new moodle1_info_handler($converter), 00050 new moodle1_course_header_handler($converter), 00051 new moodle1_course_outline_handler($converter), 00052 new moodle1_roles_definition_handler($converter), 00053 new moodle1_question_bank_handler($converter), 00054 new moodle1_scales_handler($converter), 00055 new moodle1_outcomes_handler($converter), 00056 new moodle1_gradebook_handler($converter), 00057 ); 00058 00059 $handlers = array_merge($handlers, self::get_plugin_handlers('mod', $converter)); 00060 $handlers = array_merge($handlers, self::get_plugin_handlers('block', $converter)); 00061 00062 // make sure that all handlers have expected class 00063 foreach ($handlers as $handler) { 00064 if (!$handler instanceof moodle1_handler) { 00065 throw new moodle1_convert_exception('wrong_handler_class', get_class($handler)); 00066 } 00067 } 00068 00069 return $handlers; 00070 } 00071 00073 00083 protected static function get_plugin_handlers($type, moodle1_converter $converter) { 00084 global $CFG; 00085 00086 $handlers = array(); 00087 $plugins = get_plugin_list($type); 00088 foreach ($plugins as $name => $dir) { 00089 $handlerfile = $dir . '/backup/moodle1/lib.php'; 00090 $handlerclass = "moodle1_{$type}_{$name}_handler"; 00091 if (!file_exists($handlerfile)) { 00092 continue; 00093 } 00094 require_once($handlerfile); 00095 00096 if (!class_exists($handlerclass)) { 00097 throw new moodle1_convert_exception('missing_handler_class', $handlerclass); 00098 } 00099 $handlers[] = new $handlerclass($converter, $type, $name); 00100 } 00101 return $handlers; 00102 } 00103 } 00104 00105 00109 abstract class moodle1_handler implements loggable { 00110 00112 protected $converter; 00113 00117 public function __construct(moodle1_converter $converter) { 00118 $this->converter = $converter; 00119 } 00120 00124 public function get_converter() { 00125 return $this->converter; 00126 } 00127 00137 public function log($message, $level, $a = null, $depth = null, $display = false) { 00138 $this->converter->log($message, $level, $a, $depth, $display); 00139 } 00140 } 00141 00142 00146 abstract class moodle1_xml_handler extends moodle1_handler { 00147 00149 protected $xmlfilename; 00150 00152 protected $xmlwriter; 00153 00160 protected function open_xml_writer($filename) { 00161 00162 if (!is_null($this->xmlfilename) and $filename !== $this->xmlfilename) { 00163 throw new moodle1_convert_exception('xml_writer_already_opened_for_other_file', $this->xmlfilename); 00164 } 00165 00166 if (!$this->xmlwriter instanceof xml_writer) { 00167 $this->xmlfilename = $filename; 00168 $fullpath = $this->converter->get_workdir_path() . '/' . $this->xmlfilename; 00169 $directory = pathinfo($fullpath, PATHINFO_DIRNAME); 00170 00171 if (!check_dir_exists($directory)) { 00172 throw new moodle1_convert_exception('unable_create_target_directory', $directory); 00173 } 00174 $this->xmlwriter = new xml_writer(new file_xml_output($fullpath), new moodle1_xml_transformer()); 00175 $this->xmlwriter->start(); 00176 } 00177 } 00178 00186 protected function close_xml_writer() { 00187 if ($this->xmlwriter instanceof xml_writer) { 00188 $this->xmlwriter->stop(); 00189 } 00190 unset($this->xmlwriter); 00191 $this->xmlwriter = null; 00192 $this->xmlfilename = null; 00193 } 00194 00200 protected function has_xml_writer() { 00201 00202 if ($this->xmlwriter instanceof xml_writer) { 00203 return true; 00204 } else { 00205 return false; 00206 } 00207 } 00208 00217 protected function write_xml($element, array $data, array $attribs = array(), $parent = '/') { 00218 00219 if (!$this->has_xml_writer()) { 00220 throw new moodle1_convert_exception('write_xml_without_writer'); 00221 } 00222 00223 $mypath = $parent . $element; 00224 $myattribs = array(); 00225 00226 // detect properties that should be rendered as element's attributes instead of children 00227 foreach ($data as $name => $value) { 00228 if (!is_array($value)) { 00229 if (in_array($mypath . '/' . $name, $attribs)) { 00230 $myattribs[$name] = $value; 00231 unset($data[$name]); 00232 } 00233 } 00234 } 00235 00236 // reorder the $data so that all sub-branches are at the end (needed by our parser) 00237 $leaves = array(); 00238 $branches = array(); 00239 foreach ($data as $name => $value) { 00240 if (is_array($value)) { 00241 $branches[$name] = $value; 00242 } else { 00243 $leaves[$name] = $value; 00244 } 00245 } 00246 $data = array_merge($leaves, $branches); 00247 00248 $this->xmlwriter->begin_tag($element, $myattribs); 00249 00250 foreach ($data as $name => $value) { 00251 if (is_array($value)) { 00252 // recursively call self 00253 $this->write_xml($name, $value, $attribs, $mypath.'/'); 00254 } else { 00255 $this->xmlwriter->full_tag($name, $value); 00256 } 00257 } 00258 00259 $this->xmlwriter->end_tag($element); 00260 } 00261 00274 protected function make_sure_xml_exists($filename, $rootelement = false, $content = array()) { 00275 00276 $existed = file_exists($this->converter->get_workdir_path().'/'.$filename); 00277 00278 if ($existed) { 00279 return true; 00280 } 00281 00282 if ($rootelement !== false) { 00283 $this->open_xml_writer($filename); 00284 $this->write_xml($rootelement, $content); 00285 $this->close_xml_writer(); 00286 } 00287 00288 return false; 00289 } 00290 } 00291 00292 00296 class moodle1_root_handler extends moodle1_xml_handler { 00297 00298 public function get_paths() { 00299 return array(new convert_path('root_element', '/MOODLE_BACKUP')); 00300 } 00301 00305 public function on_root_element_start() { 00306 00307 // convert course files 00308 $fileshandler = new moodle1_files_handler($this->converter); 00309 $fileshandler->process(); 00310 } 00311 00315 public function on_root_element_end() { 00316 global $CFG; 00317 00318 // restore the stashes prepared by other handlers for us 00319 $backupinfo = $this->converter->get_stash('backup_info'); 00320 $originalcourseinfo = $this->converter->get_stash('original_course_info'); 00321 00323 // write moodle_backup.xml 00325 $this->open_xml_writer('moodle_backup.xml'); 00326 00327 $this->xmlwriter->begin_tag('moodle_backup'); 00328 $this->xmlwriter->begin_tag('information'); 00329 00330 // moodle_backup/information 00331 $this->xmlwriter->full_tag('name', $backupinfo['name']); 00332 $this->xmlwriter->full_tag('moodle_version', $backupinfo['moodle_version']); 00333 $this->xmlwriter->full_tag('moodle_release', $backupinfo['moodle_release']); 00334 $this->xmlwriter->full_tag('backup_version', $CFG->backup_version); // {@see restore_prechecks_helper::execute_prechecks} 00335 $this->xmlwriter->full_tag('backup_release', $CFG->backup_release); 00336 $this->xmlwriter->full_tag('backup_date', $backupinfo['date']); 00337 // see the commit c0543b - all backups created in 1.9 and later declare the 00338 // information or it is considered as false 00339 if (isset($backupinfo['mnet_remoteusers'])) { 00340 $this->xmlwriter->full_tag('mnet_remoteusers', $backupinfo['mnet_remoteusers']); 00341 } else { 00342 $this->xmlwriter->full_tag('mnet_remoteusers', false); 00343 } 00344 $this->xmlwriter->full_tag('original_wwwroot', $backupinfo['original_wwwroot']); 00345 // {@see backup_general_helper::backup_is_samesite()} 00346 if (isset($backupinfo['original_site_identifier_hash'])) { 00347 $this->xmlwriter->full_tag('original_site_identifier_hash', $backupinfo['original_site_identifier_hash']); 00348 } else { 00349 $this->xmlwriter->full_tag('original_site_identifier_hash', null); 00350 } 00351 $this->xmlwriter->full_tag('original_course_id', $originalcourseinfo['original_course_id']); 00352 $this->xmlwriter->full_tag('original_course_fullname', $originalcourseinfo['original_course_fullname']); 00353 $this->xmlwriter->full_tag('original_course_shortname', $originalcourseinfo['original_course_shortname']); 00354 $this->xmlwriter->full_tag('original_course_startdate', $originalcourseinfo['original_course_startdate']); 00355 $this->xmlwriter->full_tag('original_system_contextid', $this->converter->get_contextid(CONTEXT_SYSTEM)); 00356 // note that even though we have original_course_contextid available, we regenerate the 00357 // original course contextid using our helper method to be sure that the data are consistent 00358 // within the MBZ file 00359 $this->xmlwriter->full_tag('original_course_contextid', $this->converter->get_contextid(CONTEXT_COURSE)); 00360 00361 // moodle_backup/information/details 00362 $this->xmlwriter->begin_tag('details'); 00363 $this->write_xml('detail', array( 00364 'backup_id' => $this->converter->get_id(), 00365 'type' => backup::TYPE_1COURSE, 00366 'format' => backup::FORMAT_MOODLE, 00367 'interactive' => backup::INTERACTIVE_YES, 00368 'mode' => backup::MODE_CONVERTED, 00369 'execution' => backup::EXECUTION_INMEDIATE, 00370 'executiontime' => 0, 00371 ), array('/detail/backup_id')); 00372 $this->xmlwriter->end_tag('details'); 00373 00374 // moodle_backup/information/contents 00375 $this->xmlwriter->begin_tag('contents'); 00376 00377 // moodle_backup/information/contents/activities 00378 $this->xmlwriter->begin_tag('activities'); 00379 $activitysettings = array(); 00380 foreach ($this->converter->get_stash('coursecontents') as $activity) { 00381 $modinfo = $this->converter->get_stash('modinfo_'.$activity['modulename']); 00382 $modinstance = $modinfo['instances'][$activity['instanceid']]; 00383 $this->write_xml('activity', array( 00384 'moduleid' => $activity['cmid'], 00385 'sectionid' => $activity['sectionid'], 00386 'modulename' => $activity['modulename'], 00387 'title' => $modinstance['name'], 00388 'directory' => 'activities/'.$activity['modulename'].'_'.$activity['cmid'] 00389 )); 00390 $activitysettings[] = array( 00391 'level' => 'activity', 00392 'activity' => $activity['modulename'].'_'.$activity['cmid'], 00393 'name' => $activity['modulename'].'_'.$activity['cmid'].'_included', 00394 'value' => (($modinfo['included'] === 'true' and $modinstance['included'] === 'true') ? 1 : 0)); 00395 $activitysettings[] = array( 00396 'level' => 'activity', 00397 'activity' => $activity['modulename'].'_'.$activity['cmid'], 00398 'name' => $activity['modulename'].'_'.$activity['cmid'].'_userinfo', 00399 //'value' => (($modinfo['userinfo'] === 'true' and $modinstance['userinfo'] === 'true') ? 1 : 0)); 00400 'value' => 0); // todo hardcoded non-userinfo for now 00401 } 00402 $this->xmlwriter->end_tag('activities'); 00403 00404 // moodle_backup/information/contents/sections 00405 $this->xmlwriter->begin_tag('sections'); 00406 $sectionsettings = array(); 00407 foreach ($this->converter->get_stash_itemids('sectioninfo') as $sectionid) { 00408 $sectioninfo = $this->converter->get_stash('sectioninfo', $sectionid); 00409 $sectionsettings[] = array( 00410 'level' => 'section', 00411 'section' => 'section_'.$sectionid, 00412 'name' => 'section_'.$sectionid.'_included', 00413 'value' => 1); 00414 $sectionsettings[] = array( 00415 'level' => 'section', 00416 'section' => 'section_'.$sectionid, 00417 'name' => 'section_'.$sectionid.'_userinfo', 00418 'value' => 0); // @todo how to detect this from moodle.xml? 00419 $this->write_xml('section', array( 00420 'sectionid' => $sectionid, 00421 'title' => $sectioninfo['number'], // because the title is not available 00422 'directory' => 'sections/section_'.$sectionid)); 00423 } 00424 $this->xmlwriter->end_tag('sections'); 00425 00426 // moodle_backup/information/contents/course 00427 $this->write_xml('course', array( 00428 'courseid' => $originalcourseinfo['original_course_id'], 00429 'title' => $originalcourseinfo['original_course_shortname'], 00430 'directory' => 'course')); 00431 unset($originalcourseinfo); 00432 00433 $this->xmlwriter->end_tag('contents'); 00434 00435 // moodle_backup/information/settings 00436 $this->xmlwriter->begin_tag('settings'); 00437 00438 // fake backup root seetings 00439 $rootsettings = array( 00440 'filename' => $backupinfo['name'], 00441 'users' => 0, // @todo how to detect this from moodle.xml? 00442 'anonymize' => 0, 00443 'role_assignments' => 0, 00444 'user_files' => 0, 00445 'activities' => 1, 00446 'blocks' => 1, 00447 'filters' => 0, 00448 'comments' => 0, 00449 'userscompletion' => 0, 00450 'logs' => 0, 00451 'grade_histories' => 0, 00452 ); 00453 unset($backupinfo); 00454 foreach ($rootsettings as $name => $value) { 00455 $this->write_xml('setting', array( 00456 'level' => 'root', 00457 'name' => $name, 00458 'value' => $value)); 00459 } 00460 unset($rootsettings); 00461 00462 // activity settings populated above 00463 foreach ($activitysettings as $activitysetting) { 00464 $this->write_xml('setting', $activitysetting); 00465 } 00466 unset($activitysettings); 00467 00468 // section settings populated above 00469 foreach ($sectionsettings as $sectionsetting) { 00470 $this->write_xml('setting', $sectionsetting); 00471 } 00472 unset($sectionsettings); 00473 00474 $this->xmlwriter->end_tag('settings'); 00475 00476 $this->xmlwriter->end_tag('information'); 00477 $this->xmlwriter->end_tag('moodle_backup'); 00478 00479 $this->close_xml_writer(); 00480 00482 // write files.xml 00484 $this->open_xml_writer('files.xml'); 00485 $this->xmlwriter->begin_tag('files'); 00486 foreach ($this->converter->get_stash_itemids('files') as $fileid) { 00487 $this->write_xml('file', $this->converter->get_stash('files', $fileid), array('/file/id')); 00488 } 00489 $this->xmlwriter->end_tag('files'); 00490 $this->close_xml_writer('files.xml'); 00491 00493 // write scales.xml 00495 $this->open_xml_writer('scales.xml'); 00496 $this->xmlwriter->begin_tag('scales_definition'); 00497 foreach ($this->converter->get_stash_itemids('scales') as $scaleid) { 00498 $this->write_xml('scale', $this->converter->get_stash('scales', $scaleid), array('/scale/id')); 00499 } 00500 $this->xmlwriter->end_tag('scales_definition'); 00501 $this->close_xml_writer('scales.xml'); 00502 00504 // write course/inforef.xml 00506 $this->open_xml_writer('course/inforef.xml'); 00507 $this->xmlwriter->begin_tag('inforef'); 00508 00509 $this->xmlwriter->begin_tag('fileref'); 00510 // legacy course files 00511 $fileids = $this->converter->get_stash('course_files_ids'); 00512 if (is_array($fileids)) { 00513 foreach ($fileids as $fileid) { 00514 $this->write_xml('file', array('id' => $fileid)); 00515 } 00516 } 00517 // todo site files 00518 // course summary files 00519 $fileids = $this->converter->get_stash('course_summary_files_ids'); 00520 if (is_array($fileids)) { 00521 foreach ($fileids as $fileid) { 00522 $this->write_xml('file', array('id' => $fileid)); 00523 } 00524 } 00525 $this->xmlwriter->end_tag('fileref'); 00526 00527 $this->xmlwriter->begin_tag('question_categoryref'); 00528 foreach ($this->converter->get_stash_itemids('question_categories') as $questioncategoryid) { 00529 $this->write_xml('question_category', array('id' => $questioncategoryid)); 00530 } 00531 $this->xmlwriter->end_tag('question_categoryref'); 00532 00533 $this->xmlwriter->end_tag('inforef'); 00534 $this->close_xml_writer(); 00535 00536 // make sure that the files required by the restore process have been generated. 00537 // missing file may happen if the watched tag is not present in moodle.xml (for example 00538 // QUESTION_CATEGORIES is optional in moodle.xml but questions.xml must exist in 00539 // moodle2 format) or the handler has not been implemented yet. 00540 // apparently this must be called after the handler had a chance to create the file. 00541 $this->make_sure_xml_exists('questions.xml', 'question_categories'); 00542 $this->make_sure_xml_exists('groups.xml', 'groups'); 00543 $this->make_sure_xml_exists('outcomes.xml', 'outcomes_definition'); 00544 $this->make_sure_xml_exists('users.xml', 'users'); 00545 $this->make_sure_xml_exists('course/roles.xml', 'roles', 00546 array('role_assignments' => array(), 'role_overrides' => array())); 00547 $this->make_sure_xml_exists('course/enrolments.xml', 'enrolments', 00548 array('enrols' => array())); 00549 } 00550 } 00551 00552 00558 class moodle1_files_handler extends moodle1_xml_handler { 00559 00563 public function process() { 00564 $this->migrate_course_files(); 00565 // todo $this->migrate_site_files(); 00566 } 00567 00571 protected function migrate_course_files() { 00572 $ids = array(); 00573 $fileman = $this->converter->get_file_manager($this->converter->get_contextid(CONTEXT_COURSE), 'course', 'legacy'); 00574 $this->converter->set_stash('course_files_ids', array()); 00575 if (file_exists($this->converter->get_tempdir_path().'/course_files')) { 00576 $ids = $fileman->migrate_directory('course_files'); 00577 $this->converter->set_stash('course_files_ids', $ids); 00578 } 00579 $this->log('course files migrated', backup::LOG_INFO, count($ids)); 00580 } 00581 } 00582 00583 00590 class moodle1_info_handler extends moodle1_handler { 00591 00593 protected $modnames = array(); 00594 00596 protected $currentmod; 00597 00598 public function get_paths() { 00599 return array( 00600 new convert_path('info', '/MOODLE_BACKUP/INFO'), 00601 new convert_path('info_details', '/MOODLE_BACKUP/INFO/DETAILS'), 00602 new convert_path('info_details_mod', '/MOODLE_BACKUP/INFO/DETAILS/MOD'), 00603 new convert_path('info_details_mod_instance', '/MOODLE_BACKUP/INFO/DETAILS/MOD/INSTANCES/INSTANCE'), 00604 ); 00605 } 00606 00610 public function process_info($data) { 00611 $this->converter->set_stash('backup_info', $data); 00612 } 00613 00617 public function process_info_details_mod($data) { 00618 $this->currentmod = $data; 00619 $this->currentmod['instances'] = array(); 00620 } 00621 00625 public function process_info_details_mod_instance($data) { 00626 $this->currentmod['instances'][$data['id']] = $data; 00627 } 00628 00632 public function on_info_details_mod_end($data) { 00633 global $CFG; 00634 00635 // keep only such modules that seem to have the support for moodle1 implemented 00636 $modname = $this->currentmod['name']; 00637 if (file_exists($CFG->dirroot.'/mod/'.$modname.'/backup/moodle1/lib.php')) { 00638 $this->converter->set_stash('modinfo_'.$modname, $this->currentmod); 00639 $this->modnames[] = $modname; 00640 } else { 00641 $this->log('unsupported activity module', backup::LOG_WARNING, $modname); 00642 } 00643 00644 $this->currentmod = array(); 00645 } 00646 00650 public function on_info_details_end() { 00651 $this->converter->set_stash('modnameslist', $this->modnames); 00652 } 00653 } 00654 00655 00659 class moodle1_course_header_handler extends moodle1_xml_handler { 00660 00662 protected $course = array(); 00663 00665 protected $courseraw = array(); 00666 00668 protected $category; 00669 00670 public function get_paths() { 00671 return array( 00672 new convert_path( 00673 'course_header', '/MOODLE_BACKUP/COURSE/HEADER', 00674 array( 00675 'newfields' => array( 00676 'summaryformat' => 1, 00677 'legacyfiles' => 2, 00678 'requested' => 0, // @todo not really new, but maybe never backed up? 00679 'restrictmodules' => 0, 00680 'enablecompletion' => 0, 00681 'completionstartonenrol' => 0, 00682 'completionnotify' => 0, 00683 'tags' => array(), 00684 'allowed_modules' => array(), 00685 ), 00686 'dropfields' => array( 00687 'roles_overrides', 00688 'roles_assignments', 00689 'cost', 00690 'currancy', 00691 'defaultrole', 00692 'enrol', 00693 'enrolenddate', 00694 'enrollable', 00695 'enrolperiod', 00696 'enrolstartdate', 00697 'expirynotify', 00698 'expirythreshold', 00699 'guest', 00700 'notifystudents', 00701 'password', 00702 'student', 00703 'students', 00704 'teacher', 00705 'teachers', 00706 'metacourse', 00707 ) 00708 ) 00709 ), 00710 new convert_path( 00711 'course_header_category', '/MOODLE_BACKUP/COURSE/HEADER/CATEGORY', 00712 array( 00713 'newfields' => array( 00714 'description' => null, 00715 ) 00716 ) 00717 ), 00718 ); 00719 } 00720 00726 public function process_course_header($data, $raw) { 00727 $this->course = array_merge($this->course, $data); 00728 $this->courseraw = array_merge($this->courseraw, $raw); 00729 } 00730 00731 public function process_course_header_category($data) { 00732 $this->category = $data; 00733 } 00734 00735 public function on_course_header_end() { 00736 00737 $contextid = $this->converter->get_contextid(CONTEXT_COURSE); 00738 00739 // stash the information needed by other handlers 00740 $info = array( 00741 'original_course_id' => $this->course['id'], 00742 'original_course_fullname' => $this->course['fullname'], 00743 'original_course_shortname' => $this->course['shortname'], 00744 'original_course_startdate' => $this->course['startdate'], 00745 'original_course_contextid' => $contextid 00746 ); 00747 $this->converter->set_stash('original_course_info', $info); 00748 00749 $this->course['contextid'] = $contextid; 00750 $this->course['category'] = $this->category; 00751 00752 // migrate files embedded into the course summary and stash their ids 00753 $fileman = $this->converter->get_file_manager($contextid, 'course', 'summary'); 00754 $this->course['summary'] = moodle1_converter::migrate_referenced_files($this->course['summary'], $fileman); 00755 $this->converter->set_stash('course_summary_files_ids', $fileman->get_fileids()); 00756 00757 // write course.xml 00758 $this->open_xml_writer('course/course.xml'); 00759 $this->write_xml('course', $this->course, array('/course/id', '/course/contextid')); 00760 $this->close_xml_writer(); 00761 } 00762 } 00763 00764 00768 class moodle1_course_outline_handler extends moodle1_xml_handler { 00769 00771 protected $coursecontents = array(); 00772 00774 protected $currentsection; 00775 00779 public function get_paths() { 00780 return array( 00781 new convert_path('course_sections', '/MOODLE_BACKUP/COURSE/SECTIONS'), 00782 new convert_path( 00783 'course_section', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION', 00784 array( 00785 'newfields' => array( 00786 'name' => null, 00787 'summaryformat' => 1, 00788 'sequence' => null, 00789 ), 00790 ) 00791 ), 00792 new convert_path( 00793 'course_module', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD', 00794 array( 00795 'newfields' => array( 00796 'completion' => 0, 00797 'completiongradeitemnumber' => null, 00798 'completionview' => 0, 00799 'completionexpected' => 0, 00800 'availablefrom' => 0, 00801 'availableuntil' => 0, 00802 'showavailability' => 0, 00803 'availability_info' => array(), 00804 'visibleold' => 1, 00805 'showdescription' => 0, 00806 ), 00807 'dropfields' => array( 00808 'instance', 00809 'roles_overrides', 00810 'roles_assignments', 00811 ), 00812 'renamefields' => array( 00813 'type' => 'modulename', 00814 ), 00815 ) 00816 ), 00817 new convert_path('course_modules', '/MOODLE_BACKUP/COURSE/MODULES'), 00818 // todo new convert_path('course_module_roles_overrides', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'), 00819 // todo new convert_path('course_module_roles_assignments', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_ASSIGNMENTS'), 00820 ); 00821 } 00822 00823 public function process_course_section($data) { 00824 $this->currentsection = $data; 00825 } 00826 00831 public function process_course_module($data, $raw) { 00832 global $CFG; 00833 00834 // check that this type of module should be included in the mbz 00835 $modinfo = $this->converter->get_stash_itemids('modinfo_'.$data['modulename']); 00836 if (empty($modinfo)) { 00837 return; 00838 } 00839 00840 // add the course module into the course contents list 00841 $this->coursecontents[$data['id']] = array( 00842 'cmid' => $data['id'], 00843 'instanceid' => $raw['INSTANCE'], 00844 'sectionid' => $this->currentsection['id'], 00845 'modulename' => $data['modulename'], 00846 'title' => null 00847 ); 00848 00849 // add the course module id into the section's sequence 00850 if (is_null($this->currentsection['sequence'])) { 00851 $this->currentsection['sequence'] = $data['id']; 00852 } else { 00853 $this->currentsection['sequence'] .= ',' . $data['id']; 00854 } 00855 00856 // add the sectionid and sectionnumber 00857 $data['sectionid'] = $this->currentsection['id']; 00858 $data['sectionnumber'] = $this->currentsection['number']; 00859 00860 // generate the module version - this is a bit tricky as this information 00861 // is not present in 1.9 backups. we will use the currently installed version 00862 // whenever we can but that might not be accurate for some modules. 00863 // also there might be problem with modules that are not present at the target 00864 // host... 00865 $versionfile = $CFG->dirroot.'/mod/'.$data['modulename'].'/version.php'; 00866 if (file_exists($versionfile)) { 00867 include($versionfile); 00868 $data['version'] = $module->version; 00869 } else { 00870 $data['version'] = null; 00871 } 00872 00873 // stash the course module info in stashes like 'cminfo_forum' with 00874 // itemid set to the instance id. this is needed so that module handlers 00875 // can later obtain information about the course module and dump it into 00876 // the module.xml file 00877 $this->converter->set_stash('cminfo_'.$data['modulename'], $data, $raw['INSTANCE']); 00878 } 00879 00883 public function on_course_section_end() { 00884 00885 // migrate files embedded into the section summary field 00886 $contextid = $this->converter->get_contextid(CONTEXT_COURSE); 00887 $fileman = $this->converter->get_file_manager($contextid, 'course', 'section', $this->currentsection['id']); 00888 $this->currentsection['summary'] = moodle1_converter::migrate_referenced_files($this->currentsection['summary'], $fileman); 00889 00890 // write section's inforef.xml with the file references 00891 $this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/inforef.xml'); 00892 $this->xmlwriter->begin_tag('inforef'); 00893 $this->xmlwriter->begin_tag('fileref'); 00894 $fileids = $fileman->get_fileids(); 00895 if (is_array($fileids)) { 00896 foreach ($fileids as $fileid) { 00897 $this->write_xml('file', array('id' => $fileid)); 00898 } 00899 } 00900 $this->xmlwriter->end_tag('fileref'); 00901 $this->xmlwriter->end_tag('inforef'); 00902 $this->close_xml_writer(); 00903 00904 // stash the section info and write section.xml 00905 $this->converter->set_stash('sectioninfo', $this->currentsection, $this->currentsection['id']); 00906 $this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/section.xml'); 00907 $this->write_xml('section', $this->currentsection); 00908 $this->close_xml_writer(); 00909 unset($this->currentsection); 00910 } 00911 00915 public function on_course_sections_end() { 00916 $this->converter->set_stash('coursecontents', $this->coursecontents); 00917 } 00918 00922 public function on_course_modules_end() { 00923 00924 foreach ($this->converter->get_stash('modnameslist') as $modname) { 00925 $modinfo = $this->converter->get_stash('modinfo_'.$modname); 00926 foreach ($modinfo['instances'] as $modinstanceid => $modinstance) { 00927 $cminfo = $this->converter->get_stash('cminfo_'.$modname, $modinstanceid); 00928 $directory = 'activities/'.$modname.'_'.$cminfo['id']; 00929 00930 // write module.xml 00931 $this->open_xml_writer($directory.'/module.xml'); 00932 $this->write_xml('module', $cminfo, array('/module/id', '/module/version')); 00933 $this->close_xml_writer(); 00934 00935 // write grades.xml 00936 $this->open_xml_writer($directory.'/grades.xml'); 00937 $this->xmlwriter->begin_tag('activity_gradebook'); 00938 $gradeitems = $this->converter->get_stash_or_default('gradebook_modgradeitem_'.$modname, $modinstanceid, array()); 00939 if (!empty($gradeitems)) { 00940 $this->xmlwriter->begin_tag('grade_items'); 00941 foreach ($gradeitems as $gradeitem) { 00942 $this->write_xml('grade_item', $gradeitem, array('/grade_item/id')); 00943 } 00944 $this->xmlwriter->end_tag('grade_items'); 00945 } 00946 $this->write_xml('grade_letters', array()); // no grade_letters in module context in Moodle 1.9 00947 $this->xmlwriter->end_tag('activity_gradebook'); 00948 $this->close_xml_writer(); 00949 00950 // todo: write proper roles.xml, for now we just make sure the file is present 00951 $this->make_sure_xml_exists($directory.'/roles.xml', 'roles'); 00952 } 00953 } 00954 } 00955 } 00956 00957 00961 class moodle1_roles_definition_handler extends moodle1_xml_handler { 00962 00966 public function get_paths() { 00967 return array( 00968 new convert_path('roles', '/MOODLE_BACKUP/ROLES'), 00969 new convert_path( 00970 'roles_role', '/MOODLE_BACKUP/ROLES/ROLE', 00971 array( 00972 'newfields' => array( 00973 'description' => '', 00974 'sortorder' => 0, 00975 'archetype' => '' 00976 ) 00977 ) 00978 ) 00979 ); 00980 } 00981 00985 public function process_roles_role($data) { 00986 00987 if (!$this->has_xml_writer()) { 00988 $this->open_xml_writer('roles.xml'); 00989 $this->xmlwriter->begin_tag('roles_definition'); 00990 } 00991 if (!isset($data['nameincourse'])) { 00992 $data['nameincourse'] = null; 00993 } 00994 $this->write_xml('role', $data, array('role/id')); 00995 } 00996 01000 public function on_roles_end() { 01001 01002 if (!$this->has_xml_writer()) { 01003 // no roles defined in moodle.xml so {link self::process_roles_role()} 01004 // was never executed 01005 $this->open_xml_writer('roles.xml'); 01006 $this->write_xml('roles_definition', array()); 01007 01008 } else { 01009 // some roles were dumped into the file, let us close their wrapper now 01010 $this->xmlwriter->end_tag('roles_definition'); 01011 } 01012 $this->close_xml_writer(); 01013 } 01014 } 01015 01016 01020 class moodle1_question_bank_handler extends moodle1_xml_handler { 01021 01023 protected $currentcategory = null; 01024 01026 protected $currentcategoryraw = null; 01027 01029 protected $fileman = null; 01030 01032 private $currentcategorywritten = false; 01033 01035 private $questionswrapperwritten = false; 01036 01038 private $qtypehandlers = null; 01039 01043 public function get_paths() { 01044 01045 $paths = array( 01046 new convert_path('question_categories', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES'), 01047 new convert_path( 01048 'question_category', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY', 01049 array( 01050 'newfields' => array( 01051 'infoformat' => 0 01052 ) 01053 )), 01054 new convert_path('question_category_context', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/CONTEXT'), 01055 new convert_path('questions', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS'), 01056 // the question element must be grouped so we can re-dispatch it to the qtype handler as a whole 01057 new convert_path('question', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION', array(), true), 01058 ); 01059 01060 // annotate all question subpaths required by the qtypes subplugins 01061 $subpaths = array(); 01062 foreach ($this->get_qtype_handler('*') as $qtypehandler) { 01063 foreach ($qtypehandler->get_question_subpaths() as $subpath) { 01064 $subpaths[$subpath] = true; 01065 } 01066 } 01067 foreach (array_keys($subpaths) as $subpath) { 01068 $name = 'subquestion_'.strtolower(str_replace('/', '_', $subpath)); 01069 $path = '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION/'.$subpath; 01070 $paths[] = new convert_path($name, $path); 01071 } 01072 01073 return $paths; 01074 } 01075 01079 public function on_question_categories_start() { 01080 $this->open_xml_writer('questions.xml'); 01081 $this->xmlwriter->begin_tag('question_categories'); 01082 if (is_null($this->fileman)) { 01083 $this->fileman = $this->converter->get_file_manager(); 01084 } 01085 } 01086 01090 public function on_question_category_start() { 01091 $this->currentcategory = array(); 01092 $this->currentcategoryraw = array(); 01093 $this->currentcategorywritten = false; 01094 $this->questionswrapperwritten = false; 01095 } 01096 01103 public function process_question_category($data, $raw) { 01104 $this->currentcategory = array_merge($this->currentcategory, $data); 01105 $this->currentcategoryraw = array_merge($this->currentcategoryraw, $raw); 01106 } 01107 01111 public function process_question_category_context($data) { 01112 01113 switch ($data['level']) { 01114 case 'module': 01115 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_MODULE, $data['instance']); 01116 $this->currentcategory['contextlevel'] = CONTEXT_MODULE; 01117 $this->currentcategory['contextinstanceid'] = $data['instance']; 01118 break; 01119 case 'course': 01120 $originalcourseinfo = $this->converter->get_stash('original_course_info'); 01121 $originalcourseid = $originalcourseinfo['original_course_id']; 01122 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSE); 01123 $this->currentcategory['contextlevel'] = CONTEXT_COURSE; 01124 $this->currentcategory['contextinstanceid'] = $originalcourseid; 01125 break; 01126 case 'coursecategory': 01127 // this is a bit hacky. the source moodle.xml defines COURSECATEGORYLEVEL as a distance 01128 // of the course category (1 = parent category, 2 = grand-parent category etc). We pretend 01129 // that this level*10 is the id of that category and create an artifical contextid for it 01130 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSECAT, $data['coursecategorylevel'] * 10); 01131 $this->currentcategory['contextlevel'] = CONTEXT_COURSECAT; 01132 $this->currentcategory['contextinstanceid'] = $data['coursecategorylevel'] * 10; 01133 break; 01134 case 'system': 01135 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_SYSTEM); 01136 $this->currentcategory['contextlevel'] = CONTEXT_SYSTEM; 01137 $this->currentcategory['contextinstanceid'] = 0; 01138 break; 01139 } 01140 } 01141 01150 public function process_question(array $data, array $raw) { 01151 global $CFG; 01152 01153 // firstly make sure that the category data and the <questions> wrapper are written 01154 // note that because of MDL-27693 we can't use {@link self::process_question_category()} 01155 // and {@link self::on_questions_start()} to do so 01156 01157 if (empty($this->currentcategorywritten)) { 01158 $this->xmlwriter->begin_tag('question_category', array('id' => $this->currentcategory['id'])); 01159 foreach ($this->currentcategory as $name => $value) { 01160 if ($name === 'id') { 01161 continue; 01162 } 01163 $this->xmlwriter->full_tag($name, $value); 01164 } 01165 $this->currentcategorywritten = true; 01166 } 01167 01168 if (empty($this->questionswrapperwritten)) { 01169 $this->xmlwriter->begin_tag('questions'); 01170 $this->questionswrapperwritten = true; 01171 } 01172 01173 $qtype = $data['qtype']; 01174 01175 // replay the upgrade step 2008050700 {@see question_fix_random_question_parents()} 01176 if ($qtype == 'random' and $data['parent'] <> $data['id']) { 01177 $data['parent'] = $data['id']; 01178 } 01179 01180 // replay the upgrade step 2010080900 and part of 2010080901 01181 $data['generalfeedbackformat'] = $data['questiontextformat']; 01182 $data['oldquestiontextformat'] = $data['questiontextformat']; 01183 01184 if ($CFG->texteditors !== 'textarea') { 01185 $data['questiontext'] = text_to_html($data['questiontext'], false, false, true); 01186 $data['questiontextformat'] = FORMAT_HTML; 01187 $data['generalfeedback'] = text_to_html($data['generalfeedback'], false, false, true); 01188 $data['generalfeedbackformat'] = FORMAT_HTML; 01189 } 01190 01191 // replay the upgrade step 2010080901 - updating question image 01192 if (!empty($data['image'])) { 01193 $textlib = textlib_get_instance(); 01194 if ($textlib->substr($textlib->strtolower($data['image']), 0, 7) == 'http://') { 01195 // it is a link, appending to existing question text 01196 $data['questiontext'] .= ' <img src="' . $data['image'] . '" />'; 01197 01198 } else { 01199 // it is a file in course_files 01200 $filename = basename($data['image']); 01201 $filepath = dirname($data['image']); 01202 if (empty($filepath) or $filepath == '.' or $filepath == '/') { 01203 $filepath = '/'; 01204 } else { 01205 // append / 01206 $filepath = '/'.trim($filepath, './@#$ ').'/'; 01207 } 01208 01209 if (file_exists($this->converter->get_tempdir_path().'/course_files'.$filepath.$filename)) { 01210 $this->fileman->contextid = $this->currentcategory['contextid']; 01211 $this->fileman->component = 'question'; 01212 $this->fileman->filearea = 'questiontext'; 01213 $this->fileman->itemid = $data['id']; 01214 $this->fileman->migrate_file('course_files'.$filepath.$filename, '/', $filename); 01215 // note this is slightly different from the upgrade code as we put the file into the 01216 // root folder here. this makes our life easier as we do not need to create all the 01217 // directories within the specified filearea/itemid 01218 $data['questiontext'] .= ' <img src="@@PLUGINFILE@@/' . $filename . '" />'; 01219 01220 } else { 01221 $this->log('question file not found', backup::LOG_WARNING, array($data['id'], $filepath.$filename)); 01222 } 01223 } 01224 } 01225 unset($data['image']); 01226 01227 // replay the upgrade step 2011060301 - Rename field defaultgrade on table question to defaultmark 01228 $data['defaultmark'] = $data['defaultgrade']; 01229 01230 // write the common question data 01231 $this->xmlwriter->begin_tag('question', array('id' => $data['id'])); 01232 foreach (array( 01233 'parent', 'name', 'questiontext', 'questiontextformat', 01234 'generalfeedback', 'generalfeedbackformat', 'defaultmark', 01235 'penalty', 'qtype', 'length', 'stamp', 'version', 'hidden', 01236 'timecreated', 'timemodified', 'createdby', 'modifiedby' 01237 ) as $fieldname) { 01238 if (!array_key_exists($fieldname, $data)) { 01239 throw new moodle1_convert_exception('missing_common_question_field', $fieldname); 01240 } 01241 $this->xmlwriter->full_tag($fieldname, $data[$fieldname]); 01242 } 01243 // unless we know that the given qtype does not append any own structures, 01244 // give the handler a chance to do so now 01245 if (!in_array($qtype, array('description', 'random'))) { 01246 $handler = $this->get_qtype_handler($qtype); 01247 if ($handler === false) { 01248 $this->log('question type converter not found', backup::LOG_ERROR, $qtype); 01249 01250 } else { 01251 $this->xmlwriter->begin_tag('plugin_qtype_'.$qtype.'_question'); 01252 $handler->use_xml_writer($this->xmlwriter); 01253 $handler->process_question($data, $raw); 01254 $this->xmlwriter->end_tag('plugin_qtype_'.$qtype.'_question'); 01255 } 01256 } 01257 01258 $this->xmlwriter->end_tag('question'); 01259 } 01260 01264 public function on_questions_end() { 01265 $this->xmlwriter->end_tag('questions'); 01266 } 01267 01272 public function on_question_category_end() { 01273 // make sure that the category data were written by {@link self::process_question()} 01274 // if not, write it now. this may happen when the current category does not contain any 01275 // questions so the subpaths is missing completely 01276 if (empty($this->currentcategorywritten)) { 01277 $this->write_xml('question_category', $this->currentcategory, array('/question_category/id')); 01278 } else { 01279 $this->xmlwriter->end_tag('question_category'); 01280 } 01281 $this->converter->set_stash('question_categories', $this->currentcategory, $this->currentcategory['id']); 01282 } 01283 01287 public function on_question_categories_end() { 01288 $this->xmlwriter->end_tag('question_categories'); 01289 $this->close_xml_writer(); 01290 } 01291 01302 protected function get_qtype_handler($qtype) { 01303 01304 if (is_null($this->qtypehandlers)) { 01305 // initialize the list of qtype handler instances 01306 $this->qtypehandlers = array(); 01307 foreach (get_plugin_list('qtype') as $qtypename => $qtypelocation) { 01308 $filename = $qtypelocation.'/backup/moodle1/lib.php'; 01309 if (file_exists($filename)) { 01310 $classname = 'moodle1_qtype_'.$qtypename.'_handler'; 01311 require_once($filename); 01312 if (!class_exists($classname)) { 01313 throw new moodle1_convert_exception('missing_handler_class', $classname); 01314 } 01315 $this->log('registering handler', backup::LOG_DEBUG, $classname, 2); 01316 $this->qtypehandlers[$qtypename] = new $classname($this, $qtypename); 01317 } 01318 } 01319 } 01320 01321 if ($qtype === '*') { 01322 return $this->qtypehandlers; 01323 01324 } else if (isset($this->qtypehandlers[$qtype])) { 01325 return $this->qtypehandlers[$qtype]; 01326 01327 } else { 01328 return false; 01329 } 01330 } 01331 } 01332 01333 01337 class moodle1_scales_handler extends moodle1_handler { 01338 01340 protected $fileman = null; 01341 01345 public function get_paths() { 01346 return array( 01347 new convert_path('scales', '/MOODLE_BACKUP/COURSE/SCALES'), 01348 new convert_path( 01349 'scale', '/MOODLE_BACKUP/COURSE/SCALES/SCALE', 01350 array( 01351 'renamefields' => array( 01352 'scaletext' => 'scale', 01353 ), 01354 'addfields' => array( 01355 'descriptionformat' => 0, 01356 ) 01357 ) 01358 ), 01359 ); 01360 } 01361 01365 public function on_scales_start() { 01366 $syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM); 01367 $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'scale'); 01368 } 01369 01377 public function process_scale(array $data, array $raw) { 01378 global $CFG; 01379 01380 // replay upgrade step 2009110400 01381 if ($CFG->texteditors !== 'textarea') { 01382 $data['description'] = text_to_html($data['description'], false, false, true); 01383 $data['descriptionformat'] = FORMAT_HTML; 01384 } 01385 01386 // convert course files embedded into the scale description field 01387 $this->fileman->itemid = $data['id']; 01388 $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman); 01389 01390 // stash the scale 01391 $this->converter->set_stash('scales', $data, $data['id']); 01392 } 01393 } 01394 01395 01399 class moodle1_outcomes_handler extends moodle1_xml_handler { 01400 01402 protected $fileman = null; 01403 01407 public function get_paths() { 01408 return array( 01409 new convert_path('gradebook_grade_outcomes', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES'), 01410 new convert_path( 01411 'gradebook_grade_outcome', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES/GRADE_OUTCOME', 01412 array( 01413 'addfields' => array( 01414 'descriptionformat' => FORMAT_MOODLE, 01415 ), 01416 ) 01417 ), 01418 ); 01419 } 01420 01424 public function on_gradebook_grade_outcomes_start() { 01425 01426 $syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM); 01427 $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'outcome'); 01428 01429 $this->open_xml_writer('outcomes.xml'); 01430 $this->xmlwriter->begin_tag('outcomes_definition'); 01431 } 01432 01436 public function process_gradebook_grade_outcome(array $data, array $raw) { 01437 global $CFG; 01438 01439 // replay the upgrade step 2009110400 01440 if ($CFG->texteditors !== 'textarea') { 01441 $data['description'] = text_to_html($data['description'], false, false, true); 01442 $data['descriptionformat'] = FORMAT_HTML; 01443 } 01444 01445 // convert course files embedded into the outcome description field 01446 $this->fileman->itemid = $data['id']; 01447 $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman); 01448 01449 // write the outcome data 01450 $this->write_xml('outcome', $data, array('/outcome/id')); 01451 01452 return $data; 01453 } 01454 01458 public function on_gradebook_grade_outcomes_end() { 01459 $this->xmlwriter->end_tag('outcomes_definition'); 01460 $this->close_xml_writer(); 01461 } 01462 } 01463 01464 01468 class moodle1_gradebook_handler extends moodle1_xml_handler { 01469 01471 protected $categoryparent = array(); 01472 01476 public function get_paths() { 01477 return array( 01478 new convert_path('gradebook', '/MOODLE_BACKUP/COURSE/GRADEBOOK'), 01479 new convert_path('gradebook_grade_letter', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_LETTERS/GRADE_LETTER'), 01480 new convert_path( 01481 'gradebook_grade_category', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_CATEGORIES/GRADE_CATEGORY', 01482 array( 01483 'addfields' => array( 01484 'hidden' => 0, // upgrade step 2010011200 01485 ), 01486 ) 01487 ), 01488 new convert_path('gradebook_grade_item', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM'), 01489 new convert_path('gradebook_grade_item_grades', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM/GRADE_GRADES'), 01490 ); 01491 } 01492 01500 public function on_gradebook_start() { 01501 $this->categoryparent = array(); 01502 } 01503 01510 public function process_gradebook_grade_letter(array $data, array $raw) { 01511 $this->converter->set_stash('gradebook_gradeletter', $data, $data['id']); 01512 } 01513 01517 public function process_gradebook_grade_category(array $data, array $raw) { 01518 $this->categoryparent[$data['id']] = $data['parent']; 01519 $this->converter->set_stash('gradebook_gradecategory', $data, $data['id']); 01520 } 01521 01525 public function process_gradebook_grade_item(array $data, array $raw) { 01526 01527 // here we use get_nextid() to get a nondecreasing sequence 01528 $data['sortorder'] = $this->converter->get_nextid(); 01529 01530 if ($data['itemtype'] === 'mod') { 01531 return $this->process_mod_grade_item($data, $raw); 01532 01533 } else if (in_array($data['itemtype'], array('manual', 'course', 'category'))) { 01534 return $this->process_nonmod_grade_item($data, $raw); 01535 01536 } else { 01537 $this->log('unsupported grade_item type', backup::LOG_ERROR, $data['itemtype']); 01538 } 01539 } 01540 01544 protected function process_mod_grade_item(array $data, array $raw) { 01545 01546 $stashname = 'gradebook_modgradeitem_'.$data['itemmodule']; 01547 $stashitemid = $data['iteminstance']; 01548 $gradeitems = $this->converter->get_stash_or_default($stashname, $stashitemid, array()); 01549 01550 // typically there will be single item with itemnumber 0 01551 $gradeitems[$data['itemnumber']] = $data; 01552 01553 $this->converter->set_stash($stashname, $gradeitems, $stashitemid); 01554 01555 return $data; 01556 } 01557 01561 protected function process_nonmod_grade_item(array $data, array $raw) { 01562 01563 $stashname = 'gradebook_nonmodgradeitem'; 01564 $stashitemid = $data['id']; 01565 $this->converter->set_stash($stashname, $data, $stashitemid); 01566 01567 return $data; 01568 } 01569 01573 public function on_gradebook_grade_item_grades_start() { 01574 } 01575 01579 public function on_gradebook_end() { 01580 01581 $this->open_xml_writer('gradebook.xml'); 01582 $this->xmlwriter->begin_tag('gradebook'); 01583 $this->write_grade_categories(); 01584 $this->write_grade_items(); 01585 $this->write_grade_letters(); 01586 $this->xmlwriter->end_tag('gradebook'); 01587 $this->close_xml_writer(); 01588 } 01589 01593 protected function write_grade_categories() { 01594 01595 $this->xmlwriter->begin_tag('grade_categories'); 01596 foreach ($this->converter->get_stash_itemids('gradebook_gradecategory') as $gradecategoryid) { 01597 $gradecategory = $this->converter->get_stash('gradebook_gradecategory', $gradecategoryid); 01598 $path = $this->calculate_category_path($gradecategoryid); 01599 $gradecategory['depth'] = count($path); 01600 $gradecategory['path'] = '/'.implode('/', $path).'/'; 01601 $this->write_xml('grade_category', $gradecategory, array('/grade_category/id')); 01602 } 01603 $this->xmlwriter->end_tag('grade_categories'); 01604 } 01605 01615 protected function calculate_category_path($categoryid) { 01616 01617 if (!array_key_exists($categoryid, $this->categoryparent)) { 01618 throw new moodle1_convert_exception('gradebook_unknown_categoryid', null, $categoryid); 01619 } 01620 01621 $path = array($categoryid); 01622 $parent = $this->categoryparent[$categoryid]; 01623 while (!is_null($parent)) { 01624 array_unshift($path, $parent); 01625 $parent = $this->categoryparent[$parent]; 01626 if (in_array($parent, $path)) { 01627 throw new moodle1_convert_exception('circular_reference_in_categories_tree'); 01628 } 01629 } 01630 01631 return $path; 01632 } 01633 01637 protected function write_grade_items() { 01638 01639 $this->xmlwriter->begin_tag('grade_items'); 01640 foreach ($this->converter->get_stash_itemids('gradebook_nonmodgradeitem') as $gradeitemid) { 01641 $gradeitem = $this->converter->get_stash('gradebook_nonmodgradeitem', $gradeitemid); 01642 $this->write_xml('grade_item', $gradeitem, array('/grade_item/id')); 01643 } 01644 $this->xmlwriter->end_tag('grade_items'); 01645 } 01646 01650 protected function write_grade_letters() { 01651 01652 $this->xmlwriter->begin_tag('grade_letters'); 01653 foreach ($this->converter->get_stash_itemids('gradebook_gradeletter') as $gradeletterid) { 01654 $gradeletter = $this->converter->get_stash('gradebook_gradeletter', $gradeletterid); 01655 $this->write_xml('grade_letter', $gradeletter, array('/grade_letter/id')); 01656 } 01657 $this->xmlwriter->end_tag('grade_letters'); 01658 } 01659 } 01660 01661 01665 abstract class moodle1_plugin_handler extends moodle1_xml_handler { 01666 01668 protected $plugintype; 01669 01671 protected $pluginname; 01672 01678 public function __construct(moodle1_converter $converter, $plugintype, $pluginname) { 01679 01680 parent::__construct($converter); 01681 $this->plugintype = $plugintype; 01682 $this->pluginname = $pluginname; 01683 } 01684 01690 public function get_component_name() { 01691 return $this->plugintype.'_'.$this->pluginname; 01692 } 01693 } 01694 01695 01699 abstract class moodle1_qtype_handler extends moodle1_plugin_handler { 01700 01702 protected $qbankhandler; 01703 01710 public function get_question_subpaths() { 01711 return array(); 01712 } 01713 01720 public function process_question(array $data, array $raw) { 01721 } 01722 01731 protected function write_answers(array $answers, $qtype) { 01732 01733 $this->xmlwriter->begin_tag('answers'); 01734 foreach ($answers as $elementname => $elements) { 01735 foreach ($elements as $element) { 01736 $answer = $this->convert_answer($element, $qtype); 01737 $this->write_xml('answer', $answer, array('/answer/id')); 01738 } 01739 } 01740 $this->xmlwriter->end_tag('answers'); 01741 } 01742 01748 protected function write_numerical_units(array $numericalunits) { 01749 01750 $this->xmlwriter->begin_tag('numerical_units'); 01751 foreach ($numericalunits as $elementname => $elements) { 01752 foreach ($elements as $element) { 01753 $element['id'] = $this->converter->get_nextid(); 01754 $this->write_xml('numerical_unit', $element, array('/numerical_unit/id')); 01755 } 01756 } 01757 $this->xmlwriter->end_tag('numerical_units'); 01758 } 01759 01766 protected function write_numerical_options(array $numericaloption) { 01767 01768 $this->xmlwriter->begin_tag('numerical_options'); 01769 if (!empty($numericaloption)) { 01770 $this->write_xml('numerical_option', $numericaloption, array('/numerical_option/id')); 01771 } 01772 $this->xmlwriter->end_tag('numerical_options'); 01773 } 01774 01784 protected function get_default_numerical_options($oldquestiontextformat, $units) { 01785 global $CFG; 01786 01787 // replay the upgrade step 2009100100 - new table 01788 $options = array( 01789 'id' => $this->converter->get_nextid(), 01790 'instructions' => null, 01791 'instructionsformat' => 0, 01792 'showunits' => 0, 01793 'unitsleft' => 0, 01794 'unitgradingtype' => 0, 01795 'unitpenalty' => 0.1 01796 ); 01797 01798 // replay the upgrade step 2009100101 01799 if ($CFG->texteditors !== 'textarea' and $oldquestiontextformat == FORMAT_MOODLE) { 01800 $options['instructionsformat'] = FORMAT_HTML; 01801 } else { 01802 $options['instructionsformat'] = $oldquestiontextformat; 01803 } 01804 01805 // Set a good default, depending on whether there are any units defined. 01806 if (empty($units)) { 01807 $options['showunits'] = 3; 01808 } 01809 01810 return $options; 01811 } 01812 01818 protected function write_dataset_definitions(array $datasetdefinitions) { 01819 01820 $this->xmlwriter->begin_tag('dataset_definitions'); 01821 foreach ($datasetdefinitions as $datasetdefinition) { 01822 $this->xmlwriter->begin_tag('dataset_definition', array('id' => $this->converter->get_nextid())); 01823 foreach (array('category', 'name', 'type', 'options', 'itemcount') as $element) { 01824 $this->xmlwriter->full_tag($element, $datasetdefinition[$element]); 01825 } 01826 $this->xmlwriter->begin_tag('dataset_items'); 01827 if (!empty($datasetdefinition['dataset_items']['dataset_item'])) { 01828 foreach ($datasetdefinition['dataset_items']['dataset_item'] as $datasetitem) { 01829 $datasetitem['id'] = $this->converter->get_nextid(); 01830 $this->write_xml('dataset_item', $datasetitem, array('/dataset_item/id')); 01831 } 01832 } 01833 $this->xmlwriter->end_tag('dataset_items'); 01834 $this->xmlwriter->end_tag('dataset_definition'); 01835 } 01836 $this->xmlwriter->end_tag('dataset_definitions'); 01837 } 01838 01840 01841 public function __construct(moodle1_question_bank_handler $qbankhandler, $qtype) { 01842 01843 parent::__construct($qbankhandler->get_converter(), 'qtype', $qtype); 01844 $this->qbankhandler = $qbankhandler; 01845 } 01846 01850 final public function get_paths() { 01851 throw new moodle1_convert_exception('qtype_handler_get_paths'); 01852 } 01853 01857 final protected function open_xml_writer() { 01858 throw new moodle1_convert_exception('opening_xml_writer_forbidden'); 01859 } 01860 01864 final protected function close_xml_writer() { 01865 throw new moodle1_convert_exception('opening_xml_writer_forbidden'); 01866 } 01867 01873 public function use_xml_writer(xml_writer $xmlwriter) { 01874 $this->xmlwriter = $xmlwriter; 01875 } 01876 01886 private function convert_answer(array $old, $qtype) { 01887 global $CFG; 01888 01889 $new = array(); 01890 $new['id'] = $old['id']; 01891 $new['answertext'] = $old['answer_text']; 01892 $new['answerformat'] = 0; // upgrade step 2010080900 01893 $new['fraction'] = $old['fraction']; 01894 $new['feedback'] = $old['feedback']; 01895 $new['feedbackformat'] = 0; // upgrade step 2010080900 01896 01897 // replay upgrade step 2010080901 01898 if ($qtype !== 'multichoice') { 01899 $new['answerformat'] = FORMAT_PLAIN; 01900 } else { 01901 $new['answerformat'] = FORMAT_MOODLE; 01902 } 01903 01904 if ($CFG->texteditors !== 'textarea') { 01905 if ($qtype == 'essay') { 01906 $new['feedback'] = text_to_html($new['feedback'], false, false, true); 01907 } 01908 $new['feedbackformat'] = FORMAT_HTML; 01909 01910 } else { 01911 $new['feedbackformat'] = FORMAT_MOODLE; 01912 } 01913 01914 return $new; 01915 } 01916 } 01917 01918 01922 abstract class moodle1_mod_handler extends moodle1_plugin_handler { 01923 01929 public function get_modname() { 01930 return $this->pluginname; 01931 } 01932 01943 protected function get_cminfo($instance, $modname = null) { 01944 01945 if (is_null($modname)) { 01946 $modname = $this->pluginname; 01947 } 01948 return $this->converter->get_stash('cminfo_'.$modname, $instance); 01949 } 01950 } 01951 01952 01956 abstract class moodle1_resource_successor_handler extends moodle1_mod_handler { 01957 01964 final public function get_paths() { 01965 return array(); 01966 } 01967 01976 public function process_legacy_resource(array $data, array $raw) { 01977 } 01978 01984 public function on_legacy_resource_end(array $data) { 01985 } 01986 } 01987 01991 abstract class moodle1_block_handler extends moodle1_plugin_handler { 01992 01993 } 01994 01995 01999 abstract class moodle1_submod_handler extends moodle1_plugin_handler { 02000 02002 protected $parenthandler; 02003 02009 public function __construct(moodle1_mod_handler $parenthandler, $subplugintype, $subpluginname) { 02010 $this->parenthandler = $parenthandler; 02011 parent::__construct($parenthandler->converter, $subplugintype, $subpluginname); 02012 } 02013 02022 final public function get_paths() { 02023 return array(); 02024 } 02025 }