Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * Defines Moodle 1.9 backup conversion handlers 20 * 21 * Handlers are classes responsible for the actual conversion work. Their logic 22 * is similar to the functionality provided by steps in plan based restore process. 23 * 24 * @package backup-convert 25 * @subpackage moodle1 26 * @copyright 2011 David Mudrak <david@moodle.com> 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 30 defined('MOODLE_INTERNAL') || die(); 31 32 require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php'); 33 require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); 34 require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php'); 35 36 /** 37 * Handlers factory class 38 */ 39 abstract class moodle1_handlers_factory { 40 41 /** 42 * @param moodle1_converter the converter requesting the converters 43 * @return list of all available conversion handlers 44 */ 45 public static function get_handlers(moodle1_converter $converter) { 46 47 $handlers = array( 48 new moodle1_root_handler($converter), 49 new moodle1_info_handler($converter), 50 new moodle1_course_header_handler($converter), 51 new moodle1_course_outline_handler($converter), 52 new moodle1_roles_definition_handler($converter), 53 new moodle1_question_bank_handler($converter), 54 new moodle1_scales_handler($converter), 55 new moodle1_outcomes_handler($converter), 56 new moodle1_gradebook_handler($converter), 57 ); 58 59 $handlers = array_merge($handlers, self::get_plugin_handlers('mod', $converter)); 60 $handlers = array_merge($handlers, self::get_plugin_handlers('block', $converter)); 61 62 // make sure that all handlers have expected class 63 foreach ($handlers as $handler) { 64 if (!$handler instanceof moodle1_handler) { 65 throw new moodle1_convert_exception('wrong_handler_class', get_class($handler)); 66 } 67 } 68 69 return $handlers; 70 } 71 72 /// public API ends here /////////////////////////////////////////////////// 73 74 /** 75 * Runs through all plugins of a specific type and instantiates their handlers 76 * 77 * @todo ask mod's subplugins 78 * @param string $type the plugin type 79 * @param moodle1_converter $converter the converter requesting the handler 80 * @throws moodle1_convert_exception 81 * @return array of {@link moodle1_handler} instances 82 */ 83 protected static function get_plugin_handlers($type, moodle1_converter $converter) { 84 global $CFG; 85 86 $handlers = array(); 87 $plugins = core_component::get_plugin_list($type); 88 foreach ($plugins as $name => $dir) { 89 $handlerfile = $dir . '/backup/moodle1/lib.php'; 90 $handlerclass = "moodle1_{$type}_{$name}_handler"; 91 if (file_exists($handlerfile)) { 92 require_once($handlerfile); 93 } elseif ($type == 'block') { 94 $handlerclass = "moodle1_block_generic_handler"; 95 } else { 96 continue; 97 } 98 99 if (!class_exists($handlerclass)) { 100 throw new moodle1_convert_exception('missing_handler_class', $handlerclass); 101 } 102 $handlers[] = new $handlerclass($converter, $type, $name); 103 } 104 return $handlers; 105 } 106 } 107 108 109 /** 110 * Base backup conversion handler 111 */ 112 abstract class moodle1_handler implements loggable { 113 114 /** @var moodle1_converter */ 115 protected $converter; 116 117 /** 118 * @param moodle1_converter $converter the converter that requires us 119 */ 120 public function __construct(moodle1_converter $converter) { 121 $this->converter = $converter; 122 } 123 124 /** 125 * @return moodle1_converter the converter that required this handler 126 */ 127 public function get_converter() { 128 return $this->converter; 129 } 130 131 /** 132 * Log a message using the converter's logging mechanism 133 * 134 * @param string $message message text 135 * @param int $level message level {@example backup::LOG_WARNING} 136 * @param null|mixed $a additional information 137 * @param null|int $depth the message depth 138 * @param bool $display whether the message should be sent to the output, too 139 */ 140 public function log($message, $level, $a = null, $depth = null, $display = false) { 141 $this->converter->log($message, $level, $a, $depth, $display); 142 } 143 } 144 145 146 /** 147 * Base backup conversion handler that generates an XML file 148 */ 149 abstract class moodle1_xml_handler extends moodle1_handler { 150 151 /** @var null|string the name of file we are writing to */ 152 protected $xmlfilename; 153 154 /** @var null|xml_writer */ 155 protected $xmlwriter; 156 157 /** 158 * Opens the XML writer - after calling, one is free to use $xmlwriter 159 * 160 * @param string $filename XML file name to write into 161 * @return void 162 */ 163 protected function open_xml_writer($filename) { 164 165 if (!is_null($this->xmlfilename) and $filename !== $this->xmlfilename) { 166 throw new moodle1_convert_exception('xml_writer_already_opened_for_other_file', $this->xmlfilename); 167 } 168 169 if (!$this->xmlwriter instanceof xml_writer) { 170 $this->xmlfilename = $filename; 171 $fullpath = $this->converter->get_workdir_path() . '/' . $this->xmlfilename; 172 $directory = pathinfo($fullpath, PATHINFO_DIRNAME); 173 174 if (!check_dir_exists($directory)) { 175 throw new moodle1_convert_exception('unable_create_target_directory', $directory); 176 } 177 $this->xmlwriter = new xml_writer(new file_xml_output($fullpath), new moodle1_xml_transformer()); 178 $this->xmlwriter->start(); 179 } 180 } 181 182 /** 183 * Close the XML writer 184 * 185 * At the moment, the caller must close all tags before calling 186 * 187 * @return void 188 */ 189 protected function close_xml_writer() { 190 if ($this->xmlwriter instanceof xml_writer) { 191 $this->xmlwriter->stop(); 192 } 193 unset($this->xmlwriter); 194 $this->xmlwriter = null; 195 $this->xmlfilename = null; 196 } 197 198 /** 199 * Checks if the XML writer has been opened by {@link self::open_xml_writer()} 200 * 201 * @return bool 202 */ 203 protected function has_xml_writer() { 204 205 if ($this->xmlwriter instanceof xml_writer) { 206 return true; 207 } else { 208 return false; 209 } 210 } 211 212 /** 213 * Writes the given XML tree data into the currently opened file 214 * 215 * @param string $element the name of the root element of the tree 216 * @param array $data the associative array of data to write 217 * @param array $attribs list of additional fields written as attributes instead of nested elements 218 * @param string $parent used internally during the recursion, do not set yourself 219 */ 220 protected function write_xml($element, array $data, array $attribs = array(), $parent = '/') { 221 222 if (!$this->has_xml_writer()) { 223 throw new moodle1_convert_exception('write_xml_without_writer'); 224 } 225 226 $mypath = $parent . $element; 227 $myattribs = array(); 228 229 // detect properties that should be rendered as element's attributes instead of children 230 foreach ($data as $name => $value) { 231 if (!is_array($value)) { 232 if (in_array($mypath . '/' . $name, $attribs)) { 233 $myattribs[$name] = $value; 234 unset($data[$name]); 235 } 236 } 237 } 238 239 // reorder the $data so that all sub-branches are at the end (needed by our parser) 240 $leaves = array(); 241 $branches = array(); 242 foreach ($data as $name => $value) { 243 if (is_array($value)) { 244 $branches[$name] = $value; 245 } else { 246 $leaves[$name] = $value; 247 } 248 } 249 $data = array_merge($leaves, $branches); 250 251 $this->xmlwriter->begin_tag($element, $myattribs); 252 253 foreach ($data as $name => $value) { 254 if (is_array($value)) { 255 // recursively call self 256 $this->write_xml($name, $value, $attribs, $mypath.'/'); 257 } else { 258 $this->xmlwriter->full_tag($name, $value); 259 } 260 } 261 262 $this->xmlwriter->end_tag($element); 263 } 264 265 /** 266 * Makes sure that a new XML file exists, or creates it itself 267 * 268 * This is here so we can check that all XML files that the restore process relies on have 269 * been created by an executed handler. If the file is not found, this method can create it 270 * using the given $rootelement as an empty root container in the file. 271 * 272 * @param string $filename relative file name like 'course/course.xml' 273 * @param string|bool $rootelement root element to use, false to not create the file 274 * @param array $content content of the root element 275 * @return bool true is the file existed, false if it did not 276 */ 277 protected function make_sure_xml_exists($filename, $rootelement = false, $content = array()) { 278 279 $existed = file_exists($this->converter->get_workdir_path().'/'.$filename); 280 281 if ($existed) { 282 return true; 283 } 284 285 if ($rootelement !== false) { 286 $this->open_xml_writer($filename); 287 $this->write_xml($rootelement, $content); 288 $this->close_xml_writer(); 289 } 290 291 return false; 292 } 293 } 294 295 296 /** 297 * Process the root element of the backup file 298 */ 299 class moodle1_root_handler extends moodle1_xml_handler { 300 301 public function get_paths() { 302 return array(new convert_path('root_element', '/MOODLE_BACKUP')); 303 } 304 305 /** 306 * Converts course_files and site_files 307 */ 308 public function on_root_element_start() { 309 310 // convert course files 311 $fileshandler = new moodle1_files_handler($this->converter); 312 $fileshandler->process(); 313 } 314 315 /** 316 * This is executed at the end of the moodle.xml parsing 317 */ 318 public function on_root_element_end() { 319 global $CFG; 320 321 // restore the stashes prepared by other handlers for us 322 $backupinfo = $this->converter->get_stash('backup_info'); 323 $originalcourseinfo = $this->converter->get_stash('original_course_info'); 324 325 //////////////////////////////////////////////////////////////////////// 326 // write moodle_backup.xml 327 //////////////////////////////////////////////////////////////////////// 328 $this->open_xml_writer('moodle_backup.xml'); 329 330 $this->xmlwriter->begin_tag('moodle_backup'); 331 $this->xmlwriter->begin_tag('information'); 332 333 // moodle_backup/information 334 $this->xmlwriter->full_tag('name', $backupinfo['name']); 335 $this->xmlwriter->full_tag('moodle_version', $backupinfo['moodle_version']); 336 $this->xmlwriter->full_tag('moodle_release', $backupinfo['moodle_release']); 337 $this->xmlwriter->full_tag('backup_version', $CFG->backup_version); // {@see restore_prechecks_helper::execute_prechecks} 338 $this->xmlwriter->full_tag('backup_release', $CFG->backup_release); 339 $this->xmlwriter->full_tag('backup_date', $backupinfo['date']); 340 // see the commit c0543b - all backups created in 1.9 and later declare the 341 // information or it is considered as false 342 if (isset($backupinfo['mnet_remoteusers'])) { 343 $this->xmlwriter->full_tag('mnet_remoteusers', $backupinfo['mnet_remoteusers']); 344 } else { 345 $this->xmlwriter->full_tag('mnet_remoteusers', false); 346 } 347 $this->xmlwriter->full_tag('original_wwwroot', $backupinfo['original_wwwroot']); 348 // {@see backup_general_helper::backup_is_samesite()} 349 if (isset($backupinfo['original_site_identifier_hash'])) { 350 $this->xmlwriter->full_tag('original_site_identifier_hash', $backupinfo['original_site_identifier_hash']); 351 } else { 352 $this->xmlwriter->full_tag('original_site_identifier_hash', null); 353 } 354 $this->xmlwriter->full_tag('original_course_id', $originalcourseinfo['original_course_id']); 355 $this->xmlwriter->full_tag('original_course_fullname', $originalcourseinfo['original_course_fullname']); 356 $this->xmlwriter->full_tag('original_course_shortname', $originalcourseinfo['original_course_shortname']); 357 $this->xmlwriter->full_tag('original_course_startdate', $originalcourseinfo['original_course_startdate']); 358 $this->xmlwriter->full_tag('original_system_contextid', $this->converter->get_contextid(CONTEXT_SYSTEM)); 359 // note that even though we have original_course_contextid available, we regenerate the 360 // original course contextid using our helper method to be sure that the data are consistent 361 // within the MBZ file 362 $this->xmlwriter->full_tag('original_course_contextid', $this->converter->get_contextid(CONTEXT_COURSE)); 363 364 // moodle_backup/information/details 365 $this->xmlwriter->begin_tag('details'); 366 $this->write_xml('detail', array( 367 'backup_id' => $this->converter->get_id(), 368 'type' => backup::TYPE_1COURSE, 369 'format' => backup::FORMAT_MOODLE, 370 'interactive' => backup::INTERACTIVE_YES, 371 'mode' => backup::MODE_CONVERTED, 372 'execution' => backup::EXECUTION_INMEDIATE, 373 'executiontime' => 0, 374 ), array('/detail/backup_id')); 375 $this->xmlwriter->end_tag('details'); 376 377 // moodle_backup/information/contents 378 $this->xmlwriter->begin_tag('contents'); 379 380 // moodle_backup/information/contents/activities 381 $this->xmlwriter->begin_tag('activities'); 382 $activitysettings = array(); 383 foreach ($this->converter->get_stash('coursecontents') as $activity) { 384 $modinfo = $this->converter->get_stash('modinfo_'.$activity['modulename']); 385 $modinstance = $modinfo['instances'][$activity['instanceid']]; 386 $this->write_xml('activity', array( 387 'moduleid' => $activity['cmid'], 388 'sectionid' => $activity['sectionid'], 389 'modulename' => $activity['modulename'], 390 'title' => $modinstance['name'], 391 'directory' => 'activities/'.$activity['modulename'].'_'.$activity['cmid'] 392 )); 393 $activitysettings[] = array( 394 'level' => 'activity', 395 'activity' => $activity['modulename'].'_'.$activity['cmid'], 396 'name' => $activity['modulename'].'_'.$activity['cmid'].'_included', 397 'value' => (($modinfo['included'] === 'true' and $modinstance['included'] === 'true') ? 1 : 0)); 398 $activitysettings[] = array( 399 'level' => 'activity', 400 'activity' => $activity['modulename'].'_'.$activity['cmid'], 401 'name' => $activity['modulename'].'_'.$activity['cmid'].'_userinfo', 402 //'value' => (($modinfo['userinfo'] === 'true' and $modinstance['userinfo'] === 'true') ? 1 : 0)); 403 'value' => 0); // todo hardcoded non-userinfo for now 404 } 405 $this->xmlwriter->end_tag('activities'); 406 407 // moodle_backup/information/contents/sections 408 $this->xmlwriter->begin_tag('sections'); 409 $sectionsettings = array(); 410 foreach ($this->converter->get_stash_itemids('sectioninfo') as $sectionid) { 411 $sectioninfo = $this->converter->get_stash('sectioninfo', $sectionid); 412 $sectionsettings[] = array( 413 'level' => 'section', 414 'section' => 'section_'.$sectionid, 415 'name' => 'section_'.$sectionid.'_included', 416 'value' => 1); 417 $sectionsettings[] = array( 418 'level' => 'section', 419 'section' => 'section_'.$sectionid, 420 'name' => 'section_'.$sectionid.'_userinfo', 421 'value' => 0); // @todo how to detect this from moodle.xml? 422 $this->write_xml('section', array( 423 'sectionid' => $sectionid, 424 'title' => $sectioninfo['number'], // because the title is not available 425 'directory' => 'sections/section_'.$sectionid)); 426 } 427 $this->xmlwriter->end_tag('sections'); 428 429 // moodle_backup/information/contents/course 430 $this->write_xml('course', array( 431 'courseid' => $originalcourseinfo['original_course_id'], 432 'title' => $originalcourseinfo['original_course_shortname'], 433 'directory' => 'course')); 434 unset($originalcourseinfo); 435 436 $this->xmlwriter->end_tag('contents'); 437 438 // moodle_backup/information/settings 439 $this->xmlwriter->begin_tag('settings'); 440 441 // fake backup root seetings 442 $rootsettings = array( 443 'filename' => $backupinfo['name'], 444 'users' => 0, // @todo how to detect this from moodle.xml? 445 'anonymize' => 0, 446 'role_assignments' => 0, 447 'activities' => 1, 448 'blocks' => 1, 449 'filters' => 0, 450 'comments' => 0, 451 'userscompletion' => 0, 452 'logs' => 0, 453 'grade_histories' => 0, 454 ); 455 unset($backupinfo); 456 foreach ($rootsettings as $name => $value) { 457 $this->write_xml('setting', array( 458 'level' => 'root', 459 'name' => $name, 460 'value' => $value)); 461 } 462 unset($rootsettings); 463 464 // activity settings populated above 465 foreach ($activitysettings as $activitysetting) { 466 $this->write_xml('setting', $activitysetting); 467 } 468 unset($activitysettings); 469 470 // section settings populated above 471 foreach ($sectionsettings as $sectionsetting) { 472 $this->write_xml('setting', $sectionsetting); 473 } 474 unset($sectionsettings); 475 476 $this->xmlwriter->end_tag('settings'); 477 478 $this->xmlwriter->end_tag('information'); 479 $this->xmlwriter->end_tag('moodle_backup'); 480 481 $this->close_xml_writer(); 482 483 //////////////////////////////////////////////////////////////////////// 484 // write files.xml 485 //////////////////////////////////////////////////////////////////////// 486 $this->open_xml_writer('files.xml'); 487 $this->xmlwriter->begin_tag('files'); 488 foreach ($this->converter->get_stash_itemids('files') as $fileid) { 489 $this->write_xml('file', $this->converter->get_stash('files', $fileid), array('/file/id')); 490 } 491 $this->xmlwriter->end_tag('files'); 492 $this->close_xml_writer('files.xml'); 493 494 //////////////////////////////////////////////////////////////////////// 495 // write scales.xml 496 //////////////////////////////////////////////////////////////////////// 497 $this->open_xml_writer('scales.xml'); 498 $this->xmlwriter->begin_tag('scales_definition'); 499 foreach ($this->converter->get_stash_itemids('scales') as $scaleid) { 500 $this->write_xml('scale', $this->converter->get_stash('scales', $scaleid), array('/scale/id')); 501 } 502 $this->xmlwriter->end_tag('scales_definition'); 503 $this->close_xml_writer('scales.xml'); 504 505 //////////////////////////////////////////////////////////////////////// 506 // write course/inforef.xml 507 //////////////////////////////////////////////////////////////////////// 508 $this->open_xml_writer('course/inforef.xml'); 509 $this->xmlwriter->begin_tag('inforef'); 510 511 $this->xmlwriter->begin_tag('fileref'); 512 // legacy course files 513 $fileids = $this->converter->get_stash('course_files_ids'); 514 if (is_array($fileids)) { 515 foreach ($fileids as $fileid) { 516 $this->write_xml('file', array('id' => $fileid)); 517 } 518 } 519 // todo site files 520 // course summary files 521 $fileids = $this->converter->get_stash('course_summary_files_ids'); 522 if (is_array($fileids)) { 523 foreach ($fileids as $fileid) { 524 $this->write_xml('file', array('id' => $fileid)); 525 } 526 } 527 $this->xmlwriter->end_tag('fileref'); 528 529 $this->xmlwriter->begin_tag('question_categoryref'); 530 foreach ($this->converter->get_stash_itemids('question_categories') as $questioncategoryid) { 531 $this->write_xml('question_category', array('id' => $questioncategoryid)); 532 } 533 $this->xmlwriter->end_tag('question_categoryref'); 534 535 $this->xmlwriter->end_tag('inforef'); 536 $this->close_xml_writer(); 537 538 // make sure that the files required by the restore process have been generated. 539 // missing file may happen if the watched tag is not present in moodle.xml (for example 540 // QUESTION_CATEGORIES is optional in moodle.xml but questions.xml must exist in 541 // moodle2 format) or the handler has not been implemented yet. 542 // apparently this must be called after the handler had a chance to create the file. 543 $this->make_sure_xml_exists('questions.xml', 'question_categories'); 544 $this->make_sure_xml_exists('groups.xml', 'groups'); 545 $this->make_sure_xml_exists('outcomes.xml', 'outcomes_definition'); 546 $this->make_sure_xml_exists('users.xml', 'users'); 547 $this->make_sure_xml_exists('course/roles.xml', 'roles', 548 array('role_assignments' => array(), 'role_overrides' => array())); 549 $this->make_sure_xml_exists('course/enrolments.xml', 'enrolments', 550 array('enrols' => array())); 551 } 552 } 553 554 555 /** 556 * The class responsible for course and site files migration 557 * 558 * @todo migrate site_files 559 */ 560 class moodle1_files_handler extends moodle1_xml_handler { 561 562 /** 563 * Migrates course_files and site_files in the converter workdir 564 */ 565 public function process() { 566 $this->migrate_course_files(); 567 // todo $this->migrate_site_files(); 568 } 569 570 /** 571 * Migrates course_files in the converter workdir 572 */ 573 protected function migrate_course_files() { 574 $ids = array(); 575 $fileman = $this->converter->get_file_manager($this->converter->get_contextid(CONTEXT_COURSE), 'course', 'legacy'); 576 $this->converter->set_stash('course_files_ids', array()); 577 if (file_exists($this->converter->get_tempdir_path().'/course_files')) { 578 $ids = $fileman->migrate_directory('course_files'); 579 $this->converter->set_stash('course_files_ids', $ids); 580 } 581 $this->log('course files migrated', backup::LOG_INFO, count($ids)); 582 } 583 } 584 585 586 /** 587 * Handles the conversion of /MOODLE_BACKUP/INFO paths 588 * 589 * We do not produce any XML file here, just storing the data in the temp 590 * table so thay can be used by a later handler. 591 */ 592 class moodle1_info_handler extends moodle1_handler { 593 594 /** @var array list of mod names included in info_details */ 595 protected $modnames = array(); 596 597 /** @var array the in-memory cache of the currently parsed info_details_mod element */ 598 protected $currentmod; 599 600 public function get_paths() { 601 return array( 602 new convert_path('info', '/MOODLE_BACKUP/INFO'), 603 new convert_path('info_details', '/MOODLE_BACKUP/INFO/DETAILS'), 604 new convert_path('info_details_mod', '/MOODLE_BACKUP/INFO/DETAILS/MOD'), 605 new convert_path('info_details_mod_instance', '/MOODLE_BACKUP/INFO/DETAILS/MOD/INSTANCES/INSTANCE'), 606 ); 607 } 608 609 /** 610 * Stashes the backup info for later processing by {@link moodle1_root_handler} 611 */ 612 public function process_info($data) { 613 $this->converter->set_stash('backup_info', $data); 614 } 615 616 /** 617 * Initializes the in-memory cache for the current mod 618 */ 619 public function process_info_details_mod($data) { 620 $this->currentmod = $data; 621 $this->currentmod['instances'] = array(); 622 } 623 624 /** 625 * Appends the current instance data to the temporary in-memory cache 626 */ 627 public function process_info_details_mod_instance($data) { 628 $this->currentmod['instances'][$data['id']] = $data; 629 } 630 631 /** 632 * Stashes the backup info for later processing by {@link moodle1_root_handler} 633 */ 634 public function on_info_details_mod_end($data) { 635 global $CFG; 636 637 // keep only such modules that seem to have the support for moodle1 implemented 638 $modname = $this->currentmod['name']; 639 if (file_exists($CFG->dirroot.'/mod/'.$modname.'/backup/moodle1/lib.php')) { 640 $this->converter->set_stash('modinfo_'.$modname, $this->currentmod); 641 $this->modnames[] = $modname; 642 } else { 643 $this->log('unsupported activity module', backup::LOG_WARNING, $modname); 644 } 645 646 $this->currentmod = array(); 647 } 648 649 /** 650 * Stashes the list of activity module types for later processing by {@link moodle1_root_handler} 651 */ 652 public function on_info_details_end() { 653 $this->converter->set_stash('modnameslist', $this->modnames); 654 } 655 } 656 657 658 /** 659 * Handles the conversion of /MOODLE_BACKUP/COURSE/HEADER paths 660 */ 661 class moodle1_course_header_handler extends moodle1_xml_handler { 662 663 /** @var array we need to merge course information because it is dispatched twice */ 664 protected $course = array(); 665 666 /** @var array we need to merge course information because it is dispatched twice */ 667 protected $courseraw = array(); 668 669 /** @var array */ 670 protected $category; 671 672 public function get_paths() { 673 return array( 674 new convert_path( 675 'course_header', '/MOODLE_BACKUP/COURSE/HEADER', 676 array( 677 'newfields' => array( 678 'summaryformat' => 1, 679 'legacyfiles' => 2, 680 'requested' => 0, // @todo not really new, but maybe never backed up? 681 'restrictmodules' => 0, 682 'enablecompletion' => 0, 683 'completionstartonenrol' => 0, 684 'completionnotify' => 0, 685 'tags' => array(), 686 'allowed_modules' => array(), 687 ), 688 'dropfields' => array( 689 'roles_overrides', 690 'roles_assignments', 691 'cost', 692 'currancy', 693 'defaultrole', 694 'enrol', 695 'enrolenddate', 696 'enrollable', 697 'enrolperiod', 698 'enrolstartdate', 699 'expirynotify', 700 'expirythreshold', 701 'guest', 702 'notifystudents', 703 'password', 704 'student', 705 'students', 706 'teacher', 707 'teachers', 708 'metacourse', 709 ) 710 ) 711 ), 712 new convert_path( 713 'course_header_category', '/MOODLE_BACKUP/COURSE/HEADER/CATEGORY', 714 array( 715 'newfields' => array( 716 'description' => null, 717 ) 718 ) 719 ), 720 ); 721 } 722 723 /** 724 * Because there is the CATEGORY branch in the middle of the COURSE/HEADER 725 * branch, this is dispatched twice. We use $this->coursecooked to merge 726 * the result. Once the parser is fixed, it can be refactored. 727 */ 728 public function process_course_header($data, $raw) { 729 $this->course = array_merge($this->course, $data); 730 $this->courseraw = array_merge($this->courseraw, $raw); 731 } 732 733 public function process_course_header_category($data) { 734 $this->category = $data; 735 } 736 737 public function on_course_header_end() { 738 739 $contextid = $this->converter->get_contextid(CONTEXT_COURSE); 740 741 // stash the information needed by other handlers 742 $info = array( 743 'original_course_id' => $this->course['id'], 744 'original_course_fullname' => $this->course['fullname'], 745 'original_course_shortname' => $this->course['shortname'], 746 'original_course_startdate' => $this->course['startdate'], 747 'original_course_contextid' => $contextid 748 ); 749 $this->converter->set_stash('original_course_info', $info); 750 751 $this->course['contextid'] = $contextid; 752 $this->course['category'] = $this->category; 753 754 // migrate files embedded into the course summary and stash their ids 755 $fileman = $this->converter->get_file_manager($contextid, 'course', 'summary'); 756 $this->course['summary'] = moodle1_converter::migrate_referenced_files($this->course['summary'], $fileman); 757 $this->converter->set_stash('course_summary_files_ids', $fileman->get_fileids()); 758 759 // write course.xml 760 $this->open_xml_writer('course/course.xml'); 761 $this->write_xml('course', $this->course, array('/course/id', '/course/contextid')); 762 $this->close_xml_writer(); 763 } 764 } 765 766 767 /** 768 * Handles the conversion of course sections and course modules 769 */ 770 class moodle1_course_outline_handler extends moodle1_xml_handler { 771 772 /** @var array ordered list of the course contents */ 773 protected $coursecontents = array(); 774 775 /** @var array current section data */ 776 protected $currentsection; 777 778 /** 779 * This handler is interested in course sections and course modules within them 780 */ 781 public function get_paths() { 782 return array( 783 new convert_path('course_sections', '/MOODLE_BACKUP/COURSE/SECTIONS'), 784 new convert_path( 785 'course_section', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION', 786 array( 787 'newfields' => array( 788 'name' => null, 789 'summaryformat' => 1, 790 'sequence' => null, 791 ), 792 ) 793 ), 794 new convert_path( 795 'course_module', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD', 796 array( 797 'newfields' => array( 798 'completion' => 0, 799 'completiongradeitemnumber' => null, 800 'completionview' => 0, 801 'completionexpected' => 0, 802 'availability' => null, 803 'visibleold' => 1, 804 'showdescription' => 0, 805 ), 806 'dropfields' => array( 807 'instance', 808 'roles_overrides', 809 'roles_assignments', 810 ), 811 'renamefields' => array( 812 'type' => 'modulename', 813 ), 814 ) 815 ), 816 new convert_path('course_modules', '/MOODLE_BACKUP/COURSE/MODULES'), 817 // todo new convert_path('course_module_roles_overrides', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'), 818 // todo new convert_path('course_module_roles_assignments', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_ASSIGNMENTS'), 819 ); 820 } 821 822 public function process_course_section($data) { 823 $this->currentsection = $data; 824 } 825 826 /** 827 * Populates the section sequence field (order of course modules) and stashes the 828 * course module info so that is can be dumped to activities/xxxx_x/module.xml later 829 */ 830 public function process_course_module($data, $raw) { 831 global $CFG; 832 833 // check that this type of module should be included in the mbz 834 $modinfo = $this->converter->get_stash_itemids('modinfo_'.$data['modulename']); 835 if (empty($modinfo)) { 836 return; 837 } 838 839 // add the course module into the course contents list 840 $this->coursecontents[$data['id']] = array( 841 'cmid' => $data['id'], 842 'instanceid' => $raw['INSTANCE'], 843 'sectionid' => $this->currentsection['id'], 844 'modulename' => $data['modulename'], 845 'title' => null 846 ); 847 848 // add the course module id into the section's sequence 849 if (is_null($this->currentsection['sequence'])) { 850 $this->currentsection['sequence'] = $data['id']; 851 } else { 852 $this->currentsection['sequence'] .= ',' . $data['id']; 853 } 854 855 // add the sectionid and sectionnumber 856 $data['sectionid'] = $this->currentsection['id']; 857 $data['sectionnumber'] = $this->currentsection['number']; 858 859 // generate the module version - this is a bit tricky as this information 860 // is not present in 1.9 backups. we will use the currently installed version 861 // whenever we can but that might not be accurate for some modules. 862 // also there might be problem with modules that are not present at the target 863 // host... 864 $versionfile = $CFG->dirroot.'/mod/'.$data['modulename'].'/version.php'; 865 if (file_exists($versionfile)) { 866 $plugin = new stdClass(); 867 $plugin->version = null; 868 $module = $plugin; 869 include($versionfile); 870 // Have to hardcode - since quiz uses some hardcoded version numbers when restoring. 871 // This is the lowest number used minus one. 872 $data['version'] = 2011010099; 873 } else { 874 $data['version'] = null; 875 } 876 877 // stash the course module info in stashes like 'cminfo_forum' with 878 // itemid set to the instance id. this is needed so that module handlers 879 // can later obtain information about the course module and dump it into 880 // the module.xml file 881 $this->converter->set_stash('cminfo_'.$data['modulename'], $data, $raw['INSTANCE']); 882 } 883 884 /** 885 * Writes sections/section_xxx/section.xml file and stashes it, too 886 */ 887 public function on_course_section_end() { 888 889 // migrate files embedded into the section summary field 890 $contextid = $this->converter->get_contextid(CONTEXT_COURSE); 891 $fileman = $this->converter->get_file_manager($contextid, 'course', 'section', $this->currentsection['id']); 892 $this->currentsection['summary'] = moodle1_converter::migrate_referenced_files($this->currentsection['summary'], $fileman); 893 894 // write section's inforef.xml with the file references 895 $this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/inforef.xml'); 896 $this->xmlwriter->begin_tag('inforef'); 897 $this->xmlwriter->begin_tag('fileref'); 898 $fileids = $fileman->get_fileids(); 899 if (is_array($fileids)) { 900 foreach ($fileids as $fileid) { 901 $this->write_xml('file', array('id' => $fileid)); 902 } 903 } 904 $this->xmlwriter->end_tag('fileref'); 905 $this->xmlwriter->end_tag('inforef'); 906 $this->close_xml_writer(); 907 908 // stash the section info and write section.xml 909 $this->converter->set_stash('sectioninfo', $this->currentsection, $this->currentsection['id']); 910 $this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/section.xml'); 911 $this->write_xml('section', $this->currentsection); 912 $this->close_xml_writer(); 913 unset($this->currentsection); 914 } 915 916 /** 917 * Stashes the course contents 918 */ 919 public function on_course_sections_end() { 920 $this->converter->set_stash('coursecontents', $this->coursecontents); 921 } 922 923 /** 924 * Writes the information collected by mod handlers 925 */ 926 public function on_course_modules_end() { 927 928 foreach ($this->converter->get_stash('modnameslist') as $modname) { 929 $modinfo = $this->converter->get_stash('modinfo_'.$modname); 930 foreach ($modinfo['instances'] as $modinstanceid => $modinstance) { 931 $cminfo = $this->converter->get_stash('cminfo_'.$modname, $modinstanceid); 932 $directory = 'activities/'.$modname.'_'.$cminfo['id']; 933 934 // write module.xml 935 $this->open_xml_writer($directory.'/module.xml'); 936 $this->write_xml('module', $cminfo, array('/module/id', '/module/version')); 937 $this->close_xml_writer(); 938 939 // write grades.xml 940 $this->open_xml_writer($directory.'/grades.xml'); 941 $this->xmlwriter->begin_tag('activity_gradebook'); 942 $gradeitems = $this->converter->get_stash_or_default('gradebook_modgradeitem_'.$modname, $modinstanceid, array()); 943 if (!empty($gradeitems)) { 944 $this->xmlwriter->begin_tag('grade_items'); 945 foreach ($gradeitems as $gradeitem) { 946 $this->write_xml('grade_item', $gradeitem, array('/grade_item/id')); 947 } 948 $this->xmlwriter->end_tag('grade_items'); 949 } 950 $this->write_xml('grade_letters', array()); // no grade_letters in module context in Moodle 1.9 951 $this->xmlwriter->end_tag('activity_gradebook'); 952 $this->close_xml_writer(); 953 954 // todo: write proper roles.xml, for now we just make sure the file is present 955 $this->make_sure_xml_exists($directory.'/roles.xml', 'roles'); 956 } 957 } 958 } 959 } 960 961 962 /** 963 * Handles the conversion of the defined roles 964 */ 965 class moodle1_roles_definition_handler extends moodle1_xml_handler { 966 967 /** 968 * Where the roles are defined in the source moodle.xml 969 */ 970 public function get_paths() { 971 return array( 972 new convert_path('roles', '/MOODLE_BACKUP/ROLES'), 973 new convert_path( 974 'roles_role', '/MOODLE_BACKUP/ROLES/ROLE', 975 array( 976 'newfields' => array( 977 'description' => '', 978 'sortorder' => 0, 979 'archetype' => '' 980 ) 981 ) 982 ) 983 ); 984 } 985 986 /** 987 * If there are any roles defined in moodle.xml, convert them to roles.xml 988 */ 989 public function process_roles_role($data) { 990 991 if (!$this->has_xml_writer()) { 992 $this->open_xml_writer('roles.xml'); 993 $this->xmlwriter->begin_tag('roles_definition'); 994 } 995 if (!isset($data['nameincourse'])) { 996 $data['nameincourse'] = null; 997 } 998 $this->write_xml('role', $data, array('role/id')); 999 } 1000 1001 /** 1002 * Finishes writing roles.xml 1003 */ 1004 public function on_roles_end() { 1005 1006 if (!$this->has_xml_writer()) { 1007 // no roles defined in moodle.xml so {link self::process_roles_role()} 1008 // was never executed 1009 $this->open_xml_writer('roles.xml'); 1010 $this->write_xml('roles_definition', array()); 1011 1012 } else { 1013 // some roles were dumped into the file, let us close their wrapper now 1014 $this->xmlwriter->end_tag('roles_definition'); 1015 } 1016 $this->close_xml_writer(); 1017 } 1018 } 1019 1020 1021 /** 1022 * Handles the conversion of the question bank included in the moodle.xml file 1023 */ 1024 class moodle1_question_bank_handler extends moodle1_xml_handler { 1025 1026 /** @var array the current question category being parsed */ 1027 protected $currentcategory = null; 1028 1029 /** @var array of the raw data for the current category */ 1030 protected $currentcategoryraw = null; 1031 1032 /** @var moodle1_file_manager instance used to convert question images */ 1033 protected $fileman = null; 1034 1035 /** @var bool are the currentcategory data already written (this is a work around MDL-27693) */ 1036 private $currentcategorywritten = false; 1037 1038 /** @var bool was the <questions> tag already written (work around MDL-27693) */ 1039 private $questionswrapperwritten = false; 1040 1041 /** @var array holds the instances of qtype specific conversion handlers */ 1042 private $qtypehandlers = null; 1043 1044 /** 1045 * Return the file manager instance used. 1046 * 1047 * @return moodle1_file_manager 1048 */ 1049 public function get_file_manager() { 1050 return $this->fileman; 1051 } 1052 1053 /** 1054 * Returns the information about the question category context being currently parsed 1055 * 1056 * @return array with keys contextid, contextlevel and contextinstanceid 1057 */ 1058 public function get_current_category_context() { 1059 return $this->currentcategory; 1060 } 1061 1062 /** 1063 * Registers path that are not qtype-specific 1064 */ 1065 public function get_paths() { 1066 1067 $paths = array( 1068 new convert_path('question_categories', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES'), 1069 new convert_path( 1070 'question_category', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY', 1071 array( 1072 'newfields' => array( 1073 'infoformat' => 0 1074 ) 1075 )), 1076 new convert_path('question_category_context', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/CONTEXT'), 1077 new convert_path('questions', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS'), 1078 // the question element must be grouped so we can re-dispatch it to the qtype handler as a whole 1079 new convert_path('question', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION', array(), true), 1080 ); 1081 1082 // annotate all question subpaths required by the qtypes subplugins 1083 $subpaths = array(); 1084 foreach ($this->get_qtype_handler('*') as $qtypehandler) { 1085 foreach ($qtypehandler->get_question_subpaths() as $subpath) { 1086 $subpaths[$subpath] = true; 1087 } 1088 } 1089 foreach (array_keys($subpaths) as $subpath) { 1090 $name = 'subquestion_'.strtolower(str_replace('/', '_', $subpath)); 1091 $path = '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION/'.$subpath; 1092 $paths[] = new convert_path($name, $path); 1093 } 1094 1095 return $paths; 1096 } 1097 1098 /** 1099 * Starts writing questions.xml and prepares the file manager instance 1100 */ 1101 public function on_question_categories_start() { 1102 $this->open_xml_writer('questions.xml'); 1103 $this->xmlwriter->begin_tag('question_categories'); 1104 if (is_null($this->fileman)) { 1105 $this->fileman = $this->converter->get_file_manager(); 1106 } 1107 } 1108 1109 /** 1110 * Initializes the current category cache 1111 */ 1112 public function on_question_category_start() { 1113 $this->currentcategory = array(); 1114 $this->currentcategoryraw = array(); 1115 $this->currentcategorywritten = false; 1116 $this->questionswrapperwritten = false; 1117 } 1118 1119 /** 1120 * Populates the current question category data 1121 * 1122 * Bacuse of the known subpath-in-the-middle problem (CONTEXT in this case), this is actually 1123 * called twice for both halves of the data. We merge them here into the currentcategory array. 1124 */ 1125 public function process_question_category($data, $raw) { 1126 $this->currentcategory = array_merge($this->currentcategory, $data); 1127 $this->currentcategoryraw = array_merge($this->currentcategoryraw, $raw); 1128 } 1129 1130 /** 1131 * Inject the context related information into the current category 1132 */ 1133 public function process_question_category_context($data) { 1134 1135 switch ($data['level']) { 1136 case 'module': 1137 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_MODULE, $data['instance']); 1138 $this->currentcategory['contextlevel'] = CONTEXT_MODULE; 1139 $this->currentcategory['contextinstanceid'] = $data['instance']; 1140 break; 1141 case 'course': 1142 $originalcourseinfo = $this->converter->get_stash('original_course_info'); 1143 $originalcourseid = $originalcourseinfo['original_course_id']; 1144 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSE); 1145 $this->currentcategory['contextlevel'] = CONTEXT_COURSE; 1146 $this->currentcategory['contextinstanceid'] = $originalcourseid; 1147 break; 1148 case 'coursecategory': 1149 // this is a bit hacky. the source moodle.xml defines COURSECATEGORYLEVEL as a distance 1150 // of the course category (1 = parent category, 2 = grand-parent category etc). We pretend 1151 // that this level*10 is the id of that category and create an artifical contextid for it 1152 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSECAT, $data['coursecategorylevel'] * 10); 1153 $this->currentcategory['contextlevel'] = CONTEXT_COURSECAT; 1154 $this->currentcategory['contextinstanceid'] = $data['coursecategorylevel'] * 10; 1155 break; 1156 case 'system': 1157 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_SYSTEM); 1158 $this->currentcategory['contextlevel'] = CONTEXT_SYSTEM; 1159 $this->currentcategory['contextinstanceid'] = 0; 1160 break; 1161 } 1162 } 1163 1164 /** 1165 * Writes the common <question> data and re-dispateches the whole grouped 1166 * <QUESTION> data to the qtype for appending its qtype specific data processing 1167 * 1168 * @param array $data 1169 * @param array $raw 1170 * @return array 1171 */ 1172 public function process_question(array $data, array $raw) { 1173 global $CFG; 1174 1175 // firstly make sure that the category data and the <questions> wrapper are written 1176 // note that because of MDL-27693 we can't use {@link self::process_question_category()} 1177 // and {@link self::on_questions_start()} to do so 1178 1179 if (empty($this->currentcategorywritten)) { 1180 $this->xmlwriter->begin_tag('question_category', array('id' => $this->currentcategory['id'])); 1181 foreach ($this->currentcategory as $name => $value) { 1182 if ($name === 'id') { 1183 continue; 1184 } 1185 $this->xmlwriter->full_tag($name, $value); 1186 } 1187 $this->currentcategorywritten = true; 1188 } 1189 1190 if (empty($this->questionswrapperwritten)) { 1191 $this->xmlwriter->begin_tag('questions'); 1192 $this->questionswrapperwritten = true; 1193 } 1194 1195 $qtype = $data['qtype']; 1196 1197 // replay the upgrade step 2008050700 {@see question_fix_random_question_parents()} 1198 if ($qtype == 'random' and $data['parent'] <> $data['id']) { 1199 $data['parent'] = $data['id']; 1200 } 1201 1202 // replay the upgrade step 2010080900 and part of 2010080901 1203 $data['generalfeedbackformat'] = $data['questiontextformat']; 1204 $data['oldquestiontextformat'] = $data['questiontextformat']; 1205 1206 if ($CFG->texteditors !== 'textarea') { 1207 $data['questiontext'] = text_to_html($data['questiontext'], false, false, true); 1208 $data['questiontextformat'] = FORMAT_HTML; 1209 $data['generalfeedback'] = text_to_html($data['generalfeedback'], false, false, true); 1210 $data['generalfeedbackformat'] = FORMAT_HTML; 1211 } 1212 1213 // Migrate files in questiontext. 1214 $this->fileman->contextid = $this->currentcategory['contextid']; 1215 $this->fileman->component = 'question'; 1216 $this->fileman->filearea = 'questiontext'; 1217 $this->fileman->itemid = $data['id']; 1218 $data['questiontext'] = moodle1_converter::migrate_referenced_files($data['questiontext'], $this->fileman); 1219 1220 // Migrate files in generalfeedback. 1221 $this->fileman->filearea = 'generalfeedback'; 1222 $data['generalfeedback'] = moodle1_converter::migrate_referenced_files($data['generalfeedback'], $this->fileman); 1223 1224 // replay the upgrade step 2010080901 - updating question image 1225 if (!empty($data['image'])) { 1226 if (core_text::substr(core_text::strtolower($data['image']), 0, 7) == 'http://') { 1227 // it is a link, appending to existing question text 1228 $data['questiontext'] .= ' <img src="' . $data['image'] . '" />'; 1229 1230 } else { 1231 // it is a file in course_files 1232 $filename = basename($data['image']); 1233 $filepath = dirname($data['image']); 1234 if (empty($filepath) or $filepath == '.' or $filepath == '/') { 1235 $filepath = '/'; 1236 } else { 1237 // append / 1238 $filepath = '/'.trim($filepath, './@#$ ').'/'; 1239 } 1240 1241 if (file_exists($this->converter->get_tempdir_path().'/course_files'.$filepath.$filename)) { 1242 $this->fileman->contextid = $this->currentcategory['contextid']; 1243 $this->fileman->component = 'question'; 1244 $this->fileman->filearea = 'questiontext'; 1245 $this->fileman->itemid = $data['id']; 1246 $this->fileman->migrate_file('course_files'.$filepath.$filename, '/', $filename); 1247 // note this is slightly different from the upgrade code as we put the file into the 1248 // root folder here. this makes our life easier as we do not need to create all the 1249 // directories within the specified filearea/itemid 1250 $data['questiontext'] .= ' <img src="@@PLUGINFILE@@/' . $filename . '" />'; 1251 1252 } else { 1253 $this->log('question file not found', backup::LOG_WARNING, array($data['id'], $filepath.$filename)); 1254 } 1255 } 1256 } 1257 unset($data['image']); 1258 1259 // replay the upgrade step 2011060301 - Rename field defaultgrade on table question to defaultmark 1260 $data['defaultmark'] = $data['defaultgrade']; 1261 1262 // write the common question data 1263 $this->xmlwriter->begin_tag('question', array('id' => $data['id'])); 1264 foreach (array( 1265 'parent', 'name', 'questiontext', 'questiontextformat', 1266 'generalfeedback', 'generalfeedbackformat', 'defaultmark', 1267 'penalty', 'qtype', 'length', 'stamp', 'version', 'hidden', 1268 'timecreated', 'timemodified', 'createdby', 'modifiedby' 1269 ) as $fieldname) { 1270 if (!array_key_exists($fieldname, $data)) { 1271 throw new moodle1_convert_exception('missing_common_question_field', $fieldname); 1272 } 1273 $this->xmlwriter->full_tag($fieldname, $data[$fieldname]); 1274 } 1275 // unless we know that the given qtype does not append any own structures, 1276 // give the handler a chance to do so now 1277 if (!in_array($qtype, array('description', 'random'))) { 1278 $handler = $this->get_qtype_handler($qtype); 1279 if ($handler === false) { 1280 $this->log('question type converter not found', backup::LOG_ERROR, $qtype); 1281 1282 } else { 1283 $this->xmlwriter->begin_tag('plugin_qtype_'.$qtype.'_question'); 1284 $handler->use_xml_writer($this->xmlwriter); 1285 $handler->process_question($data, $raw); 1286 $this->xmlwriter->end_tag('plugin_qtype_'.$qtype.'_question'); 1287 } 1288 } 1289 1290 $this->xmlwriter->end_tag('question'); 1291 } 1292 1293 /** 1294 * Closes the questions wrapper 1295 */ 1296 public function on_questions_end() { 1297 if ($this->questionswrapperwritten) { 1298 $this->xmlwriter->end_tag('questions'); 1299 } 1300 } 1301 1302 /** 1303 * Closes the question_category and annotates the category id 1304 * so that it can be dumped into course/inforef.xml 1305 */ 1306 public function on_question_category_end() { 1307 // make sure that the category data were written by {@link self::process_question()} 1308 // if not, write it now. this may happen when the current category does not contain any 1309 // questions so the subpaths is missing completely 1310 if (empty($this->currentcategorywritten)) { 1311 $this->write_xml('question_category', $this->currentcategory, array('/question_category/id')); 1312 } else { 1313 $this->xmlwriter->end_tag('question_category'); 1314 } 1315 $this->converter->set_stash('question_categories', $this->currentcategory, $this->currentcategory['id']); 1316 } 1317 1318 /** 1319 * Stops writing questions.xml 1320 */ 1321 public function on_question_categories_end() { 1322 $this->xmlwriter->end_tag('question_categories'); 1323 $this->close_xml_writer(); 1324 } 1325 1326 /** 1327 * Provides access to the qtype handlers 1328 * 1329 * Returns either list of all qtype handler instances (if passed '*') or a particular handler 1330 * for the given qtype or false if the qtype is not supported. 1331 * 1332 * @throws moodle1_convert_exception 1333 * @param string $qtype the name of the question type or '*' for returning all 1334 * @return array|moodle1_qtype_handler|bool 1335 */ 1336 protected function get_qtype_handler($qtype) { 1337 1338 if (is_null($this->qtypehandlers)) { 1339 // initialize the list of qtype handler instances 1340 $this->qtypehandlers = array(); 1341 foreach (core_component::get_plugin_list('qtype') as $qtypename => $qtypelocation) { 1342 $filename = $qtypelocation.'/backup/moodle1/lib.php'; 1343 if (file_exists($filename)) { 1344 $classname = 'moodle1_qtype_'.$qtypename.'_handler'; 1345 require_once($filename); 1346 if (!class_exists($classname)) { 1347 throw new moodle1_convert_exception('missing_handler_class', $classname); 1348 } 1349 $this->log('registering handler', backup::LOG_DEBUG, $classname, 2); 1350 $this->qtypehandlers[$qtypename] = new $classname($this, $qtypename); 1351 } 1352 } 1353 } 1354 1355 if ($qtype === '*') { 1356 return $this->qtypehandlers; 1357 1358 } else if (isset($this->qtypehandlers[$qtype])) { 1359 return $this->qtypehandlers[$qtype]; 1360 1361 } else { 1362 return false; 1363 } 1364 } 1365 } 1366 1367 1368 /** 1369 * Handles the conversion of the scales included in the moodle.xml file 1370 */ 1371 class moodle1_scales_handler extends moodle1_handler { 1372 1373 /** @var moodle1_file_manager instance used to convert question images */ 1374 protected $fileman = null; 1375 1376 /** 1377 * Registers paths 1378 */ 1379 public function get_paths() { 1380 return array( 1381 new convert_path('scales', '/MOODLE_BACKUP/COURSE/SCALES'), 1382 new convert_path( 1383 'scale', '/MOODLE_BACKUP/COURSE/SCALES/SCALE', 1384 array( 1385 'renamefields' => array( 1386 'scaletext' => 'scale', 1387 ), 1388 'addfields' => array( 1389 'descriptionformat' => 0, 1390 ) 1391 ) 1392 ), 1393 ); 1394 } 1395 1396 /** 1397 * Prepare the file manager for the files embedded in the scale description field 1398 */ 1399 public function on_scales_start() { 1400 $syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM); 1401 $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'scale'); 1402 } 1403 1404 /** 1405 * This is executed every time we have one <SCALE> data available 1406 * 1407 * @param array $data 1408 * @param array $raw 1409 * @return array 1410 */ 1411 public function process_scale(array $data, array $raw) { 1412 global $CFG; 1413 1414 // replay upgrade step 2009110400 1415 if ($CFG->texteditors !== 'textarea') { 1416 $data['description'] = text_to_html($data['description'], false, false, true); 1417 $data['descriptionformat'] = FORMAT_HTML; 1418 } 1419 1420 // convert course files embedded into the scale description field 1421 $this->fileman->itemid = $data['id']; 1422 $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman); 1423 1424 // stash the scale 1425 $this->converter->set_stash('scales', $data, $data['id']); 1426 } 1427 } 1428 1429 1430 /** 1431 * Handles the conversion of the outcomes 1432 */ 1433 class moodle1_outcomes_handler extends moodle1_xml_handler { 1434 1435 /** @var moodle1_file_manager instance used to convert images embedded into outcome descriptions */ 1436 protected $fileman = null; 1437 1438 /** 1439 * Registers paths 1440 */ 1441 public function get_paths() { 1442 return array( 1443 new convert_path('gradebook_grade_outcomes', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES'), 1444 new convert_path( 1445 'gradebook_grade_outcome', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES/GRADE_OUTCOME', 1446 array( 1447 'addfields' => array( 1448 'descriptionformat' => FORMAT_MOODLE, 1449 ), 1450 ) 1451 ), 1452 ); 1453 } 1454 1455 /** 1456 * Prepares the file manager and starts writing outcomes.xml 1457 */ 1458 public function on_gradebook_grade_outcomes_start() { 1459 1460 $syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM); 1461 $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'outcome'); 1462 1463 $this->open_xml_writer('outcomes.xml'); 1464 $this->xmlwriter->begin_tag('outcomes_definition'); 1465 } 1466 1467 /** 1468 * Processes GRADE_OUTCOME tags progressively 1469 */ 1470 public function process_gradebook_grade_outcome(array $data, array $raw) { 1471 global $CFG; 1472 1473 // replay the upgrade step 2009110400 1474 if ($CFG->texteditors !== 'textarea') { 1475 $data['description'] = text_to_html($data['description'], false, false, true); 1476 $data['descriptionformat'] = FORMAT_HTML; 1477 } 1478 1479 // convert course files embedded into the outcome description field 1480 $this->fileman->itemid = $data['id']; 1481 $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman); 1482 1483 // write the outcome data 1484 $this->write_xml('outcome', $data, array('/outcome/id')); 1485 1486 return $data; 1487 } 1488 1489 /** 1490 * Closes outcomes.xml 1491 */ 1492 public function on_gradebook_grade_outcomes_end() { 1493 $this->xmlwriter->end_tag('outcomes_definition'); 1494 $this->close_xml_writer(); 1495 } 1496 } 1497 1498 1499 /** 1500 * Handles the conversion of the gradebook structures in the moodle.xml file 1501 */ 1502 class moodle1_gradebook_handler extends moodle1_xml_handler { 1503 1504 /** @var array of (int)gradecategoryid => (int|null)parentcategoryid */ 1505 protected $categoryparent = array(); 1506 1507 /** 1508 * Registers paths 1509 */ 1510 public function get_paths() { 1511 return array( 1512 new convert_path('gradebook', '/MOODLE_BACKUP/COURSE/GRADEBOOK'), 1513 new convert_path('gradebook_grade_letter', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_LETTERS/GRADE_LETTER'), 1514 new convert_path( 1515 'gradebook_grade_category', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_CATEGORIES/GRADE_CATEGORY', 1516 array( 1517 'addfields' => array( 1518 'hidden' => 0, // upgrade step 2010011200 1519 ), 1520 ) 1521 ), 1522 new convert_path('gradebook_grade_item', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM'), 1523 new convert_path('gradebook_grade_item_grades', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM/GRADE_GRADES'), 1524 ); 1525 } 1526 1527 /** 1528 * Initializes the in-memory structures 1529 * 1530 * This should not be needed actually as the moodle.xml contains just one GRADEBOOK 1531 * element. But who knows - maybe someone will want to write a mass conversion 1532 * tool in the future (not me definitely ;-) 1533 */ 1534 public function on_gradebook_start() { 1535 $this->categoryparent = array(); 1536 } 1537 1538 /** 1539 * Processes one GRADE_LETTER data 1540 * 1541 * In Moodle 1.9, all grade_letters are from course context only. Therefore 1542 * we put them here. 1543 */ 1544 public function process_gradebook_grade_letter(array $data, array $raw) { 1545 $this->converter->set_stash('gradebook_gradeletter', $data, $data['id']); 1546 } 1547 1548 /** 1549 * Processes one GRADE_CATEGORY data 1550 */ 1551 public function process_gradebook_grade_category(array $data, array $raw) { 1552 $this->categoryparent[$data['id']] = $data['parent']; 1553 $this->converter->set_stash('gradebook_gradecategory', $data, $data['id']); 1554 } 1555 1556 /** 1557 * Processes one GRADE_ITEM data 1558 */ 1559 public function process_gradebook_grade_item(array $data, array $raw) { 1560 1561 // here we use get_nextid() to get a nondecreasing sequence 1562 $data['sortorder'] = $this->converter->get_nextid(); 1563 1564 if ($data['itemtype'] === 'mod') { 1565 return $this->process_mod_grade_item($data, $raw); 1566 1567 } else if (in_array($data['itemtype'], array('manual', 'course', 'category'))) { 1568 return $this->process_nonmod_grade_item($data, $raw); 1569 1570 } else { 1571 $this->log('unsupported grade_item type', backup::LOG_ERROR, $data['itemtype']); 1572 } 1573 } 1574 1575 /** 1576 * Processes one GRADE_ITEM of the type 'mod' 1577 */ 1578 protected function process_mod_grade_item(array $data, array $raw) { 1579 1580 $stashname = 'gradebook_modgradeitem_'.$data['itemmodule']; 1581 $stashitemid = $data['iteminstance']; 1582 $gradeitems = $this->converter->get_stash_or_default($stashname, $stashitemid, array()); 1583 1584 // typically there will be single item with itemnumber 0 1585 $gradeitems[$data['itemnumber']] = $data; 1586 1587 $this->converter->set_stash($stashname, $gradeitems, $stashitemid); 1588 1589 return $data; 1590 } 1591 1592 /** 1593 * Processes one GRADE_ITEM of te type 'manual' or 'course' or 'category' 1594 */ 1595 protected function process_nonmod_grade_item(array $data, array $raw) { 1596 1597 $stashname = 'gradebook_nonmodgradeitem'; 1598 $stashitemid = $data['id']; 1599 $this->converter->set_stash($stashname, $data, $stashitemid); 1600 1601 return $data; 1602 } 1603 1604 /** 1605 * @todo 1606 */ 1607 public function on_gradebook_grade_item_grades_start() { 1608 } 1609 1610 /** 1611 * Writes the collected information into gradebook.xml 1612 */ 1613 public function on_gradebook_end() { 1614 1615 $this->open_xml_writer('gradebook.xml'); 1616 $this->xmlwriter->begin_tag('gradebook'); 1617 $this->write_grade_categories(); 1618 $this->write_grade_items(); 1619 $this->write_grade_letters(); 1620 $this->xmlwriter->end_tag('gradebook'); 1621 $this->close_xml_writer(); 1622 } 1623 1624 /** 1625 * Writes grade_categories 1626 */ 1627 protected function write_grade_categories() { 1628 1629 $this->xmlwriter->begin_tag('grade_categories'); 1630 foreach ($this->converter->get_stash_itemids('gradebook_gradecategory') as $gradecategoryid) { 1631 $gradecategory = $this->converter->get_stash('gradebook_gradecategory', $gradecategoryid); 1632 $path = $this->calculate_category_path($gradecategoryid); 1633 $gradecategory['depth'] = count($path); 1634 $gradecategory['path'] = '/'.implode('/', $path).'/'; 1635 $this->write_xml('grade_category', $gradecategory, array('/grade_category/id')); 1636 } 1637 $this->xmlwriter->end_tag('grade_categories'); 1638 } 1639 1640 /** 1641 * Calculates the path to the grade_category 1642 * 1643 * Moodle 1.9 backup does not store the grade_category's depth and path. This method is used 1644 * to repopulate this information using the $this->categoryparent values. 1645 * 1646 * @param int $categoryid 1647 * @return array of ids including the categoryid 1648 */ 1649 protected function calculate_category_path($categoryid) { 1650 1651 if (!array_key_exists($categoryid, $this->categoryparent)) { 1652 throw new moodle1_convert_exception('gradebook_unknown_categoryid', null, $categoryid); 1653 } 1654 1655 $path = array($categoryid); 1656 $parent = $this->categoryparent[$categoryid]; 1657 while (!is_null($parent)) { 1658 array_unshift($path, $parent); 1659 $parent = $this->categoryparent[$parent]; 1660 if (in_array($parent, $path)) { 1661 throw new moodle1_convert_exception('circular_reference_in_categories_tree'); 1662 } 1663 } 1664 1665 return $path; 1666 } 1667 1668 /** 1669 * Writes grade_items 1670 */ 1671 protected function write_grade_items() { 1672 1673 $this->xmlwriter->begin_tag('grade_items'); 1674 foreach ($this->converter->get_stash_itemids('gradebook_nonmodgradeitem') as $gradeitemid) { 1675 $gradeitem = $this->converter->get_stash('gradebook_nonmodgradeitem', $gradeitemid); 1676 $this->write_xml('grade_item', $gradeitem, array('/grade_item/id')); 1677 } 1678 $this->xmlwriter->end_tag('grade_items'); 1679 } 1680 1681 /** 1682 * Writes grade_letters 1683 */ 1684 protected function write_grade_letters() { 1685 1686 $this->xmlwriter->begin_tag('grade_letters'); 1687 foreach ($this->converter->get_stash_itemids('gradebook_gradeletter') as $gradeletterid) { 1688 $gradeletter = $this->converter->get_stash('gradebook_gradeletter', $gradeletterid); 1689 $this->write_xml('grade_letter', $gradeletter, array('/grade_letter/id')); 1690 } 1691 $this->xmlwriter->end_tag('grade_letters'); 1692 } 1693 } 1694 1695 1696 /** 1697 * Shared base class for activity modules, blocks and qtype handlers 1698 */ 1699 abstract class moodle1_plugin_handler extends moodle1_xml_handler { 1700 1701 /** @var string */ 1702 protected $plugintype; 1703 1704 /** @var string */ 1705 protected $pluginname; 1706 1707 /** 1708 * @param moodle1_converter $converter the converter that requires us 1709 * @param string $plugintype 1710 * @param string $pluginname 1711 */ 1712 public function __construct(moodle1_converter $converter, $plugintype, $pluginname) { 1713 1714 parent::__construct($converter); 1715 $this->plugintype = $plugintype; 1716 $this->pluginname = $pluginname; 1717 } 1718 1719 /** 1720 * Returns the normalized name of the plugin, eg mod_workshop 1721 * 1722 * @return string 1723 */ 1724 public function get_component_name() { 1725 return $this->plugintype.'_'.$this->pluginname; 1726 } 1727 } 1728 1729 1730 /** 1731 * Base class for all question type handlers 1732 */ 1733 abstract class moodle1_qtype_handler extends moodle1_plugin_handler { 1734 1735 /** @var moodle1_question_bank_handler */ 1736 protected $qbankhandler; 1737 1738 /** 1739 * Returns the list of paths within one <QUESTION> that this qtype needs to have included 1740 * in the grouped question structure 1741 * 1742 * @return array of strings 1743 */ 1744 public function get_question_subpaths() { 1745 return array(); 1746 } 1747 1748 /** 1749 * Gives the qtype handler a chance to write converted data into questions.xml 1750 * 1751 * @param array $data grouped question data 1752 * @param array $raw grouped raw QUESTION data 1753 */ 1754 public function process_question(array $data, array $raw) { 1755 } 1756 1757 /** 1758 * Converts the answers and writes them into the questions.xml 1759 * 1760 * The structure "answers" is used by several qtypes. It contains data from {question_answers} table. 1761 * 1762 * @param array $answers as parsed by the grouped parser in moodle.xml 1763 * @param string $qtype containing the answers 1764 */ 1765 protected function write_answers(array $answers, $qtype) { 1766 1767 $this->xmlwriter->begin_tag('answers'); 1768 foreach ($answers as $elementname => $elements) { 1769 foreach ($elements as $element) { 1770 $answer = $this->convert_answer($element, $qtype); 1771 // Migrate images in answertext. 1772 if ($answer['answerformat'] == FORMAT_HTML) { 1773 $answer['answertext'] = $this->migrate_files($answer['answertext'], 'question', 'answer', $answer['id']); 1774 } 1775 // Migrate images in feedback. 1776 if ($answer['feedbackformat'] == FORMAT_HTML) { 1777 $answer['feedback'] = $this->migrate_files($answer['feedback'], 'question', 'answerfeedback', $answer['id']); 1778 } 1779 $this->write_xml('answer', $answer, array('/answer/id')); 1780 } 1781 } 1782 $this->xmlwriter->end_tag('answers'); 1783 } 1784 1785 /** 1786 * Migrate files belonging to one qtype plugin text field. 1787 * 1788 * @param array $text the html fragment containing references to files 1789 * @param string $component the component for restored files 1790 * @param string $filearea the file area for restored files 1791 * @param int $itemid the itemid for restored files 1792 * 1793 * @return string the text for this field, after files references have been processed 1794 */ 1795 protected function migrate_files($text, $component, $filearea, $itemid) { 1796 $context = $this->qbankhandler->get_current_category_context(); 1797 $fileman = $this->qbankhandler->get_file_manager(); 1798 $fileman->contextid = $context['contextid']; 1799 $fileman->component = $component; 1800 $fileman->filearea = $filearea; 1801 $fileman->itemid = $itemid; 1802 $text = moodle1_converter::migrate_referenced_files($text, $fileman); 1803 return $text; 1804 } 1805 1806 /** 1807 * Writes the grouped numerical_units structure 1808 * 1809 * @param array $numericalunits 1810 */ 1811 protected function write_numerical_units(array $numericalunits) { 1812 1813 $this->xmlwriter->begin_tag('numerical_units'); 1814 foreach ($numericalunits as $elementname => $elements) { 1815 foreach ($elements as $element) { 1816 $element['id'] = $this->converter->get_nextid(); 1817 $this->write_xml('numerical_unit', $element, array('/numerical_unit/id')); 1818 } 1819 } 1820 $this->xmlwriter->end_tag('numerical_units'); 1821 } 1822 1823 /** 1824 * Writes the numerical_options structure 1825 * 1826 * @see get_default_numerical_options() 1827 * @param array $numericaloption 1828 */ 1829 protected function write_numerical_options(array $numericaloption) { 1830 1831 $this->xmlwriter->begin_tag('numerical_options'); 1832 if (!empty($numericaloption)) { 1833 $this->write_xml('numerical_option', $numericaloption, array('/numerical_option/id')); 1834 } 1835 $this->xmlwriter->end_tag('numerical_options'); 1836 } 1837 1838 /** 1839 * Returns default numerical_option structure 1840 * 1841 * This structure is not present in moodle.xml, we create a new artificial one here. 1842 * 1843 * @see write_numerical_options() 1844 * @param int $oldquestiontextformat 1845 * @return array 1846 */ 1847 protected function get_default_numerical_options($oldquestiontextformat, $units) { 1848 global $CFG; 1849 1850 // replay the upgrade step 2009100100 - new table 1851 $options = array( 1852 'id' => $this->converter->get_nextid(), 1853 'instructions' => null, 1854 'instructionsformat' => 0, 1855 'showunits' => 0, 1856 'unitsleft' => 0, 1857 'unitgradingtype' => 0, 1858 'unitpenalty' => 0.1 1859 ); 1860 1861 // replay the upgrade step 2009100101 1862 if ($CFG->texteditors !== 'textarea' and $oldquestiontextformat == FORMAT_MOODLE) { 1863 $options['instructionsformat'] = FORMAT_HTML; 1864 } else { 1865 $options['instructionsformat'] = $oldquestiontextformat; 1866 } 1867 1868 // Set a good default, depending on whether there are any units defined. 1869 if (empty($units)) { 1870 $options['showunits'] = 3; 1871 } 1872 1873 return $options; 1874 } 1875 1876 /** 1877 * Writes the dataset_definitions structure 1878 * 1879 * @param array $datasetdefinitions array of dataset_definition structures 1880 */ 1881 protected function write_dataset_definitions(array $datasetdefinitions) { 1882 1883 $this->xmlwriter->begin_tag('dataset_definitions'); 1884 foreach ($datasetdefinitions as $datasetdefinition) { 1885 $this->xmlwriter->begin_tag('dataset_definition', array('id' => $this->converter->get_nextid())); 1886 foreach (array('category', 'name', 'type', 'options', 'itemcount') as $element) { 1887 $this->xmlwriter->full_tag($element, $datasetdefinition[$element]); 1888 } 1889 $this->xmlwriter->begin_tag('dataset_items'); 1890 if (!empty($datasetdefinition['dataset_items']['dataset_item'])) { 1891 foreach ($datasetdefinition['dataset_items']['dataset_item'] as $datasetitem) { 1892 $datasetitem['id'] = $this->converter->get_nextid(); 1893 $this->write_xml('dataset_item', $datasetitem, array('/dataset_item/id')); 1894 } 1895 } 1896 $this->xmlwriter->end_tag('dataset_items'); 1897 $this->xmlwriter->end_tag('dataset_definition'); 1898 } 1899 $this->xmlwriter->end_tag('dataset_definitions'); 1900 } 1901 1902 /// implementation details follow ////////////////////////////////////////// 1903 1904 public function __construct(moodle1_question_bank_handler $qbankhandler, $qtype) { 1905 1906 parent::__construct($qbankhandler->get_converter(), 'qtype', $qtype); 1907 $this->qbankhandler = $qbankhandler; 1908 } 1909 1910 /** 1911 * @see self::get_question_subpaths() 1912 */ 1913 final public function get_paths() { 1914 throw new moodle1_convert_exception('qtype_handler_get_paths'); 1915 } 1916 1917 /** 1918 * Question type handlers cannot open the xml_writer 1919 */ 1920 final protected function open_xml_writer($filename) { 1921 throw new moodle1_convert_exception('opening_xml_writer_forbidden'); 1922 } 1923 1924 /** 1925 * Question type handlers cannot close the xml_writer 1926 */ 1927 final protected function close_xml_writer() { 1928 throw new moodle1_convert_exception('opening_xml_writer_forbidden'); 1929 } 1930 1931 /** 1932 * Provides a xml_writer instance to this qtype converter 1933 * 1934 * @param xml_writer $xmlwriter 1935 */ 1936 public function use_xml_writer(xml_writer $xmlwriter) { 1937 $this->xmlwriter = $xmlwriter; 1938 } 1939 1940 /** 1941 * Converts <ANSWER> structure into the new <answer> one 1942 * 1943 * See question_backup_answers() in 1.9 and add_question_question_answers() in 2.0 1944 * 1945 * @param array $old the parsed answer array in moodle.xml 1946 * @param string $qtype the question type the answer is part of 1947 * @return array 1948 */ 1949 private function convert_answer(array $old, $qtype) { 1950 global $CFG; 1951 1952 $new = array(); 1953 $new['id'] = $old['id']; 1954 $new['answertext'] = $old['answer_text']; 1955 $new['answerformat'] = 0; // upgrade step 2010080900 1956 $new['fraction'] = $old['fraction']; 1957 $new['feedback'] = $old['feedback']; 1958 $new['feedbackformat'] = 0; // upgrade step 2010080900 1959 1960 // replay upgrade step 2010080901 1961 if ($qtype !== 'multichoice') { 1962 $new['answerformat'] = FORMAT_PLAIN; 1963 } else { 1964 $new['answertext'] = text_to_html($new['answertext'], false, false, true); 1965 $new['answerformat'] = FORMAT_HTML; 1966 } 1967 1968 if ($CFG->texteditors !== 'textarea') { 1969 if ($qtype == 'essay') { 1970 $new['feedback'] = text_to_html($new['feedback'], false, false, true); 1971 } 1972 $new['feedbackformat'] = FORMAT_HTML; 1973 1974 } else { 1975 $new['feedbackformat'] = FORMAT_MOODLE; 1976 } 1977 1978 return $new; 1979 } 1980 } 1981 1982 1983 /** 1984 * Base class for activity module handlers 1985 */ 1986 abstract class moodle1_mod_handler extends moodle1_plugin_handler { 1987 1988 /** 1989 * Returns the name of the module, eg. 'forum' 1990 * 1991 * @return string 1992 */ 1993 public function get_modname() { 1994 return $this->pluginname; 1995 } 1996 1997 /** 1998 * Returns course module information for the given instance id 1999 * 2000 * The information for this instance id has been stashed by 2001 * {@link moodle1_course_outline_handler::process_course_module()} 2002 * 2003 * @param int $instance the module instance id 2004 * @param string $modname the module type, defaults to $this->pluginname 2005 * @return int 2006 */ 2007 protected function get_cminfo($instance, $modname = null) { 2008 2009 if (is_null($modname)) { 2010 $modname = $this->pluginname; 2011 } 2012 return $this->converter->get_stash('cminfo_'.$modname, $instance); 2013 } 2014 } 2015 2016 2017 /** 2018 * Base class for all modules that are successors of the 1.9 resource module 2019 */ 2020 abstract class moodle1_resource_successor_handler extends moodle1_mod_handler { 2021 2022 /** 2023 * Resource successors do not attach to paths themselves, they are called explicitely 2024 * by moodle1_mod_resource_handler 2025 * 2026 * @return array 2027 */ 2028 final public function get_paths() { 2029 return array(); 2030 } 2031 2032 /** 2033 * Converts /MOODLE_BACKUP/COURSE/MODULES/MOD/RESOURCE data 2034 * 2035 * Called by {@link moodle1_mod_resource_handler::process_resource()} 2036 * 2037 * @param array $data pre-cooked legacy resource data 2038 * @param array $raw raw legacy resource data 2039 */ 2040 public function process_legacy_resource(array $data, array $raw = null) { 2041 } 2042 2043 /** 2044 * Called when the parses reaches the end </MOD> resource tag 2045 * 2046 * @param array $data the data returned by {@link self::process_resource} or just pre-cooked 2047 */ 2048 public function on_legacy_resource_end(array $data) { 2049 } 2050 } 2051 2052 /** 2053 * Base class for block handlers 2054 */ 2055 abstract class moodle1_block_handler extends moodle1_plugin_handler { 2056 2057 public function get_paths() { 2058 $blockname = strtoupper($this->pluginname); 2059 return array( 2060 new convert_path('block', "/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/{$blockname}"), 2061 ); 2062 } 2063 2064 public function process_block(array $data) { 2065 $newdata = $this->convert_common_block_data($data); 2066 2067 $this->write_block_xml($newdata, $data); 2068 $this->write_inforef_xml($newdata, $data); 2069 $this->write_roles_xml($newdata, $data); 2070 2071 return $data; 2072 } 2073 2074 protected function convert_common_block_data(array $olddata) { 2075 $newdata = array(); 2076 2077 $newdata['blockname'] = $olddata['name']; 2078 $newdata['parentcontextid'] = $this->converter->get_contextid(CONTEXT_COURSE, 0); 2079 $newdata['showinsubcontexts'] = 0; 2080 $newdata['pagetypepattern'] = $olddata['pagetype'].='-*'; 2081 $newdata['subpagepattern'] = null; 2082 $newdata['defaultregion'] = ($olddata['position']=='l')?'side-pre':'side-post'; 2083 $newdata['defaultweight'] = $olddata['weight']; 2084 $newdata['configdata'] = $this->convert_configdata($olddata); 2085 2086 return $newdata; 2087 } 2088 2089 protected function convert_configdata(array $olddata) { 2090 return $olddata['configdata']; 2091 } 2092 2093 protected function write_block_xml($newdata, $data) { 2094 $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']); 2095 2096 $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/block.xml"); 2097 $this->xmlwriter->begin_tag('block', array('id' => $data['id'], 'contextid' => $contextid)); 2098 2099 foreach ($newdata as $field => $value) { 2100 $this->xmlwriter->full_tag($field, $value); 2101 } 2102 2103 $this->xmlwriter->begin_tag('block_positions'); 2104 $this->xmlwriter->begin_tag('block_position', array('id' => 1)); 2105 $this->xmlwriter->full_tag('contextid', $newdata['parentcontextid']); 2106 $this->xmlwriter->full_tag('pagetype', $data['pagetype']); 2107 $this->xmlwriter->full_tag('subpage', ''); 2108 $this->xmlwriter->full_tag('visible', $data['visible']); 2109 $this->xmlwriter->full_tag('region', $newdata['defaultregion']); 2110 $this->xmlwriter->full_tag('weight', $newdata['defaultweight']); 2111 $this->xmlwriter->end_tag('block_position'); 2112 $this->xmlwriter->end_tag('block_positions'); 2113 $this->xmlwriter->end_tag('block'); 2114 $this->close_xml_writer(); 2115 } 2116 2117 protected function write_inforef_xml($newdata, $data) { 2118 $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml"); 2119 $this->xmlwriter->begin_tag('inforef'); 2120 // Subclasses may provide inforef contents if needed 2121 $this->xmlwriter->end_tag('inforef'); 2122 $this->close_xml_writer(); 2123 } 2124 2125 protected function write_roles_xml($newdata, $data) { 2126 // This is an empty shell, as the moodle1 converter doesn't handle user data. 2127 $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/roles.xml"); 2128 $this->xmlwriter->begin_tag('roles'); 2129 $this->xmlwriter->full_tag('role_overrides', ''); 2130 $this->xmlwriter->full_tag('role_assignments', ''); 2131 $this->xmlwriter->end_tag('roles'); 2132 $this->close_xml_writer(); 2133 } 2134 } 2135 2136 2137 /** 2138 * Base class for block generic handler 2139 */ 2140 class moodle1_block_generic_handler extends moodle1_block_handler { 2141 2142 } 2143 2144 /** 2145 * Base class for the activity modules' subplugins 2146 */ 2147 abstract class moodle1_submod_handler extends moodle1_plugin_handler { 2148 2149 /** @var moodle1_mod_handler */ 2150 protected $parenthandler; 2151 2152 /** 2153 * @param moodle1_mod_handler $parenthandler the handler of a module we are subplugin of 2154 * @param string $subplugintype the type of the subplugin 2155 * @param string $subpluginname the name of the subplugin 2156 */ 2157 public function __construct(moodle1_mod_handler $parenthandler, $subplugintype, $subpluginname) { 2158 $this->parenthandler = $parenthandler; 2159 parent::__construct($parenthandler->converter, $subplugintype, $subpluginname); 2160 } 2161 2162 /** 2163 * Activity module subplugins can't declare any paths to handle 2164 * 2165 * The paths must be registered by the parent module and then re-dispatched to the 2166 * relevant subplugins for eventual processing. 2167 * 2168 * @return array empty array 2169 */ 2170 final public function get_paths() { 2171 return array(); 2172 } 2173 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body