MDL-33424 Images missing when restoring a 1.9 backup
[moodle.git] / backup / converter / moodle1 / handlerlib.php
CommitLineData
1e2c7351
DM
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
30defined('MOODLE_INTERNAL') || die();
31
32require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
33require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
34require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php');
1e2c7351
DM
35
36/**
37 * Handlers factory class
38 */
39abstract 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
9b5f1ad5
DM
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),
ba66847b 52 new moodle1_roles_definition_handler($converter),
e19d0c10 53 new moodle1_question_bank_handler($converter),
e29746c2 54 new moodle1_scales_handler($converter),
ddefec8d 55 new moodle1_outcomes_handler($converter),
544e0d59 56 new moodle1_gradebook_handler($converter),
9b5f1ad5 57 );
1e2c7351
DM
58
59 $handlers = array_merge($handlers, self::get_plugin_handlers('mod', $converter));
60 $handlers = array_merge($handlers, self::get_plugin_handlers('block', $converter));
61
a5fe5912
DM
62 // make sure that all handlers have expected class
63 foreach ($handlers as $handler) {
64 if (!$handler instanceof moodle1_handler) {
716a9616 65 throw new moodle1_convert_exception('wrong_handler_class', get_class($handler));
a5fe5912
DM
66 }
67 }
68
1e2c7351
DM
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
716a9616 80 * @throws moodle1_convert_exception
1e2c7351
DM
81 * @return array of {@link moodle1_handler} instances
82 */
a5fe5912 83 protected static function get_plugin_handlers($type, moodle1_converter $converter) {
1e2c7351
DM
84 global $CFG;
85
86 $handlers = array();
87 $plugins = get_plugin_list($type);
88 foreach ($plugins as $name => $dir) {
89 $handlerfile = $dir . '/backup/moodle1/lib.php';
90 $handlerclass = "moodle1_{$type}_{$name}_handler";
afaabdaf
PN
91 if (file_exists($handlerfile)) {
92 require_once($handlerfile);
93 } elseif ($type == 'block') {
94 $handlerclass = "moodle1_block_generic_handler";
95 } else {
1e2c7351
DM
96 continue;
97 }
1e2c7351
DM
98
99 if (!class_exists($handlerclass)) {
716a9616 100 throw new moodle1_convert_exception('missing_handler_class', $handlerclass);
1e2c7351
DM
101 }
102 $handlers[] = new $handlerclass($converter, $type, $name);
103 }
104 return $handlers;
105 }
106}
107
a5fe5912 108
1e2c7351 109/**
a5fe5912 110 * Base backup conversion handler
1e2c7351 111 */
fe50f530 112abstract class moodle1_handler implements loggable {
1e2c7351
DM
113
114 /** @var moodle1_converter */
115 protected $converter;
116
1e2c7351
DM
117 /**
118 * @param moodle1_converter $converter the converter that requires us
119 */
120 public function __construct(moodle1_converter $converter) {
1e2c7351
DM
121 $this->converter = $converter;
122 }
123
a5fe5912
DM
124 /**
125 * @return moodle1_converter the converter that required this handler
126 */
127 public function get_converter() {
128 return $this->converter;
129 }
fe50f530
DM
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 }
a5fe5912
DM
143}
144
145
146/**
147 * Base backup conversion handler that generates an XML file
148 */
149abstract 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
1e2c7351
DM
157 /**
158 * Opens the XML writer - after calling, one is free to use $xmlwriter
159 *
a5fe5912 160 * @param string $filename XML file name to write into
1e2c7351
DM
161 * @return void
162 */
ae80f68f 163 protected function open_xml_writer($filename) {
1e2c7351 164
a5fe5912 165 if (!is_null($this->xmlfilename) and $filename !== $this->xmlfilename) {
716a9616 166 throw new moodle1_convert_exception('xml_writer_already_opened_for_other_file', $this->xmlfilename);
1e2c7351
DM
167 }
168
169 if (!$this->xmlwriter instanceof xml_writer) {
a5fe5912
DM
170 $this->xmlfilename = $filename;
171 $fullpath = $this->converter->get_workdir_path() . '/' . $this->xmlfilename;
1e2c7351
DM
172 $directory = pathinfo($fullpath, PATHINFO_DIRNAME);
173
174 if (!check_dir_exists($directory)) {
716a9616 175 throw new moodle1_convert_exception('unable_create_target_directory', $directory);
1e2c7351 176 }
96f7c7ad 177 $this->xmlwriter = new xml_writer(new file_xml_output($fullpath), new moodle1_xml_transformer());
1e2c7351
DM
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 */
ae80f68f 189 protected function close_xml_writer() {
1e2c7351
DM
190 if ($this->xmlwriter instanceof xml_writer) {
191 $this->xmlwriter->stop();
1e2c7351 192 }
a5fe5912
DM
193 unset($this->xmlwriter);
194 $this->xmlwriter = null;
195 $this->xmlfilename = null;
1e2c7351
DM
196 }
197
ae80f68f
DM
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
1e2c7351 212 /**
baa6f6a8 213 * Writes the given XML tree data into the currently opened file
beb7de37
DM
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
f3b0b1e5 217 * @param array $attribs list of additional fields written as attributes instead of nested elements
beb7de37 218 * @param string $parent used internally during the recursion, do not set yourself
1e2c7351 219 */
ae80f68f
DM
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 }
beb7de37
DM
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)) {
f3b0b1e5 232 if (in_array($mypath . '/' . $name, $attribs)) {
beb7de37
DM
233 $myattribs[$name] = $value;
234 unset($data[$name]);
235 }
236 }
237 }
a5fe5912 238
beb7de37
DM
239 // reorder the $data so that all sub-branches are at the end (needed by our parser)
240 $leaves = array();
241 $branches = array();
a5fe5912 242 foreach ($data as $name => $value) {
beb7de37
DM
243 if (is_array($value)) {
244 $branches[$name] = $value;
245 } else {
246 $leaves[$name] = $value;
247 }
a5fe5912 248 }
beb7de37
DM
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
1056c9b9 256 $this->write_xml($name, $value, $attribs, $mypath.'/');
beb7de37
DM
257 } else {
258 $this->xmlwriter->full_tag($name, $value);
259 }
260 }
261
262 $this->xmlwriter->end_tag($element);
a5fe5912 263 }
317f1c6f
DM
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 }
1e2c7351
DM
293}
294
295
1e2c7351
DM
296/**
297 * Process the root element of the backup file
298 */
99a82518 299class moodle1_root_handler extends moodle1_xml_handler {
1e2c7351
DM
300
301 public function get_paths() {
302 return array(new convert_path('root_element', '/MOODLE_BACKUP'));
303 }
304
66f79e50
DM
305 /**
306 * Converts course_files and site_files
307 */
308 public function on_root_element_start() {
fe50f530 309
66f79e50
DM
310 // convert course files
311 $fileshandler = new moodle1_files_handler($this->converter);
312 $fileshandler->process();
313 }
314
1e2c7351
DM
315 /**
316 * This is executed at the end of the moodle.xml parsing
317 */
318 public function on_root_element_end() {
99a82518
DM
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
e29746c2
DM
325 ////////////////////////////////////////////////////////////////////////
326 // write moodle_backup.xml
327 ////////////////////////////////////////////////////////////////////////
99a82518
DM
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']);
10d7dac5
DM
351 } else {
352 $this->xmlwriter->full_tag('original_site_identifier_hash', null);
99a82518
DM
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']);
26cac34a 358 $this->xmlwriter->full_tag('original_system_contextid', $this->converter->get_contextid(CONTEXT_SYSTEM));
e19d0c10
DM
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));
99a82518
DM
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();
4ece48dc
DM
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
99a82518
DM
404 }
405 $this->xmlwriter->end_tag('activities');
406
407 // moodle_backup/information/contents/sections
408 $this->xmlwriter->begin_tag('sections');
409 $sectionsettings = array();
4ece48dc 410 foreach ($this->converter->get_stash_itemids('sectioninfo') as $sectionid) {
99a82518
DM
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,
99a82518
DM
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();
c171d28e 482
e29746c2 483 ////////////////////////////////////////////////////////////////////////
66f79e50 484 // write files.xml
e29746c2 485 ////////////////////////////////////////////////////////////////////////
66f79e50
DM
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
e29746c2
DM
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 ////////////////////////////////////////////////////////////////////////
e19d0c10
DM
508 $this->open_xml_writer('course/inforef.xml');
509 $this->xmlwriter->begin_tag('inforef');
510
511 $this->xmlwriter->begin_tag('fileref');
718f7816 512 // legacy course files
0e3a69b0
DM
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 }
e19d0c10
DM
518 }
519 // todo site files
718f7816
DM
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 }
e19d0c10
DM
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
317f1c6f
DM
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');
317f1c6f 544 $this->make_sure_xml_exists('groups.xml', 'groups');
317f1c6f
DM
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()));
a5fe5912 551 }
8312ab67
DM
552}
553
554
555/**
66f79e50 556 * The class responsible for course and site files migration
8312ab67 557 *
66f79e50 558 * @todo migrate site_files
8312ab67
DM
559 */
560class moodle1_files_handler extends moodle1_xml_handler {
561
8312ab67
DM
562 /**
563 * Migrates course_files and site_files in the converter workdir
564 */
565 public function process() {
8312ab67 566 $this->migrate_course_files();
66f79e50 567 // todo $this->migrate_site_files();
8312ab67
DM
568 }
569
570 /**
571 * Migrates course_files in the converter workdir
572 */
573 protected function migrate_course_files() {
66f79e50
DM
574 $ids = array();
575 $fileman = $this->converter->get_file_manager($this->converter->get_contextid(CONTEXT_COURSE), 'course', 'legacy');
78d22af9 576 $this->converter->set_stash('course_files_ids', array());
214c4924
DM
577 if (file_exists($this->converter->get_tempdir_path().'/course_files')) {
578 $ids = $fileman->migrate_directory('course_files');
66f79e50 579 $this->converter->set_stash('course_files_ids', $ids);
8312ab67 580 }
fe50f530 581 $this->log('course files migrated', backup::LOG_INFO, count($ids));
8312ab67 582 }
a5fe5912
DM
583}
584
585
586/**
587 * Handles the conversion of /MOODLE_BACKUP/INFO paths
beb7de37
DM
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.
a5fe5912
DM
591 */
592class moodle1_info_handler extends moodle1_handler {
593
99a82518
DM
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
a5fe5912
DM
600 public function get_paths() {
601 return array(
beb7de37 602 new convert_path('info', '/MOODLE_BACKUP/INFO'),
a5fe5912
DM
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
99a82518
DM
609 /**
610 * Stashes the backup info for later processing by {@link moodle1_root_handler}
611 */
a5fe5912 612 public function process_info($data) {
99a82518 613 $this->converter->set_stash('backup_info', $data);
a5fe5912 614 }
1e2c7351 615
99a82518
DM
616 /**
617 * Initializes the in-memory cache for the current mod
618 */
a5fe5912 619 public function process_info_details_mod($data) {
99a82518 620 $this->currentmod = $data;
317f1c6f 621 $this->currentmod['instances'] = array();
a5fe5912
DM
622 }
623
99a82518
DM
624 /**
625 * Appends the current instance data to the temporary in-memory cache
626 */
a5fe5912 627 public function process_info_details_mod_instance($data) {
99a82518
DM
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) {
2304cbaa
DM
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;
fe50f530
DM
642 } else {
643 $this->log('unsupported activity module', backup::LOG_WARNING, $modname);
2304cbaa
DM
644 }
645
99a82518
DM
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);
a5fe5912
DM
654 }
655}
656
657
658/**
659 * Handles the conversion of /MOODLE_BACKUP/COURSE/HEADER paths
660 */
661class 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,
8312ab67 679 'legacyfiles' => 2,
a5fe5912
DM
680 'requested' => 0, // @todo not really new, but maybe never backed up?
681 'restrictmodules' => 0,
682 'enablecompletion' => 0,
683 'completionstartonenrol' => 0,
684 'completionnotify' => 0,
beb7de37
DM
685 'tags' => array(),
686 'allowed_modules' => array(),
a5fe5912
DM
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 ),
beb7de37
DM
712 new convert_path(
713 'course_header_category', '/MOODLE_BACKUP/COURSE/HEADER/CATEGORY',
714 array(
715 'newfields' => array(
716 'description' => null,
717 )
718 )
719 ),
a5fe5912 720 );
1e2c7351
DM
721 }
722
723 /**
a5fe5912
DM
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.
1e2c7351 727 */
a5fe5912
DM
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
26cac34a 739 $contextid = $this->converter->get_contextid(CONTEXT_COURSE);
beb7de37
DM
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;
a5fe5912 753
718f7816
DM
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
a5fe5912 760 $this->open_xml_writer('course/course.xml');
8312ab67
DM
761 $this->write_xml('course', $this->course, array('/course/id', '/course/contextid'));
762 $this->close_xml_writer();
1e2c7351
DM
763 }
764}
9b5f1ad5
DM
765
766
767/**
768 * Handles the conversion of course sections and course modules
769 */
770class moodle1_course_outline_handler extends moodle1_xml_handler {
771
4ece48dc
DM
772 /** @var array ordered list of the course contents */
773 protected $coursecontents = array();
99a82518 774
9b5f1ad5
DM
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(
99a82518 783 new convert_path('course_sections', '/MOODLE_BACKUP/COURSE/SECTIONS'),
9b5f1ad5
DM
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(
baa6f6a8
DM
797 'newfields' => array(
798 'completion' => 0,
799 'completiongradeitemnumber' => null,
800 'completionview' => 0,
801 'completionexpected' => 0,
802 'availablefrom' => 0,
803 'availableuntil' => 0,
804 'showavailability' => 0,
805 'availability_info' => array(),
806 'visibleold' => 1,
8c40662e 807 'showdescription' => 0,
baa6f6a8 808 ),
9b5f1ad5 809 'dropfields' => array(
baa6f6a8 810 'instance',
9b5f1ad5
DM
811 'roles_overrides',
812 'roles_assignments',
813 ),
baa6f6a8
DM
814 'renamefields' => array(
815 'type' => 'modulename',
816 ),
9b5f1ad5 817 )
317f1c6f 818 ),
2fe7d14a 819 new convert_path('course_modules', '/MOODLE_BACKUP/COURSE/MODULES'),
317f1c6f
DM
820 // todo new convert_path('course_module_roles_overrides', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'),
821 // todo new convert_path('course_module_roles_assignments', '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_ASSIGNMENTS'),
9b5f1ad5
DM
822 );
823 }
824
825 public function process_course_section($data) {
826 $this->currentsection = $data;
827 }
828
829 /**
830 * Populates the section sequence field (order of course modules) and stashes the
831 * course module info so that is can be dumped to activities/xxxx_x/module.xml later
832 */
baa6f6a8
DM
833 public function process_course_module($data, $raw) {
834 global $CFG;
9b5f1ad5 835
b7119f4f
DM
836 // check that this type of module should be included in the mbz
837 $modinfo = $this->converter->get_stash_itemids('modinfo_'.$data['modulename']);
838 if (empty($modinfo)) {
839 return;
840 }
841
4ece48dc
DM
842 // add the course module into the course contents list
843 $this->coursecontents[$data['id']] = array(
844 'cmid' => $data['id'],
845 'instanceid' => $raw['INSTANCE'],
846 'sectionid' => $this->currentsection['id'],
847 'modulename' => $data['modulename'],
848 'title' => null
849 );
850
9b5f1ad5
DM
851 // add the course module id into the section's sequence
852 if (is_null($this->currentsection['sequence'])) {
853 $this->currentsection['sequence'] = $data['id'];
854 } else {
855 $this->currentsection['sequence'] .= ',' . $data['id'];
856 }
857
858 // add the sectionid and sectionnumber
859 $data['sectionid'] = $this->currentsection['id'];
860 $data['sectionnumber'] = $this->currentsection['number'];
861
baa6f6a8
DM
862 // generate the module version - this is a bit tricky as this information
863 // is not present in 1.9 backups. we will use the currently installed version
864 // whenever we can but that might not be accurate for some modules.
865 // also there might be problem with modules that are not present at the target
866 // host...
867 $versionfile = $CFG->dirroot.'/mod/'.$data['modulename'].'/version.php';
868 if (file_exists($versionfile)) {
caee6e6c 869 $module = new stdClass();
baa6f6a8
DM
870 include($versionfile);
871 $data['version'] = $module->version;
872 } else {
873 $data['version'] = null;
874 }
875
9b5f1ad5 876 // stash the course module info in stashes like 'cminfo_forum' with
baa6f6a8 877 // itemid set to the instance id. this is needed so that module handlers
2fe7d14a
DM
878 // can later obtain information about the course module and dump it into
879 // the module.xml file
baa6f6a8 880 $this->converter->set_stash('cminfo_'.$data['modulename'], $data, $raw['INSTANCE']);
9b5f1ad5
DM
881 }
882
883 /**
99a82518 884 * Writes sections/section_xxx/section.xml file and stashes it, too
9b5f1ad5
DM
885 */
886 public function on_course_section_end() {
887
718f7816
DM
888 // migrate files embedded into the section summary field
889 $contextid = $this->converter->get_contextid(CONTEXT_COURSE);
890 $fileman = $this->converter->get_file_manager($contextid, 'course', 'section', $this->currentsection['id']);
891 $this->currentsection['summary'] = moodle1_converter::migrate_referenced_files($this->currentsection['summary'], $fileman);
892
893 // write section's inforef.xml with the file references
894 $this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/inforef.xml');
895 $this->xmlwriter->begin_tag('inforef');
896 $this->xmlwriter->begin_tag('fileref');
897 $fileids = $fileman->get_fileids();
898 if (is_array($fileids)) {
899 foreach ($fileids as $fileid) {
900 $this->write_xml('file', array('id' => $fileid));
901 }
902 }
903 $this->xmlwriter->end_tag('fileref');
904 $this->xmlwriter->end_tag('inforef');
905 $this->close_xml_writer();
906
907 // stash the section info and write section.xml
99a82518 908 $this->converter->set_stash('sectioninfo', $this->currentsection, $this->currentsection['id']);
9b5f1ad5
DM
909 $this->open_xml_writer('sections/section_' . $this->currentsection['id'] . '/section.xml');
910 $this->write_xml('section', $this->currentsection);
911 $this->close_xml_writer();
912 unset($this->currentsection);
913 }
99a82518
DM
914
915 /**
4ece48dc 916 * Stashes the course contents
99a82518
DM
917 */
918 public function on_course_sections_end() {
4ece48dc 919 $this->converter->set_stash('coursecontents', $this->coursecontents);
99a82518 920 }
2fe7d14a
DM
921
922 /**
923 * Writes the information collected by mod handlers
924 */
925 public function on_course_modules_end() {
926
927 foreach ($this->converter->get_stash('modnameslist') as $modname) {
928 $modinfo = $this->converter->get_stash('modinfo_'.$modname);
929 foreach ($modinfo['instances'] as $modinstanceid => $modinstance) {
930 $cminfo = $this->converter->get_stash('cminfo_'.$modname, $modinstanceid);
931 $directory = 'activities/'.$modname.'_'.$cminfo['id'];
932
933 // write module.xml
934 $this->open_xml_writer($directory.'/module.xml');
935 $this->write_xml('module', $cminfo, array('/module/id', '/module/version'));
936 $this->close_xml_writer();
937
544e0d59
DM
938 // write grades.xml
939 $this->open_xml_writer($directory.'/grades.xml');
940 $this->xmlwriter->begin_tag('activity_gradebook');
cfab2099 941 $gradeitems = $this->converter->get_stash_or_default('gradebook_modgradeitem_'.$modname, $modinstanceid, array());
544e0d59
DM
942 if (!empty($gradeitems)) {
943 $this->xmlwriter->begin_tag('grade_items');
944 foreach ($gradeitems as $gradeitem) {
945 $this->write_xml('grade_item', $gradeitem, array('/grade_item/id'));
946 }
947 $this->xmlwriter->end_tag('grade_items');
948 }
d98100e4 949 $this->write_xml('grade_letters', array()); // no grade_letters in module context in Moodle 1.9
544e0d59
DM
950 $this->xmlwriter->end_tag('activity_gradebook');
951 $this->close_xml_writer();
952
953 // todo: write proper roles.xml, for now we just make sure the file is present
2fe7d14a 954 $this->make_sure_xml_exists($directory.'/roles.xml', 'roles');
2fe7d14a
DM
955 }
956 }
957 }
9b5f1ad5 958}
baa6f6a8
DM
959
960
ba66847b
DM
961/**
962 * Handles the conversion of the defined roles
963 */
964class moodle1_roles_definition_handler extends moodle1_xml_handler {
965
966 /**
967 * Where the roles are defined in the source moodle.xml
968 */
969 public function get_paths() {
970 return array(
971 new convert_path('roles', '/MOODLE_BACKUP/ROLES'),
972 new convert_path(
973 'roles_role', '/MOODLE_BACKUP/ROLES/ROLE',
974 array(
975 'newfields' => array(
976 'description' => '',
977 'sortorder' => 0,
978 'archetype' => ''
979 )
980 )
981 )
982 );
983 }
984
317f1c6f
DM
985 /**
986 * If there are any roles defined in moodle.xml, convert them to roles.xml
987 */
ba66847b 988 public function process_roles_role($data) {
317f1c6f
DM
989
990 if (!$this->has_xml_writer()) {
991 $this->open_xml_writer('roles.xml');
992 $this->xmlwriter->begin_tag('roles_definition');
993 }
ba66847b
DM
994 if (!isset($data['nameincourse'])) {
995 $data['nameincourse'] = null;
996 }
997 $this->write_xml('role', $data, array('role/id'));
998 }
999
317f1c6f
DM
1000 /**
1001 * Finishes writing roles.xml
1002 */
ba66847b 1003 public function on_roles_end() {
317f1c6f
DM
1004
1005 if (!$this->has_xml_writer()) {
1006 // no roles defined in moodle.xml so {link self::process_roles_role()}
1007 // was never executed
1008 $this->open_xml_writer('roles.xml');
1009 $this->write_xml('roles_definition', array());
1010
1011 } else {
1012 // some roles were dumped into the file, let us close their wrapper now
1013 $this->xmlwriter->end_tag('roles_definition');
1014 }
ba66847b
DM
1015 $this->close_xml_writer();
1016 }
1017}
1018
1019
c171d28e 1020/**
e19d0c10 1021 * Handles the conversion of the question bank included in the moodle.xml file
c171d28e 1022 */
e19d0c10
DM
1023class moodle1_question_bank_handler extends moodle1_xml_handler {
1024
1025 /** @var array the current question category being parsed */
1026 protected $currentcategory = null;
1027
1028 /** @var array of the raw data for the current category */
1029 protected $currentcategoryraw = null;
1030
1031 /** @var moodle1_file_manager instance used to convert question images */
1032 protected $fileman = null;
1033
1034 /** @var bool are the currentcategory data already written (this is a work around MDL-27693) */
1035 private $currentcategorywritten = false;
1036
1037 /** @var bool was the <questions> tag already written (work around MDL-27693) */
1038 private $questionswrapperwritten = false;
c171d28e 1039
23007e5d
DM
1040 /** @var array holds the instances of qtype specific conversion handlers */
1041 private $qtypehandlers = null;
1042
888b6ee5
JMV
1043 /**
1044 * Return the file manager instance used.
1045 *
1046 * @return moodle1_file_manager
1047 */
1048 public function get_file_manager() {
1049 return $this->fileman;
1050 }
1051
1052 /**
1053 * Returns the information about the question category context being currently parsed
1054 *
1055 * @return array with keys contextid, contextlevel and contextinstanceid
1056 */
1057 public function get_current_category_context() {
1058 return $this->currentcategory;
1059 }
1060
c171d28e 1061 /**
e19d0c10 1062 * Registers path that are not qtype-specific
c171d28e
DM
1063 */
1064 public function get_paths() {
e19d0c10
DM
1065
1066 $paths = array(
1067 new convert_path('question_categories', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES'),
1068 new convert_path(
1069 'question_category', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY',
1070 array(
1071 'newfields' => array(
1072 'infoformat' => 0
1073 )
1074 )),
1075 new convert_path('question_category_context', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/CONTEXT'),
1076 new convert_path('questions', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS'),
1077 // the question element must be grouped so we can re-dispatch it to the qtype handler as a whole
1078 new convert_path('question', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION', array(), true),
1079 );
1080
1081 // annotate all question subpaths required by the qtypes subplugins
1082 $subpaths = array();
1083 foreach ($this->get_qtype_handler('*') as $qtypehandler) {
1084 foreach ($qtypehandler->get_question_subpaths() as $subpath) {
1085 $subpaths[$subpath] = true;
1086 }
1087 }
1088 foreach (array_keys($subpaths) as $subpath) {
1089 $name = 'subquestion_'.strtolower(str_replace('/', '_', $subpath));
1090 $path = '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION/'.$subpath;
1091 $paths[] = new convert_path($name, $path);
1092 }
1093
1094 return $paths;
1095 }
1096
1097 /**
1098 * Starts writing questions.xml and prepares the file manager instance
1099 */
1100 public function on_question_categories_start() {
1101 $this->open_xml_writer('questions.xml');
1102 $this->xmlwriter->begin_tag('question_categories');
1103 if (is_null($this->fileman)) {
1104 $this->fileman = $this->converter->get_file_manager();
1105 }
1106 }
1107
1108 /**
1109 * Initializes the current category cache
1110 */
1111 public function on_question_category_start() {
1112 $this->currentcategory = array();
1113 $this->currentcategoryraw = array();
1114 $this->currentcategorywritten = false;
1115 $this->questionswrapperwritten = false;
c171d28e
DM
1116 }
1117
e19d0c10
DM
1118 /**
1119 * Populates the current question category data
1120 *
1121 * Bacuse of the known subpath-in-the-middle problem (CONTEXT in this case), this is actually
1122 * called twice for both halves of the data. We merge them here into the currentcategory array.
1123 */
1124 public function process_question_category($data, $raw) {
1125 $this->currentcategory = array_merge($this->currentcategory, $data);
1126 $this->currentcategoryraw = array_merge($this->currentcategoryraw, $raw);
1127 }
1128
1129 /**
1130 * Inject the context related information into the current category
1131 */
1132 public function process_question_category_context($data) {
e19d0c10
DM
1133
1134 switch ($data['level']) {
1135 case 'module':
1136 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_MODULE, $data['instance']);
1137 $this->currentcategory['contextlevel'] = CONTEXT_MODULE;
1138 $this->currentcategory['contextinstanceid'] = $data['instance'];
1139 break;
1140 case 'course':
23007e5d
DM
1141 $originalcourseinfo = $this->converter->get_stash('original_course_info');
1142 $originalcourseid = $originalcourseinfo['original_course_id'];
e19d0c10
DM
1143 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSE);
1144 $this->currentcategory['contextlevel'] = CONTEXT_COURSE;
1145 $this->currentcategory['contextinstanceid'] = $originalcourseid;
1146 break;
1147 case 'coursecategory':
1148 // this is a bit hacky. the source moodle.xml defines COURSECATEGORYLEVEL as a distance
1149 // of the course category (1 = parent category, 2 = grand-parent category etc). We pretend
1150 // that this level*10 is the id of that category and create an artifical contextid for it
1151 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSECAT, $data['coursecategorylevel'] * 10);
1152 $this->currentcategory['contextlevel'] = CONTEXT_COURSECAT;
1153 $this->currentcategory['contextinstanceid'] = $data['coursecategorylevel'] * 10;
1154 break;
1155 case 'system':
1156 $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_SYSTEM);
1157 $this->currentcategory['contextlevel'] = CONTEXT_SYSTEM;
1158 $this->currentcategory['contextinstanceid'] = 0;
1159 break;
1160 }
1161 }
1162
1163 /**
1164 * Writes the common <question> data and re-dispateches the whole grouped
1165 * <QUESTION> data to the qtype for appending its qtype specific data processing
1166 *
1167 * @param array $data
1168 * @param array $raw
1169 * @return array
1170 */
1171 public function process_question(array $data, array $raw) {
1172 global $CFG;
1173
1174 // firstly make sure that the category data and the <questions> wrapper are written
1175 // note that because of MDL-27693 we can't use {@link self::process_question_category()}
1176 // and {@link self::on_questions_start()} to do so
1177
1178 if (empty($this->currentcategorywritten)) {
1179 $this->xmlwriter->begin_tag('question_category', array('id' => $this->currentcategory['id']));
1180 foreach ($this->currentcategory as $name => $value) {
1181 if ($name === 'id') {
1182 continue;
1183 }
1184 $this->xmlwriter->full_tag($name, $value);
1185 }
1186 $this->currentcategorywritten = true;
1187 }
1188
1189 if (empty($this->questionswrapperwritten)) {
1190 $this->xmlwriter->begin_tag('questions');
1191 $this->questionswrapperwritten = true;
1192 }
1193
1194 $qtype = $data['qtype'];
1195
1196 // replay the upgrade step 2008050700 {@see question_fix_random_question_parents()}
1197 if ($qtype == 'random' and $data['parent'] <> $data['id']) {
1198 $data['parent'] = $data['id'];
1199 }
1200
1201 // replay the upgrade step 2010080900 and part of 2010080901
1202 $data['generalfeedbackformat'] = $data['questiontextformat'];
1203 $data['oldquestiontextformat'] = $data['questiontextformat'];
1204
1205 if ($CFG->texteditors !== 'textarea') {
1206 $data['questiontext'] = text_to_html($data['questiontext'], false, false, true);
1207 $data['questiontextformat'] = FORMAT_HTML;
1208 $data['generalfeedback'] = text_to_html($data['generalfeedback'], false, false, true);
1209 $data['generalfeedbackformat'] = FORMAT_HTML;
1210 }
1211
888b6ee5
JMV
1212 // Migrate files in questiontext.
1213 $this->fileman->contextid = $this->currentcategory['contextid'];
1214 $this->fileman->component = 'question';
1215 $this->fileman->filearea = 'questiontext';
1216 $this->fileman->itemid = $data['id'];
1217 $data['questiontext'] = moodle1_converter::migrate_referenced_files($data['questiontext'], $this->fileman);
1218
1219 // Migrate files in generalfeedback.
1220 $this->fileman->filearea = 'generalfeedback';
1221 $data['generalfeedback'] = moodle1_converter::migrate_referenced_files($data['generalfeedback'], $this->fileman);
1222
e19d0c10
DM
1223 // replay the upgrade step 2010080901 - updating question image
1224 if (!empty($data['image'])) {
f8311def 1225 if (textlib::substr(textlib::strtolower($data['image']), 0, 7) == 'http://') {
e19d0c10
DM
1226 // it is a link, appending to existing question text
1227 $data['questiontext'] .= ' <img src="' . $data['image'] . '" />';
1228
1229 } else {
1230 // it is a file in course_files
1231 $filename = basename($data['image']);
1232 $filepath = dirname($data['image']);
1233 if (empty($filepath) or $filepath == '.' or $filepath == '/') {
1234 $filepath = '/';
1235 } else {
1236 // append /
1237 $filepath = '/'.trim($filepath, './@#$ ').'/';
1238 }
1239
1240 if (file_exists($this->converter->get_tempdir_path().'/course_files'.$filepath.$filename)) {
1241 $this->fileman->contextid = $this->currentcategory['contextid'];
1242 $this->fileman->component = 'question';
1243 $this->fileman->filearea = 'questiontext';
1244 $this->fileman->itemid = $data['id'];
1245 $this->fileman->migrate_file('course_files'.$filepath.$filename, '/', $filename);
1246 // note this is slightly different from the upgrade code as we put the file into the
1247 // root folder here. this makes our life easier as we do not need to create all the
1248 // directories within the specified filearea/itemid
1249 $data['questiontext'] .= ' <img src="@@PLUGINFILE@@/' . $filename . '" />';
1250
1251 } else {
fe50f530 1252 $this->log('question file not found', backup::LOG_WARNING, array($data['id'], $filepath.$filename));
e19d0c10
DM
1253 }
1254 }
1255 }
1256 unset($data['image']);
1257
9107daa0
JMV
1258 // replay the upgrade step 2011060301 - Rename field defaultgrade on table question to defaultmark
1259 $data['defaultmark'] = $data['defaultgrade'];
1260
e19d0c10
DM
1261 // write the common question data
1262 $this->xmlwriter->begin_tag('question', array('id' => $data['id']));
1263 foreach (array(
1264 'parent', 'name', 'questiontext', 'questiontextformat',
9107daa0 1265 'generalfeedback', 'generalfeedbackformat', 'defaultmark',
e19d0c10
DM
1266 'penalty', 'qtype', 'length', 'stamp', 'version', 'hidden',
1267 'timecreated', 'timemodified', 'createdby', 'modifiedby'
1268 ) as $fieldname) {
1269 if (!array_key_exists($fieldname, $data)) {
1270 throw new moodle1_convert_exception('missing_common_question_field', $fieldname);
1271 }
1272 $this->xmlwriter->full_tag($fieldname, $data[$fieldname]);
1273 }
1274 // unless we know that the given qtype does not append any own structures,
1275 // give the handler a chance to do so now
c15649fb 1276 if (!in_array($qtype, array('description', 'random'))) {
e19d0c10
DM
1277 $handler = $this->get_qtype_handler($qtype);
1278 if ($handler === false) {
fe50f530 1279 $this->log('question type converter not found', backup::LOG_ERROR, $qtype);
e19d0c10
DM
1280
1281 } else {
1282 $this->xmlwriter->begin_tag('plugin_qtype_'.$qtype.'_question');
1283 $handler->use_xml_writer($this->xmlwriter);
1ff4b8de 1284 $handler->process_question($data, $raw);
e19d0c10
DM
1285 $this->xmlwriter->end_tag('plugin_qtype_'.$qtype.'_question');
1286 }
1287 }
1288
1289 $this->xmlwriter->end_tag('question');
e19d0c10
DM
1290 }
1291
1292 /**
1293 * Closes the questions wrapper
1294 */
1295 public function on_questions_end() {
1296 $this->xmlwriter->end_tag('questions');
1297 }
1298
1299 /**
1300 * Closes the question_category and annotates the category id
1301 * so that it can be dumped into course/inforef.xml
1302 */
1303 public function on_question_category_end() {
1304 // make sure that the category data were written by {@link self::process_question()}
1305 // if not, write it now. this may happen when the current category does not contain any
1306 // questions so the subpaths is missing completely
1307 if (empty($this->currentcategorywritten)) {
1308 $this->write_xml('question_category', $this->currentcategory, array('/question_category/id'));
1309 } else {
1310 $this->xmlwriter->end_tag('question_category');
1311 }
1312 $this->converter->set_stash('question_categories', $this->currentcategory, $this->currentcategory['id']);
1313 }
1314
1315 /**
1316 * Stops writing questions.xml
1317 */
1318 public function on_question_categories_end() {
1319 $this->xmlwriter->end_tag('question_categories');
1320 $this->close_xml_writer();
1321 }
1322
1323 /**
1324 * Provides access to the qtype handlers
1325 *
1326 * Returns either list of all qtype handler instances (if passed '*') or a particular handler
1327 * for the given qtype or false if the qtype is not supported.
1328 *
1329 * @throws moodle1_convert_exception
1330 * @param string $qtype the name of the question type or '*' for returning all
1331 * @return array|moodle1_qtype_handler|bool
1332 */
1333 protected function get_qtype_handler($qtype) {
e19d0c10 1334
23007e5d 1335 if (is_null($this->qtypehandlers)) {
fe50f530 1336 // initialize the list of qtype handler instances
23007e5d 1337 $this->qtypehandlers = array();
e19d0c10
DM
1338 foreach (get_plugin_list('qtype') as $qtypename => $qtypelocation) {
1339 $filename = $qtypelocation.'/backup/moodle1/lib.php';
1340 if (file_exists($filename)) {
1341 $classname = 'moodle1_qtype_'.$qtypename.'_handler';
1342 require_once($filename);
1343 if (!class_exists($classname)) {
1344 throw new moodle1_convert_exception('missing_handler_class', $classname);
1345 }
fe50f530 1346 $this->log('registering handler', backup::LOG_DEBUG, $classname, 2);
23007e5d 1347 $this->qtypehandlers[$qtypename] = new $classname($this, $qtypename);
e19d0c10
DM
1348 }
1349 }
1350 }
1351
1352 if ($qtype === '*') {
23007e5d 1353 return $this->qtypehandlers;
e19d0c10 1354
23007e5d
DM
1355 } else if (isset($this->qtypehandlers[$qtype])) {
1356 return $this->qtypehandlers[$qtype];
e19d0c10
DM
1357
1358 } else {
1359 return false;
1360 }
c171d28e
DM
1361 }
1362}
1363
1364
e29746c2
DM
1365/**
1366 * Handles the conversion of the scales included in the moodle.xml file
1367 */
1368class moodle1_scales_handler extends moodle1_handler {
1369
1370 /** @var moodle1_file_manager instance used to convert question images */
1371 protected $fileman = null;
1372
1373 /**
544e0d59 1374 * Registers paths
e29746c2
DM
1375 */
1376 public function get_paths() {
1377 return array(
1378 new convert_path('scales', '/MOODLE_BACKUP/COURSE/SCALES'),
1379 new convert_path(
1380 'scale', '/MOODLE_BACKUP/COURSE/SCALES/SCALE',
1381 array(
1382 'renamefields' => array(
1383 'scaletext' => 'scale',
1384 ),
1385 'addfields' => array(
1386 'descriptionformat' => 0,
1387 )
1388 )
1389 ),
1390 );
1391 }
1392
1393 /**
1394 * Prepare the file manager for the files embedded in the scale description field
1395 */
1396 public function on_scales_start() {
1397 $syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM);
1398 $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'scale');
1399 }
1400
1401 /**
1402 * This is executed every time we have one <SCALE> data available
1403 *
1404 * @param array $data
1405 * @param array $raw
1406 * @return array
1407 */
1408 public function process_scale(array $data, array $raw) {
1409 global $CFG;
1410
1411 // replay upgrade step 2009110400
1412 if ($CFG->texteditors !== 'textarea') {
1413 $data['description'] = text_to_html($data['description'], false, false, true);
1414 $data['descriptionformat'] = FORMAT_HTML;
1415 }
1416
1417 // convert course files embedded into the scale description field
1418 $this->fileman->itemid = $data['id'];
1419 $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman);
1420
1421 // stash the scale
1422 $this->converter->set_stash('scales', $data, $data['id']);
1423 }
1424}
1425
1426
ddefec8d
DM
1427/**
1428 * Handles the conversion of the outcomes
1429 */
1430class moodle1_outcomes_handler extends moodle1_xml_handler {
1431
1432 /** @var moodle1_file_manager instance used to convert images embedded into outcome descriptions */
1433 protected $fileman = null;
1434
1435 /**
1436 * Registers paths
1437 */
1438 public function get_paths() {
1439 return array(
1440 new convert_path('gradebook_grade_outcomes', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES'),
1441 new convert_path(
1442 'gradebook_grade_outcome', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES/GRADE_OUTCOME',
1443 array(
1444 'addfields' => array(
1445 'descriptionformat' => FORMAT_MOODLE,
1446 ),
1447 )
1448 ),
1449 );
1450 }
1451
1452 /**
1453 * Prepares the file manager and starts writing outcomes.xml
1454 */
1455 public function on_gradebook_grade_outcomes_start() {
1456
1457 $syscontextid = $this->converter->get_contextid(CONTEXT_SYSTEM);
1458 $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'outcome');
1459
1460 $this->open_xml_writer('outcomes.xml');
1461 $this->xmlwriter->begin_tag('outcomes_definition');
1462 }
1463
1464 /**
1465 * Processes GRADE_OUTCOME tags progressively
1466 */
1467 public function process_gradebook_grade_outcome(array $data, array $raw) {
1468 global $CFG;
1469
1470 // replay the upgrade step 2009110400
1471 if ($CFG->texteditors !== 'textarea') {
1472 $data['description'] = text_to_html($data['description'], false, false, true);
1473 $data['descriptionformat'] = FORMAT_HTML;
1474 }
1475
1476 // convert course files embedded into the outcome description field
1477 $this->fileman->itemid = $data['id'];
1478 $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman);
1479
1480 // write the outcome data
1481 $this->write_xml('outcome', $data, array('/outcome/id'));
1482
1483 return $data;
1484 }
1485
1486 /**
1487 * Closes outcomes.xml
1488 */
1489 public function on_gradebook_grade_outcomes_end() {
1490 $this->xmlwriter->end_tag('outcomes_definition');
1491 $this->close_xml_writer();
1492 }
1493}
1494
1495
544e0d59
DM
1496/**
1497 * Handles the conversion of the gradebook structures in the moodle.xml file
1498 */
1499class moodle1_gradebook_handler extends moodle1_xml_handler {
1500
cfab2099
DM
1501 /** @var array of (int)gradecategoryid => (int|null)parentcategoryid */
1502 protected $categoryparent = array();
1503
544e0d59
DM
1504 /**
1505 * Registers paths
1506 */
1507 public function get_paths() {
1508 return array(
cfab2099 1509 new convert_path('gradebook', '/MOODLE_BACKUP/COURSE/GRADEBOOK'),
d98100e4 1510 new convert_path('gradebook_grade_letter', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_LETTERS/GRADE_LETTER'),
cfab2099
DM
1511 new convert_path(
1512 'gradebook_grade_category', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_CATEGORIES/GRADE_CATEGORY',
1513 array(
1514 'addfields' => array(
1515 'hidden' => 0, // upgrade step 2010011200
1516 ),
1517 )
1518 ),
544e0d59 1519 new convert_path('gradebook_grade_item', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM'),
3343677e 1520 new convert_path('gradebook_grade_item_grades', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM/GRADE_GRADES'),
544e0d59
DM
1521 );
1522 }
1523
cfab2099
DM
1524 /**
1525 * Initializes the in-memory structures
1526 *
1527 * This should not be needed actually as the moodle.xml contains just one GRADEBOOK
1528 * element. But who knows - maybe someone will want to write a mass conversion
1529 * tool in the future (not me definitely ;-)
1530 */
1531 public function on_gradebook_start() {
1532 $this->categoryparent = array();
1533 }
1534
1535 /**
1536 * Processes one GRADE_LETTER data
1537 *
1538 * In Moodle 1.9, all grade_letters are from course context only. Therefore
1539 * we put them here.
1540 */
1541 public function process_gradebook_grade_letter(array $data, array $raw) {
1542 $this->converter->set_stash('gradebook_gradeletter', $data, $data['id']);
1543 }
1544
1545 /**
1546 * Processes one GRADE_CATEGORY data
1547 */
1548 public function process_gradebook_grade_category(array $data, array $raw) {
1549 $this->categoryparent[$data['id']] = $data['parent'];
1550 $this->converter->set_stash('gradebook_gradecategory', $data, $data['id']);
1551 }
1552
544e0d59
DM
1553 /**
1554 * Processes one GRADE_ITEM data
1555 */
1556 public function process_gradebook_grade_item(array $data, array $raw) {
1557
1558 // here we use get_nextid() to get a nondecreasing sequence
1559 $data['sortorder'] = $this->converter->get_nextid();
1560
1561 if ($data['itemtype'] === 'mod') {
1562 return $this->process_mod_grade_item($data, $raw);
cfab2099
DM
1563
1564 } else if (in_array($data['itemtype'], array('manual', 'course', 'category'))) {
1565 return $this->process_nonmod_grade_item($data, $raw);
1566
1567 } else {
1568 $this->log('unsupported grade_item type', backup::LOG_ERROR, $data['itemtype']);
544e0d59
DM
1569 }
1570 }
1571
1572 /**
1573 * Processes one GRADE_ITEM of the type 'mod'
1574 */
1575 protected function process_mod_grade_item(array $data, array $raw) {
1576
cfab2099 1577 $stashname = 'gradebook_modgradeitem_'.$data['itemmodule'];
544e0d59
DM
1578 $stashitemid = $data['iteminstance'];
1579 $gradeitems = $this->converter->get_stash_or_default($stashname, $stashitemid, array());
1580
1581 // typically there will be single item with itemnumber 0
1582 $gradeitems[$data['itemnumber']] = $data;
1583
1584 $this->converter->set_stash($stashname, $gradeitems, $stashitemid);
cfab2099
DM
1585
1586 return $data;
1587 }
1588
1589 /**
1590 * Processes one GRADE_ITEM of te type 'manual' or 'course' or 'category'
1591 */
1592 protected function process_nonmod_grade_item(array $data, array $raw) {
1593
1594 $stashname = 'gradebook_nonmodgradeitem';
1595 $stashitemid = $data['id'];
1596 $this->converter->set_stash($stashname, $data, $stashitemid);
1597
1598 return $data;
1599 }
1600
3343677e
DM
1601 /**
1602 * @todo
1603 */
1604 public function on_gradebook_grade_item_grades_start() {
1605 }
1606
cfab2099
DM
1607 /**
1608 * Writes the collected information into gradebook.xml
1609 */
1610 public function on_gradebook_end() {
1611
1612 $this->open_xml_writer('gradebook.xml');
1613 $this->xmlwriter->begin_tag('gradebook');
1614 $this->write_grade_categories();
1615 $this->write_grade_items();
1616 $this->write_grade_letters();
cfab2099
DM
1617 $this->xmlwriter->end_tag('gradebook');
1618 $this->close_xml_writer();
1619 }
1620
1621 /**
1622 * Writes grade_categories
1623 */
1624 protected function write_grade_categories() {
1625
1626 $this->xmlwriter->begin_tag('grade_categories');
1627 foreach ($this->converter->get_stash_itemids('gradebook_gradecategory') as $gradecategoryid) {
1628 $gradecategory = $this->converter->get_stash('gradebook_gradecategory', $gradecategoryid);
1629 $path = $this->calculate_category_path($gradecategoryid);
1630 $gradecategory['depth'] = count($path);
1631 $gradecategory['path'] = '/'.implode('/', $path).'/';
1632 $this->write_xml('grade_category', $gradecategory, array('/grade_category/id'));
1633 }
1634 $this->xmlwriter->end_tag('grade_categories');
1635 }
1636
1637 /**
1638 * Calculates the path to the grade_category
1639 *
1640 * Moodle 1.9 backup does not store the grade_category's depth and path. This method is used
1641 * to repopulate this information using the $this->categoryparent values.
1642 *
1643 * @param int $categoryid
1644 * @return array of ids including the categoryid
1645 */
1646 protected function calculate_category_path($categoryid) {
1647
1648 if (!array_key_exists($categoryid, $this->categoryparent)) {
1649 throw new moodle1_convert_exception('gradebook_unknown_categoryid', null, $categoryid);
1650 }
1651
1652 $path = array($categoryid);
1653 $parent = $this->categoryparent[$categoryid];
1654 while (!is_null($parent)) {
1655 array_unshift($path, $parent);
1656 $parent = $this->categoryparent[$parent];
1657 if (in_array($parent, $path)) {
1658 throw new moodle1_convert_exception('circular_reference_in_categories_tree');
1659 }
1660 }
1661
1662 return $path;
1663 }
1664
1665 /**
1666 * Writes grade_items
1667 */
1668 protected function write_grade_items() {
1669
1670 $this->xmlwriter->begin_tag('grade_items');
1671 foreach ($this->converter->get_stash_itemids('gradebook_nonmodgradeitem') as $gradeitemid) {
1672 $gradeitem = $this->converter->get_stash('gradebook_nonmodgradeitem', $gradeitemid);
1673 $this->write_xml('grade_item', $gradeitem, array('/grade_item/id'));
1674 }
1675 $this->xmlwriter->end_tag('grade_items');
1676 }
1677
1678 /**
1679 * Writes grade_letters
1680 */
1681 protected function write_grade_letters() {
d98100e4
DM
1682
1683 $this->xmlwriter->begin_tag('grade_letters');
1684 foreach ($this->converter->get_stash_itemids('gradebook_gradeletter') as $gradeletterid) {
1685 $gradeletter = $this->converter->get_stash('gradebook_gradeletter', $gradeletterid);
1686 $this->write_xml('grade_letter', $gradeletter, array('/grade_letter/id'));
1687 }
1688 $this->xmlwriter->end_tag('grade_letters');
cfab2099 1689 }
544e0d59
DM
1690}
1691
1692
baa6f6a8 1693/**
e19d0c10 1694 * Shared base class for activity modules, blocks and qtype handlers
baa6f6a8
DM
1695 */
1696abstract class moodle1_plugin_handler extends moodle1_xml_handler {
1697
1698 /** @var string */
1699 protected $plugintype;
1700
1701 /** @var string */
1702 protected $pluginname;
1703
1704 /**
1705 * @param moodle1_converter $converter the converter that requires us
4ddf9ac6
DM
1706 * @param string $plugintype
1707 * @param string $pluginname
baa6f6a8
DM
1708 */
1709 public function __construct(moodle1_converter $converter, $plugintype, $pluginname) {
1710
1711 parent::__construct($converter);
1712 $this->plugintype = $plugintype;
1713 $this->pluginname = $pluginname;
1714 }
66f79e50
DM
1715
1716 /**
1717 * Returns the normalized name of the plugin, eg mod_workshop
1718 *
1719 * @return string
1720 */
1721 public function get_component_name() {
1722 return $this->plugintype.'_'.$this->pluginname;
1723 }
baa6f6a8
DM
1724}
1725
1726
e19d0c10
DM
1727/**
1728 * Base class for all question type handlers
1729 */
1730abstract class moodle1_qtype_handler extends moodle1_plugin_handler {
1731
1732 /** @var moodle1_question_bank_handler */
1733 protected $qbankhandler;
1734
1735 /**
1736 * Returns the list of paths within one <QUESTION> that this qtype needs to have included
1737 * in the grouped question structure
1738 *
1739 * @return array of strings
1740 */
1741 public function get_question_subpaths() {
1742 return array();
1743 }
1744
1745 /**
1746 * Gives the qtype handler a chance to write converted data into questions.xml
1747 *
1748 * @param array $data grouped question data
1749 * @param array $raw grouped raw QUESTION data
e19d0c10
DM
1750 */
1751 public function process_question(array $data, array $raw) {
e19d0c10
DM
1752 }
1753
22b8486f
DM
1754 /**
1755 * Converts the answers and writes them into the questions.xml
1756 *
1757 * The structure "answers" is used by several qtypes. It contains data from {question_answers} table.
1758 *
1759 * @param array $answers as parsed by the grouped parser in moodle.xml
1760 * @param string $qtype containing the answers
1761 */
1762 protected function write_answers(array $answers, $qtype) {
1763
1764 $this->xmlwriter->begin_tag('answers');
1765 foreach ($answers as $elementname => $elements) {
1766 foreach ($elements as $element) {
1767 $answer = $this->convert_answer($element, $qtype);
888b6ee5
JMV
1768 // Migrate images in answertext.
1769 if ($answer['answerformat'] == FORMAT_HTML) {
1770 $answer['answertext'] = $this->migrate_files($answer['answertext'], 'question', 'answer', $answer['id']);
1771 }
1772 // Migrate images in feedback.
1773 if ($answer['feedbackformat'] == FORMAT_HTML) {
1774 $answer['feedback'] = $this->migrate_files($answer['feedback'], 'question', 'answerfeedback', $answer['id']);
1775 }
22b8486f
DM
1776 $this->write_xml('answer', $answer, array('/answer/id'));
1777 }
1778 }
1779 $this->xmlwriter->end_tag('answers');
1780 }
1781
888b6ee5
JMV
1782 /**
1783 * Migrate files belonging to one qtype plugin text field.
1784 *
1785 * @param array $text the html fragment containing references to files
1786 * @param string $component the component for restored files
1787 * @param string $filearea the file area for restored files
1788 * @param int $itemid the itemid for restored files
1789 *
1790 * @return string the text for this field, after files references have been processed
1791 */
1792 protected function migrate_files($text, $component, $filearea, $itemid) {
1793 $context = $this->qbankhandler->get_current_category_context();
1794 $fileman = $this->qbankhandler->get_file_manager();
1795 $fileman->contextid = $context['contextid'];
1796 $fileman->component = $component;
1797 $fileman->filearea = $filearea;
1798 $fileman->itemid = $itemid;
1799 $text = moodle1_converter::migrate_referenced_files($text, $fileman);
1800 return $text;
1801 }
1802
f75ad8d7
DM
1803 /**
1804 * Writes the grouped numerical_units structure
1805 *
1806 * @param array $numericalunits
1807 */
1808 protected function write_numerical_units(array $numericalunits) {
1809
1810 $this->xmlwriter->begin_tag('numerical_units');
1811 foreach ($numericalunits as $elementname => $elements) {
1812 foreach ($elements as $element) {
1813 $element['id'] = $this->converter->get_nextid();
1814 $this->write_xml('numerical_unit', $element, array('/numerical_unit/id'));
1815 }
1816 }
1817 $this->xmlwriter->end_tag('numerical_units');
1818 }
1819
1820 /**
1821 * Writes the numerical_options structure
1822 *
aaf88d2c
DM
1823 * @see get_default_numerical_options()
1824 * @param array $numericaloption
1825 */
1826 protected function write_numerical_options(array $numericaloption) {
1827
1828 $this->xmlwriter->begin_tag('numerical_options');
1829 if (!empty($numericaloption)) {
1830 $this->write_xml('numerical_option', $numericaloption, array('/numerical_option/id'));
1831 }
1832 $this->xmlwriter->end_tag('numerical_options');
1833 }
1834
1835 /**
1836 * Returns default numerical_option structure
1837 *
f75ad8d7
DM
1838 * This structure is not present in moodle.xml, we create a new artificial one here.
1839 *
aaf88d2c 1840 * @see write_numerical_options()
f75ad8d7 1841 * @param int $oldquestiontextformat
aaf88d2c 1842 * @return array
f75ad8d7 1843 */
de529cd2 1844 protected function get_default_numerical_options($oldquestiontextformat, $units) {
f75ad8d7
DM
1845 global $CFG;
1846
1847 // replay the upgrade step 2009100100 - new table
1848 $options = array(
aaf88d2c
DM
1849 'id' => $this->converter->get_nextid(),
1850 'instructions' => null,
1851 'instructionsformat' => 0,
1852 'showunits' => 0,
1853 'unitsleft' => 0,
1854 'unitgradingtype' => 0,
1855 'unitpenalty' => 0.1
f75ad8d7
DM
1856 );
1857
1858 // replay the upgrade step 2009100101
1859 if ($CFG->texteditors !== 'textarea' and $oldquestiontextformat == FORMAT_MOODLE) {
aaf88d2c 1860 $options['instructionsformat'] = FORMAT_HTML;
f75ad8d7 1861 } else {
aaf88d2c 1862 $options['instructionsformat'] = $oldquestiontextformat;
f75ad8d7
DM
1863 }
1864
de529cd2
TH
1865 // Set a good default, depending on whether there are any units defined.
1866 if (empty($units)) {
1867 $options['showunits'] = 3;
1868 }
1869
aaf88d2c 1870 return $options;
f75ad8d7
DM
1871 }
1872
c0e3796e
DM
1873 /**
1874 * Writes the dataset_definitions structure
1875 *
1876 * @param array $datasetdefinitions array of dataset_definition structures
1877 */
1878 protected function write_dataset_definitions(array $datasetdefinitions) {
1879
1880 $this->xmlwriter->begin_tag('dataset_definitions');
1881 foreach ($datasetdefinitions as $datasetdefinition) {
1882 $this->xmlwriter->begin_tag('dataset_definition', array('id' => $this->converter->get_nextid()));
1883 foreach (array('category', 'name', 'type', 'options', 'itemcount') as $element) {
1884 $this->xmlwriter->full_tag($element, $datasetdefinition[$element]);
1885 }
1886 $this->xmlwriter->begin_tag('dataset_items');
1887 if (!empty($datasetdefinition['dataset_items']['dataset_item'])) {
1888 foreach ($datasetdefinition['dataset_items']['dataset_item'] as $datasetitem) {
1889 $datasetitem['id'] = $this->converter->get_nextid();
1890 $this->write_xml('dataset_item', $datasetitem, array('/dataset_item/id'));
1891 }
1892 }
1893 $this->xmlwriter->end_tag('dataset_items');
1894 $this->xmlwriter->end_tag('dataset_definition');
1895 }
1896 $this->xmlwriter->end_tag('dataset_definitions');
1897 }
1898
e19d0c10
DM
1899 /// implementation details follow //////////////////////////////////////////
1900
1901 public function __construct(moodle1_question_bank_handler $qbankhandler, $qtype) {
1902
1903 parent::__construct($qbankhandler->get_converter(), 'qtype', $qtype);
1904 $this->qbankhandler = $qbankhandler;
1905 }
1906
1907 /**
1908 * @see self::get_question_subpaths()
1909 */
1910 final public function get_paths() {
1911 throw new moodle1_convert_exception('qtype_handler_get_paths');
1912 }
1913
1914 /**
1915 * Question type handlers cannot open the xml_writer
1916 */
caee6e6c 1917 final protected function open_xml_writer($filename) {
e19d0c10
DM
1918 throw new moodle1_convert_exception('opening_xml_writer_forbidden');
1919 }
1920
1921 /**
1922 * Question type handlers cannot close the xml_writer
1923 */
1924 final protected function close_xml_writer() {
1925 throw new moodle1_convert_exception('opening_xml_writer_forbidden');
1926 }
1927
1928 /**
1929 * Provides a xml_writer instance to this qtype converter
1930 *
1931 * @param xml_writer $xmlwriter
1932 */
1933 public function use_xml_writer(xml_writer $xmlwriter) {
1934 $this->xmlwriter = $xmlwriter;
1935 }
22b8486f
DM
1936
1937 /**
1938 * Converts <ANSWER> structure into the new <answer> one
1939 *
1940 * See question_backup_answers() in 1.9 and add_question_question_answers() in 2.0
1941 *
1942 * @param array $old the parsed answer array in moodle.xml
1943 * @param string $qtype the question type the answer is part of
1944 * @return array
1945 */
1946 private function convert_answer(array $old, $qtype) {
1947 global $CFG;
1948
1949 $new = array();
1950 $new['id'] = $old['id'];
1951 $new['answertext'] = $old['answer_text'];
1952 $new['answerformat'] = 0; // upgrade step 2010080900
1953 $new['fraction'] = $old['fraction'];
1954 $new['feedback'] = $old['feedback'];
1955 $new['feedbackformat'] = 0; // upgrade step 2010080900
1956
1957 // replay upgrade step 2010080901
1958 if ($qtype !== 'multichoice') {
1959 $new['answerformat'] = FORMAT_PLAIN;
1960 } else {
888b6ee5
JMV
1961 $new['answertext'] = text_to_html($new['answertext'], false, false, true);
1962 $new['answerformat'] = FORMAT_HTML;
22b8486f
DM
1963 }
1964
1965 if ($CFG->texteditors !== 'textarea') {
1966 if ($qtype == 'essay') {
1967 $new['feedback'] = text_to_html($new['feedback'], false, false, true);
1968 }
1969 $new['feedbackformat'] = FORMAT_HTML;
1970
1971 } else {
1972 $new['feedbackformat'] = FORMAT_MOODLE;
1973 }
1974
1975 return $new;
1976 }
e19d0c10
DM
1977}
1978
1979
baa6f6a8
DM
1980/**
1981 * Base class for activity module handlers
1982 */
1983abstract class moodle1_mod_handler extends moodle1_plugin_handler {
1984
1985 /**
8bcefb32 1986 * Returns the name of the module, eg. 'forum'
baa6f6a8 1987 *
8bcefb32
DM
1988 * @return string
1989 */
1990 public function get_modname() {
1991 return $this->pluginname;
1992 }
1993
1994 /**
1995 * Returns course module information for the given instance id
1996 *
1997 * The information for this instance id has been stashed by
baa6f6a8
DM
1998 * {@link moodle1_course_outline_handler::process_course_module()}
1999 *
2000 * @param int $instance the module instance id
8bcefb32 2001 * @param string $modname the module type, defaults to $this->pluginname
baa6f6a8
DM
2002 * @return int
2003 */
8bcefb32 2004 protected function get_cminfo($instance, $modname = null) {
baa6f6a8 2005
8bcefb32
DM
2006 if (is_null($modname)) {
2007 $modname = $this->pluginname;
2008 }
2009 return $this->converter->get_stash('cminfo_'.$modname, $instance);
baa6f6a8
DM
2010 }
2011}
2012
2013
2014/**
9b5f8318
DM
2015 * Base class for all modules that are successors of the 1.9 resource module
2016 */
2017abstract class moodle1_resource_successor_handler extends moodle1_mod_handler {
2018
2019 /**
2020 * Resource successors do not attach to paths themselves, they are called explicitely
2021 * by moodle1_mod_resource_handler
2022 *
2023 * @return array
2024 */
2025 final public function get_paths() {
2026 return array();
2027 }
2028
2029 /**
2030 * Converts /MOODLE_BACKUP/COURSE/MODULES/MOD/RESOURCE data
2031 *
2032 * Called by {@link moodle1_mod_resource_handler::process_resource()}
2033 *
2034 * @param array $data pre-cooked legacy resource data
2035 * @param array $raw raw legacy resource data
2036 */
caee6e6c 2037 public function process_legacy_resource(array $data, array $raw = null) {
9b5f8318
DM
2038 }
2039
2040 /**
2041 * Called when the parses reaches the end </MOD> resource tag
2042 *
2043 * @param array $data the data returned by {@link self::process_resource} or just pre-cooked
2044 */
9e3e8c80 2045 public function on_legacy_resource_end(array $data) {
9b5f8318
DM
2046 }
2047}
2048
2049/**
2050 * Base class for block handlers
baa6f6a8
DM
2051 */
2052abstract class moodle1_block_handler extends moodle1_plugin_handler {
2053
afaabdaf
PN
2054 public function get_paths() {
2055 $blockname = strtoupper($this->pluginname);
2056 return array(
2057 new convert_path('block', "/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/{$blockname}"),
2058 );
2059 }
2060
2061 public function process_block(array $data) {
2062 $newdata = $this->convert_common_block_data($data);
2063
2064 $this->write_block_xml($newdata, $data);
2065 $this->write_inforef_xml($newdata, $data);
2066 $this->write_roles_xml($newdata, $data);
2067
2068 return $data;
2069 }
2070
2071 protected function convert_common_block_data(array $olddata) {
2072 $newdata = array();
2073
2074 $newdata['blockname'] = $olddata['name'];
2075 $newdata['parentcontextid'] = $this->converter->get_contextid(CONTEXT_COURSE, 0);
2076 $newdata['showinsubcontexts'] = 0;
2077 $newdata['pagetypepattern'] = $olddata['pagetype'].='-*';
2078 $newdata['subpagepattern'] = null;
2079 $newdata['defaultregion'] = ($olddata['position']=='l')?'side-pre':'side-post';
2080 $newdata['defaultweight'] = $olddata['weight'];
2081 $newdata['configdata'] = $this->convert_configdata($olddata);
2082
2083 return $newdata;
2084 }
2085
2086 protected function convert_configdata(array $olddata) {
2087 return $olddata['configdata'];
2088 }
2089
2090 protected function write_block_xml($newdata, $data) {
2091 $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']);
2092
2093 $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/block.xml");
2094 $this->xmlwriter->begin_tag('block', array('id' => $data['id'], 'contextid' => $contextid));
2095
2096 foreach ($newdata as $field => $value) {
2097 $this->xmlwriter->full_tag($field, $value);
2098 }
2099
2100 $this->xmlwriter->begin_tag('block_positions');
2101 $this->xmlwriter->begin_tag('block_position', array('id' => 1));
2102 $this->xmlwriter->full_tag('contextid', $newdata['parentcontextid']);
2103 $this->xmlwriter->full_tag('pagetype', $data['pagetype']);
2104 $this->xmlwriter->full_tag('subpage', '');
2105 $this->xmlwriter->full_tag('visible', $data['visible']);
2106 $this->xmlwriter->full_tag('region', $newdata['defaultregion']);
2107 $this->xmlwriter->full_tag('weight', $newdata['defaultweight']);
2108 $this->xmlwriter->end_tag('block_position');
2109 $this->xmlwriter->end_tag('block_positions');
2110 $this->xmlwriter->end_tag('block');
2111 $this->close_xml_writer();
2112 }
2113
2114 protected function write_inforef_xml($newdata, $data) {
2115 $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml");
2116 $this->xmlwriter->begin_tag('inforef');
2117 // Subclasses may provide inforef contents if needed
2118 $this->xmlwriter->end_tag('inforef');
2119 $this->close_xml_writer();
2120 }
2121
2122 protected function write_roles_xml($newdata, $data) {
2123 // This is an empty shell, as the moodle1 converter doesn't handle user data.
2124 $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/roles.xml");
2125 $this->xmlwriter->begin_tag('roles');
2126 $this->xmlwriter->full_tag('role_overrides', '');
2127 $this->xmlwriter->full_tag('role_assignments', '');
2128 $this->xmlwriter->end_tag('roles');
2129 $this->close_xml_writer();
2130 }
baa6f6a8 2131}
4ddf9ac6
DM
2132
2133
afaabdaf
PN
2134/**
2135 * Base class for block generic handler
2136 */
2137class moodle1_block_generic_handler extends moodle1_block_handler {
2138
2139}
2140
4ddf9ac6
DM
2141/**
2142 * Base class for the activity modules' subplugins
2143 */
2144abstract class moodle1_submod_handler extends moodle1_plugin_handler {
2145
2146 /** @var moodle1_mod_handler */
2147 protected $parenthandler;
2148
2149 /**
2150 * @param moodle1_mod_handler $parenthandler the handler of a module we are subplugin of
2151 * @param string $subplugintype the type of the subplugin
2152 * @param string $subpluginname the name of the subplugin
2153 */
2154 public function __construct(moodle1_mod_handler $parenthandler, $subplugintype, $subpluginname) {
2155 $this->parenthandler = $parenthandler;
2156 parent::__construct($parenthandler->converter, $subplugintype, $subpluginname);
2157 }
2158
2159 /**
2160 * Activity module subplugins can't declare any paths to handle
2161 *
2162 * The paths must be registered by the parent module and then re-dispatched to the
2163 * relevant subplugins for eventual processing.
2164 *
2165 * @return array empty array
2166 */
2167 final public function get_paths() {
2168 return array();
2169 }
2170}