a2744c284c391daa34722505cbf06ac0ff857d86
[moodle.git] / backup / converter / moodle1 / handlerlib.php
1 <?php
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/>.
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  */
30 defined('MOODLE_INTERNAL') || die();
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');
36 /**
37  * Handlers factory class
38  */
39 abstract class moodle1_handlers_factory {
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) {
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         );
59         $handlers = array_merge($handlers, self::get_plugin_handlers('mod', $converter));
60         $handlers = array_merge($handlers, self::get_plugin_handlers('block', $converter));
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         }
69         return $handlers;
70     }
72     /// public API ends here ///////////////////////////////////////////////////
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;
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";
91             if (file_exists($handlerfile)) {
92                 require_once($handlerfile);
93             } elseif ($type == 'block') {
94                 $handlerclass = "moodle1_block_generic_handler";
95             } else {
96                 continue;
97             }
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     }
109 /**
110  * Base backup conversion handler
111  */
112 abstract class moodle1_handler implements loggable {
114     /** @var moodle1_converter */
115     protected $converter;
117     /**
118      * @param moodle1_converter $converter the converter that requires us
119      */
120     public function __construct(moodle1_converter $converter) {
121         $this->converter = $converter;
122     }
124     /**
125      * @return moodle1_converter the converter that required this handler
126      */
127     public function get_converter() {
128         return $this->converter;
129     }
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     }
146 /**
147  * Base backup conversion handler that generates an XML file
148  */
149 abstract class moodle1_xml_handler extends moodle1_handler {
151     /** @var null|string the name of file we are writing to */
152     protected $xmlfilename;
154     /** @var null|xml_writer */
155     protected $xmlwriter;
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) {
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         }
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);
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     }
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     }
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() {
205         if ($this->xmlwriter instanceof xml_writer) {
206             return true;
207         } else {
208             return false;
209         }
210     }
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 = '/') {
222         if (!$this->has_xml_writer()) {
223             throw new moodle1_convert_exception('write_xml_without_writer');
224         }
226         $mypath    = $parent . $element;
227         $myattribs = array();
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         }
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);
251         $this->xmlwriter->begin_tag($element, $myattribs);
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         }
262         $this->xmlwriter->end_tag($element);
263     }
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()) {
279         $existed = file_exists($this->converter->get_workdir_path().'/'.$filename);
281         if ($existed) {
282             return true;
283         }
285         if ($rootelement !== false) {
286             $this->open_xml_writer($filename);
287             $this->write_xml($rootelement, $content);
288             $this->close_xml_writer();
289         }
291         return false;
292     }
296 /**
297  * Process the root element of the backup file
298  */
299 class moodle1_root_handler extends moodle1_xml_handler {
301     public function get_paths() {
302         return array(new convert_path('root_element', '/MOODLE_BACKUP'));
303     }
305     /**
306      * Converts course_files and site_files
307      */
308     public function on_root_element_start() {
310         // convert course files
311         $fileshandler = new moodle1_files_handler($this->converter);
312         $fileshandler->process();
313     }
315     /**
316      * This is executed at the end of the moodle.xml parsing
317      */
318     public function on_root_element_end() {
319         global $CFG;
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');
325         ////////////////////////////////////////////////////////////////////////
326         // write moodle_backup.xml
327         ////////////////////////////////////////////////////////////////////////
328         $this->open_xml_writer('moodle_backup.xml');
330         $this->xmlwriter->begin_tag('moodle_backup');
331         $this->xmlwriter->begin_tag('information');
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));
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');
377         // moodle_backup/information/contents
378         $this->xmlwriter->begin_tag('contents');
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');
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');
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);
436         $this->xmlwriter->end_tag('contents');
438         // moodle_backup/information/settings
439         $this->xmlwriter->begin_tag('settings');
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);
464         // activity settings populated above
465         foreach ($activitysettings as $activitysetting) {
466             $this->write_xml('setting', $activitysetting);
467         }
468         unset($activitysettings);
470         // section settings populated above
471         foreach ($sectionsettings as $sectionsetting) {
472             $this->write_xml('setting', $sectionsetting);
473         }
474         unset($sectionsettings);
476         $this->xmlwriter->end_tag('settings');
478         $this->xmlwriter->end_tag('information');
479         $this->xmlwriter->end_tag('moodle_backup');
481         $this->close_xml_writer();
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');
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');
505         ////////////////////////////////////////////////////////////////////////
506         // write course/inforef.xml
507         ////////////////////////////////////////////////////////////////////////
508         $this->open_xml_writer('course/inforef.xml');
509         $this->xmlwriter->begin_tag('inforef');
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');
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');
535         $this->xmlwriter->end_tag('inforef');
536         $this->close_xml_writer();
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     }
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 {
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     }
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     }
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 {
594     /** @var array list of mod names included in info_details */
595     protected $modnames = array();
597     /** @var array the in-memory cache of the currently parsed info_details_mod element */
598     protected $currentmod;
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     }
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     }
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     }
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     }
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;
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         }
646         $this->currentmod = array();
647     }
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     }
658 /**
659  * Handles the conversion of /MOODLE_BACKUP/COURSE/HEADER paths
660  */
661 class moodle1_course_header_handler extends moodle1_xml_handler {
663     /** @var array we need to merge course information because it is dispatched twice */
664     protected $course = array();
666     /** @var array we need to merge course information because it is dispatched twice */
667     protected $courseraw = array();
669     /** @var array */
670     protected $category;
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     }
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     }
733     public function process_course_header_category($data) {
734         $this->category = $data;
735     }
737     public function on_course_header_end() {
739         $contextid = $this->converter->get_contextid(CONTEXT_COURSE);
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);
751         $this->course['contextid'] = $contextid;
752         $this->course['category'] = $this->category;
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());
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     }
767 /**
768  * Handles the conversion of course sections and course modules
769  */
770 class moodle1_course_outline_handler extends moodle1_xml_handler {
772     /** @var array ordered list of the course contents */
773     protected $coursecontents = array();
775     /** @var array current section data */
776     protected $currentsection;
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                         'availablefrom'             => 0,
803                         'availableuntil'            => 0,
804                         'showavailability'          => 0,
805                         'availability_info'         => array(),
806                         'visibleold'                => 1,
807                         'showdescription'           => 0,
808                     ),
809                     'dropfields' => array(
810                         'instance',
811                         'roles_overrides',
812                         'roles_assignments',
813                     ),
814                     'renamefields' => array(
815                         'type' => 'modulename',
816                     ),
817                 )
818             ),
819             new convert_path('course_modules', '/MOODLE_BACKUP/COURSE/MODULES'),
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'),
822         );
823     }
825     public function process_course_section($data) {
826         $this->currentsection = $data;
827     }
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      */
833     public function process_course_module($data, $raw) {
834         global $CFG;
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         }
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         );
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         }
858         // add the sectionid and sectionnumber
859         $data['sectionid']      = $this->currentsection['id'];
860         $data['sectionnumber']  = $this->currentsection['number'];
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)) {
869             $module = new stdClass();
870             include($versionfile);
871             $data['version'] = $module->version;
872         } else {
873             $data['version'] = null;
874         }
876         // stash the course module info in stashes like 'cminfo_forum' with
877         // itemid set to the instance id. this is needed so that module handlers
878         // can later obtain information about the course module and dump it into
879         // the module.xml file
880         $this->converter->set_stash('cminfo_'.$data['modulename'], $data, $raw['INSTANCE']);
881     }
883     /**
884      * Writes sections/section_xxx/section.xml file and stashes it, too
885      */
886     public function on_course_section_end() {
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);
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();
907         // stash the section info and write section.xml
908         $this->converter->set_stash('sectioninfo', $this->currentsection, $this->currentsection['id']);
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     }
915     /**
916      * Stashes the course contents
917      */
918     public function on_course_sections_end() {
919         $this->converter->set_stash('coursecontents', $this->coursecontents);
920     }
922     /**
923      * Writes the information collected by mod handlers
924      */
925     public function on_course_modules_end() {
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'];
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();
938                 // write grades.xml
939                 $this->open_xml_writer($directory.'/grades.xml');
940                 $this->xmlwriter->begin_tag('activity_gradebook');
941                 $gradeitems = $this->converter->get_stash_or_default('gradebook_modgradeitem_'.$modname, $modinstanceid, array());
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                 }
949                 $this->write_xml('grade_letters', array()); // no grade_letters in module context in Moodle 1.9
950                 $this->xmlwriter->end_tag('activity_gradebook');
951                 $this->close_xml_writer();
953                 // todo: write proper roles.xml, for now we just make sure the file is present
954                 $this->make_sure_xml_exists($directory.'/roles.xml', 'roles');
955             }
956         }
957     }
961 /**
962  * Handles the conversion of the defined roles
963  */
964 class moodle1_roles_definition_handler extends moodle1_xml_handler {
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     }
985     /**
986      * If there are any roles defined in moodle.xml, convert them to roles.xml
987      */
988     public function process_roles_role($data) {
990         if (!$this->has_xml_writer()) {
991             $this->open_xml_writer('roles.xml');
992             $this->xmlwriter->begin_tag('roles_definition');
993         }
994         if (!isset($data['nameincourse'])) {
995             $data['nameincourse'] = null;
996         }
997         $this->write_xml('role', $data, array('role/id'));
998     }
1000     /**
1001      * Finishes writing roles.xml
1002      */
1003     public function on_roles_end() {
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());
1011         } else {
1012             // some roles were dumped into the file, let us close their wrapper now
1013             $this->xmlwriter->end_tag('roles_definition');
1014         }
1015         $this->close_xml_writer();
1016     }
1020 /**
1021  * Handles the conversion of the question bank included in the moodle.xml file
1022  */
1023 class moodle1_question_bank_handler extends moodle1_xml_handler {
1025     /** @var array the current question category being parsed */
1026     protected $currentcategory = null;
1028     /** @var array of the raw data for the current category */
1029     protected $currentcategoryraw = null;
1031     /** @var moodle1_file_manager instance used to convert question images */
1032     protected $fileman = null;
1034     /** @var bool are the currentcategory data already written (this is a work around MDL-27693) */
1035     private $currentcategorywritten = false;
1037     /** @var bool was the <questions> tag already written (work around MDL-27693) */
1038     private $questionswrapperwritten = false;
1040     /** @var array holds the instances of qtype specific conversion handlers */
1041     private $qtypehandlers = null;
1043     /**
1044      * Registers path that are not qtype-specific
1045      */
1046     public function get_paths() {
1048         $paths = array(
1049             new convert_path('question_categories', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES'),
1050             new convert_path(
1051                 'question_category', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY',
1052                 array(
1053                     'newfields' => array(
1054                         'infoformat' => 0
1055                     )
1056                 )),
1057             new convert_path('question_category_context', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/CONTEXT'),
1058             new convert_path('questions', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS'),
1059             // the question element must be grouped so we can re-dispatch it to the qtype handler as a whole
1060             new convert_path('question', '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION', array(), true),
1061         );
1063         // annotate all question subpaths required by the qtypes subplugins
1064         $subpaths = array();
1065         foreach ($this->get_qtype_handler('*') as $qtypehandler) {
1066             foreach ($qtypehandler->get_question_subpaths() as $subpath) {
1067                 $subpaths[$subpath] = true;
1068             }
1069         }
1070         foreach (array_keys($subpaths) as $subpath) {
1071             $name = 'subquestion_'.strtolower(str_replace('/', '_', $subpath));
1072             $path = '/MOODLE_BACKUP/COURSE/QUESTION_CATEGORIES/QUESTION_CATEGORY/QUESTIONS/QUESTION/'.$subpath;
1073             $paths[] = new convert_path($name, $path);
1074         }
1076         return $paths;
1077     }
1079     /**
1080      * Starts writing questions.xml and prepares the file manager instance
1081      */
1082     public function on_question_categories_start() {
1083         $this->open_xml_writer('questions.xml');
1084         $this->xmlwriter->begin_tag('question_categories');
1085         if (is_null($this->fileman)) {
1086             $this->fileman = $this->converter->get_file_manager();
1087         }
1088     }
1090     /**
1091      * Initializes the current category cache
1092      */
1093     public function on_question_category_start() {
1094         $this->currentcategory         = array();
1095         $this->currentcategoryraw      = array();
1096         $this->currentcategorywritten  = false;
1097         $this->questionswrapperwritten = false;
1098     }
1100     /**
1101      * Populates the current question category data
1102      *
1103      * Bacuse of the known subpath-in-the-middle problem (CONTEXT in this case), this is actually
1104      * called twice for both halves of the data. We merge them here into the currentcategory array.
1105      */
1106     public function process_question_category($data, $raw) {
1107         $this->currentcategory    = array_merge($this->currentcategory, $data);
1108         $this->currentcategoryraw = array_merge($this->currentcategoryraw, $raw);
1109     }
1111     /**
1112      * Inject the context related information into the current category
1113      */
1114     public function process_question_category_context($data) {
1116         switch ($data['level']) {
1117         case 'module':
1118             $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_MODULE, $data['instance']);
1119             $this->currentcategory['contextlevel'] = CONTEXT_MODULE;
1120             $this->currentcategory['contextinstanceid'] = $data['instance'];
1121             break;
1122         case 'course':
1123             $originalcourseinfo = $this->converter->get_stash('original_course_info');
1124             $originalcourseid   = $originalcourseinfo['original_course_id'];
1125             $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSE);
1126             $this->currentcategory['contextlevel'] = CONTEXT_COURSE;
1127             $this->currentcategory['contextinstanceid'] = $originalcourseid;
1128             break;
1129         case 'coursecategory':
1130             // this is a bit hacky. the source moodle.xml defines COURSECATEGORYLEVEL as a distance
1131             // of the course category (1 = parent category, 2 = grand-parent category etc). We pretend
1132             // that this level*10 is the id of that category and create an artifical contextid for it
1133             $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_COURSECAT, $data['coursecategorylevel'] * 10);
1134             $this->currentcategory['contextlevel'] = CONTEXT_COURSECAT;
1135             $this->currentcategory['contextinstanceid'] = $data['coursecategorylevel'] * 10;
1136             break;
1137         case 'system':
1138             $this->currentcategory['contextid'] = $this->converter->get_contextid(CONTEXT_SYSTEM);
1139             $this->currentcategory['contextlevel'] = CONTEXT_SYSTEM;
1140             $this->currentcategory['contextinstanceid'] = 0;
1141             break;
1142         }
1143     }
1145     /**
1146      * Writes the common <question> data and re-dispateches the whole grouped
1147      * <QUESTION> data to the qtype for appending its qtype specific data processing
1148      *
1149      * @param array $data
1150      * @param array $raw
1151      * @return array
1152      */
1153     public function process_question(array $data, array $raw) {
1154         global $CFG;
1156         // firstly make sure that the category data and the <questions> wrapper are written
1157         // note that because of MDL-27693 we can't use {@link self::process_question_category()}
1158         // and {@link self::on_questions_start()} to do so
1160         if (empty($this->currentcategorywritten)) {
1161             $this->xmlwriter->begin_tag('question_category', array('id' => $this->currentcategory['id']));
1162             foreach ($this->currentcategory as $name => $value) {
1163                 if ($name === 'id') {
1164                     continue;
1165                 }
1166                 $this->xmlwriter->full_tag($name, $value);
1167             }
1168             $this->currentcategorywritten = true;
1169         }
1171         if (empty($this->questionswrapperwritten)) {
1172             $this->xmlwriter->begin_tag('questions');
1173             $this->questionswrapperwritten = true;
1174         }
1176         $qtype = $data['qtype'];
1178         // replay the upgrade step 2008050700 {@see question_fix_random_question_parents()}
1179         if ($qtype == 'random' and $data['parent'] <> $data['id']) {
1180             $data['parent'] = $data['id'];
1181         }
1183         // replay the upgrade step 2010080900 and part of 2010080901
1184         $data['generalfeedbackformat'] = $data['questiontextformat'];
1185         $data['oldquestiontextformat'] = $data['questiontextformat'];
1187         if ($CFG->texteditors !== 'textarea') {
1188             $data['questiontext'] = text_to_html($data['questiontext'], false, false, true);
1189             $data['questiontextformat'] = FORMAT_HTML;
1190             $data['generalfeedback'] = text_to_html($data['generalfeedback'], false, false, true);
1191             $data['generalfeedbackformat'] = FORMAT_HTML;
1192         }
1194         // replay the upgrade step 2010080901 - updating question image
1195         if (!empty($data['image'])) {
1196             if (textlib::substr(textlib::strtolower($data['image']), 0, 7) == 'http://') {
1197                 // it is a link, appending to existing question text
1198                 $data['questiontext'] .= ' <img src="' . $data['image'] . '" />';
1200             } else {
1201                 // it is a file in course_files
1202                 $filename = basename($data['image']);
1203                 $filepath = dirname($data['image']);
1204                 if (empty($filepath) or $filepath == '.' or $filepath == '/') {
1205                     $filepath = '/';
1206                 } else {
1207                     // append /
1208                     $filepath = '/'.trim($filepath, './@#$ ').'/';
1209                 }
1211                 if (file_exists($this->converter->get_tempdir_path().'/course_files'.$filepath.$filename)) {
1212                     $this->fileman->contextid = $this->currentcategory['contextid'];
1213                     $this->fileman->component = 'question';
1214                     $this->fileman->filearea  = 'questiontext';
1215                     $this->fileman->itemid    = $data['id'];
1216                     $this->fileman->migrate_file('course_files'.$filepath.$filename, '/', $filename);
1217                     // note this is slightly different from the upgrade code as we put the file into the
1218                     // root folder here. this makes our life easier as we do not need to create all the
1219                     // directories within the specified filearea/itemid
1220                     $data['questiontext'] .= ' <img src="@@PLUGINFILE@@/' . $filename . '" />';
1222                 } else {
1223                     $this->log('question file not found', backup::LOG_WARNING, array($data['id'], $filepath.$filename));
1224                 }
1225             }
1226         }
1227         unset($data['image']);
1229         // replay the upgrade step 2011060301 - Rename field defaultgrade on table question to defaultmark
1230         $data['defaultmark'] = $data['defaultgrade'];
1232         // write the common question data
1233         $this->xmlwriter->begin_tag('question', array('id' => $data['id']));
1234         foreach (array(
1235             'parent', 'name', 'questiontext', 'questiontextformat',
1236             'generalfeedback', 'generalfeedbackformat', 'defaultmark',
1237             'penalty', 'qtype', 'length', 'stamp', 'version', 'hidden',
1238             'timecreated', 'timemodified', 'createdby', 'modifiedby'
1239         ) as $fieldname) {
1240             if (!array_key_exists($fieldname, $data)) {
1241                 throw new moodle1_convert_exception('missing_common_question_field', $fieldname);
1242             }
1243             $this->xmlwriter->full_tag($fieldname, $data[$fieldname]);
1244         }
1245         // unless we know that the given qtype does not append any own structures,
1246         // give the handler a chance to do so now
1247         if (!in_array($qtype, array('description', 'random'))) {
1248             $handler = $this->get_qtype_handler($qtype);
1249             if ($handler === false) {
1250                 $this->log('question type converter not found', backup::LOG_ERROR, $qtype);
1252             } else {
1253                 $this->xmlwriter->begin_tag('plugin_qtype_'.$qtype.'_question');
1254                 $handler->use_xml_writer($this->xmlwriter);
1255                 $handler->process_question($data, $raw);
1256                 $this->xmlwriter->end_tag('plugin_qtype_'.$qtype.'_question');
1257             }
1258         }
1260         $this->xmlwriter->end_tag('question');
1261     }
1263     /**
1264      * Closes the questions wrapper
1265      */
1266     public function on_questions_end() {
1267         $this->xmlwriter->end_tag('questions');
1268     }
1270     /**
1271      * Closes the question_category and annotates the category id
1272      * so that it can be dumped into course/inforef.xml
1273      */
1274     public function on_question_category_end() {
1275         // make sure that the category data were written by {@link self::process_question()}
1276         // if not, write it now. this may happen when the current category does not contain any
1277         // questions so the subpaths is missing completely
1278         if (empty($this->currentcategorywritten)) {
1279             $this->write_xml('question_category', $this->currentcategory, array('/question_category/id'));
1280         } else {
1281             $this->xmlwriter->end_tag('question_category');
1282         }
1283         $this->converter->set_stash('question_categories', $this->currentcategory, $this->currentcategory['id']);
1284     }
1286     /**
1287      * Stops writing questions.xml
1288      */
1289     public function on_question_categories_end() {
1290         $this->xmlwriter->end_tag('question_categories');
1291         $this->close_xml_writer();
1292     }
1294     /**
1295      * Provides access to the qtype handlers
1296      *
1297      * Returns either list of all qtype handler instances (if passed '*') or a particular handler
1298      * for the given qtype or false if the qtype is not supported.
1299      *
1300      * @throws moodle1_convert_exception
1301      * @param string $qtype the name of the question type or '*' for returning all
1302      * @return array|moodle1_qtype_handler|bool
1303      */
1304     protected function get_qtype_handler($qtype) {
1306         if (is_null($this->qtypehandlers)) {
1307             // initialize the list of qtype handler instances
1308             $this->qtypehandlers = array();
1309             foreach (get_plugin_list('qtype') as $qtypename => $qtypelocation) {
1310                 $filename = $qtypelocation.'/backup/moodle1/lib.php';
1311                 if (file_exists($filename)) {
1312                     $classname = 'moodle1_qtype_'.$qtypename.'_handler';
1313                     require_once($filename);
1314                     if (!class_exists($classname)) {
1315                         throw new moodle1_convert_exception('missing_handler_class', $classname);
1316                     }
1317                     $this->log('registering handler', backup::LOG_DEBUG, $classname, 2);
1318                     $this->qtypehandlers[$qtypename] = new $classname($this, $qtypename);
1319                 }
1320             }
1321         }
1323         if ($qtype === '*') {
1324             return $this->qtypehandlers;
1326         } else if (isset($this->qtypehandlers[$qtype])) {
1327             return $this->qtypehandlers[$qtype];
1329         } else {
1330             return false;
1331         }
1332     }
1336 /**
1337  * Handles the conversion of the scales included in the moodle.xml file
1338  */
1339 class moodle1_scales_handler extends moodle1_handler {
1341     /** @var moodle1_file_manager instance used to convert question images */
1342     protected $fileman = null;
1344     /**
1345      * Registers paths
1346      */
1347     public function get_paths() {
1348         return array(
1349             new convert_path('scales', '/MOODLE_BACKUP/COURSE/SCALES'),
1350             new convert_path(
1351                 'scale', '/MOODLE_BACKUP/COURSE/SCALES/SCALE',
1352                 array(
1353                     'renamefields' => array(
1354                         'scaletext' => 'scale',
1355                     ),
1356                     'addfields' => array(
1357                         'descriptionformat' => 0,
1358                     )
1359                 )
1360             ),
1361         );
1362     }
1364     /**
1365      * Prepare the file manager for the files embedded in the scale description field
1366      */
1367     public function on_scales_start() {
1368         $syscontextid  = $this->converter->get_contextid(CONTEXT_SYSTEM);
1369         $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'scale');
1370     }
1372     /**
1373      * This is executed every time we have one <SCALE> data available
1374      *
1375      * @param array $data
1376      * @param array $raw
1377      * @return array
1378      */
1379     public function process_scale(array $data, array $raw) {
1380         global $CFG;
1382         // replay upgrade step 2009110400
1383         if ($CFG->texteditors !== 'textarea') {
1384             $data['description'] = text_to_html($data['description'], false, false, true);
1385             $data['descriptionformat'] = FORMAT_HTML;
1386         }
1388         // convert course files embedded into the scale description field
1389         $this->fileman->itemid = $data['id'];
1390         $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman);
1392         // stash the scale
1393         $this->converter->set_stash('scales', $data, $data['id']);
1394     }
1398 /**
1399  * Handles the conversion of the outcomes
1400  */
1401 class moodle1_outcomes_handler extends moodle1_xml_handler {
1403     /** @var moodle1_file_manager instance used to convert images embedded into outcome descriptions */
1404     protected $fileman = null;
1406     /**
1407      * Registers paths
1408      */
1409     public function get_paths() {
1410         return array(
1411             new convert_path('gradebook_grade_outcomes', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES'),
1412             new convert_path(
1413                 'gradebook_grade_outcome', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_OUTCOMES/GRADE_OUTCOME',
1414                 array(
1415                     'addfields' => array(
1416                         'descriptionformat' => FORMAT_MOODLE,
1417                     ),
1418                 )
1419             ),
1420         );
1421     }
1423     /**
1424      * Prepares the file manager and starts writing outcomes.xml
1425      */
1426     public function on_gradebook_grade_outcomes_start() {
1428         $syscontextid  = $this->converter->get_contextid(CONTEXT_SYSTEM);
1429         $this->fileman = $this->converter->get_file_manager($syscontextid, 'grade', 'outcome');
1431         $this->open_xml_writer('outcomes.xml');
1432         $this->xmlwriter->begin_tag('outcomes_definition');
1433     }
1435     /**
1436      * Processes GRADE_OUTCOME tags progressively
1437      */
1438     public function process_gradebook_grade_outcome(array $data, array $raw) {
1439         global $CFG;
1441         // replay the upgrade step 2009110400
1442         if ($CFG->texteditors !== 'textarea') {
1443             $data['description']       = text_to_html($data['description'], false, false, true);
1444             $data['descriptionformat'] = FORMAT_HTML;
1445         }
1447         // convert course files embedded into the outcome description field
1448         $this->fileman->itemid = $data['id'];
1449         $data['description'] = moodle1_converter::migrate_referenced_files($data['description'], $this->fileman);
1451         // write the outcome data
1452         $this->write_xml('outcome', $data, array('/outcome/id'));
1454         return $data;
1455     }
1457     /**
1458      * Closes outcomes.xml
1459      */
1460     public function on_gradebook_grade_outcomes_end() {
1461         $this->xmlwriter->end_tag('outcomes_definition');
1462         $this->close_xml_writer();
1463     }
1467 /**
1468  * Handles the conversion of the gradebook structures in the moodle.xml file
1469  */
1470 class moodle1_gradebook_handler extends moodle1_xml_handler {
1472     /** @var array of (int)gradecategoryid => (int|null)parentcategoryid */
1473     protected $categoryparent = array();
1475     /**
1476      * Registers paths
1477      */
1478     public function get_paths() {
1479         return array(
1480             new convert_path('gradebook', '/MOODLE_BACKUP/COURSE/GRADEBOOK'),
1481             new convert_path('gradebook_grade_letter', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_LETTERS/GRADE_LETTER'),
1482             new convert_path(
1483                 'gradebook_grade_category', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_CATEGORIES/GRADE_CATEGORY',
1484                 array(
1485                     'addfields' => array(
1486                         'hidden' => 0,  // upgrade step 2010011200
1487                     ),
1488                 )
1489             ),
1490             new convert_path('gradebook_grade_item', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM'),
1491             new convert_path('gradebook_grade_item_grades', '/MOODLE_BACKUP/COURSE/GRADEBOOK/GRADE_ITEMS/GRADE_ITEM/GRADE_GRADES'),
1492         );
1493     }
1495     /**
1496      * Initializes the in-memory structures
1497      *
1498      * This should not be needed actually as the moodle.xml contains just one GRADEBOOK
1499      * element. But who knows - maybe someone will want to write a mass conversion
1500      * tool in the future (not me definitely ;-)
1501      */
1502     public function on_gradebook_start() {
1503         $this->categoryparent = array();
1504     }
1506     /**
1507      * Processes one GRADE_LETTER data
1508      *
1509      * In Moodle 1.9, all grade_letters are from course context only. Therefore
1510      * we put them here.
1511      */
1512     public function process_gradebook_grade_letter(array $data, array $raw) {
1513         $this->converter->set_stash('gradebook_gradeletter', $data, $data['id']);
1514     }
1516     /**
1517      * Processes one GRADE_CATEGORY data
1518      */
1519     public function process_gradebook_grade_category(array $data, array $raw) {
1520         $this->categoryparent[$data['id']] = $data['parent'];
1521         $this->converter->set_stash('gradebook_gradecategory', $data, $data['id']);
1522     }
1524     /**
1525      * Processes one GRADE_ITEM data
1526      */
1527     public function process_gradebook_grade_item(array $data, array $raw) {
1529         // here we use get_nextid() to get a nondecreasing sequence
1530         $data['sortorder'] = $this->converter->get_nextid();
1532         if ($data['itemtype'] === 'mod') {
1533             return $this->process_mod_grade_item($data, $raw);
1535         } else if (in_array($data['itemtype'], array('manual', 'course', 'category'))) {
1536             return $this->process_nonmod_grade_item($data, $raw);
1538         } else {
1539             $this->log('unsupported grade_item type', backup::LOG_ERROR, $data['itemtype']);
1540         }
1541     }
1543     /**
1544      * Processes one GRADE_ITEM of the type 'mod'
1545      */
1546     protected function process_mod_grade_item(array $data, array $raw) {
1548         $stashname   = 'gradebook_modgradeitem_'.$data['itemmodule'];
1549         $stashitemid = $data['iteminstance'];
1550         $gradeitems  = $this->converter->get_stash_or_default($stashname, $stashitemid, array());
1552         // typically there will be single item with itemnumber 0
1553         $gradeitems[$data['itemnumber']] = $data;
1555         $this->converter->set_stash($stashname, $gradeitems, $stashitemid);
1557         return $data;
1558     }
1560     /**
1561      * Processes one GRADE_ITEM of te type 'manual' or 'course' or 'category'
1562      */
1563     protected function process_nonmod_grade_item(array $data, array $raw) {
1565         $stashname   = 'gradebook_nonmodgradeitem';
1566         $stashitemid = $data['id'];
1567         $this->converter->set_stash($stashname, $data, $stashitemid);
1569         return $data;
1570     }
1572     /**
1573      * @todo
1574      */
1575     public function on_gradebook_grade_item_grades_start() {
1576     }
1578     /**
1579      * Writes the collected information into gradebook.xml
1580      */
1581     public function on_gradebook_end() {
1583         $this->open_xml_writer('gradebook.xml');
1584         $this->xmlwriter->begin_tag('gradebook');
1585         $this->write_grade_categories();
1586         $this->write_grade_items();
1587         $this->write_grade_letters();
1588         $this->xmlwriter->end_tag('gradebook');
1589         $this->close_xml_writer();
1590     }
1592     /**
1593      * Writes grade_categories
1594      */
1595     protected function write_grade_categories() {
1597         $this->xmlwriter->begin_tag('grade_categories');
1598         foreach ($this->converter->get_stash_itemids('gradebook_gradecategory') as $gradecategoryid) {
1599             $gradecategory = $this->converter->get_stash('gradebook_gradecategory', $gradecategoryid);
1600             $path = $this->calculate_category_path($gradecategoryid);
1601             $gradecategory['depth'] = count($path);
1602             $gradecategory['path']  = '/'.implode('/', $path).'/';
1603             $this->write_xml('grade_category', $gradecategory, array('/grade_category/id'));
1604         }
1605         $this->xmlwriter->end_tag('grade_categories');
1606     }
1608     /**
1609      * Calculates the path to the grade_category
1610      *
1611      * Moodle 1.9 backup does not store the grade_category's depth and path. This method is used
1612      * to repopulate this information using the $this->categoryparent values.
1613      *
1614      * @param int $categoryid
1615      * @return array of ids including the categoryid
1616      */
1617     protected function calculate_category_path($categoryid) {
1619         if (!array_key_exists($categoryid, $this->categoryparent)) {
1620             throw new moodle1_convert_exception('gradebook_unknown_categoryid', null, $categoryid);
1621         }
1623         $path = array($categoryid);
1624         $parent = $this->categoryparent[$categoryid];
1625         while (!is_null($parent)) {
1626             array_unshift($path, $parent);
1627             $parent = $this->categoryparent[$parent];
1628             if (in_array($parent, $path)) {
1629                 throw new moodle1_convert_exception('circular_reference_in_categories_tree');
1630             }
1631         }
1633         return $path;
1634     }
1636     /**
1637      * Writes grade_items
1638      */
1639     protected function write_grade_items() {
1641         $this->xmlwriter->begin_tag('grade_items');
1642         foreach ($this->converter->get_stash_itemids('gradebook_nonmodgradeitem') as $gradeitemid) {
1643             $gradeitem = $this->converter->get_stash('gradebook_nonmodgradeitem', $gradeitemid);
1644             $this->write_xml('grade_item', $gradeitem, array('/grade_item/id'));
1645         }
1646         $this->xmlwriter->end_tag('grade_items');
1647     }
1649     /**
1650      * Writes grade_letters
1651      */
1652     protected function write_grade_letters() {
1654         $this->xmlwriter->begin_tag('grade_letters');
1655         foreach ($this->converter->get_stash_itemids('gradebook_gradeletter') as $gradeletterid) {
1656             $gradeletter = $this->converter->get_stash('gradebook_gradeletter', $gradeletterid);
1657             $this->write_xml('grade_letter', $gradeletter, array('/grade_letter/id'));
1658         }
1659         $this->xmlwriter->end_tag('grade_letters');
1660     }
1664 /**
1665  * Shared base class for activity modules, blocks and qtype handlers
1666  */
1667 abstract class moodle1_plugin_handler extends moodle1_xml_handler {
1669     /** @var string */
1670     protected $plugintype;
1672     /** @var string */
1673     protected $pluginname;
1675     /**
1676      * @param moodle1_converter $converter the converter that requires us
1677      * @param string $plugintype
1678      * @param string $pluginname
1679      */
1680     public function __construct(moodle1_converter $converter, $plugintype, $pluginname) {
1682         parent::__construct($converter);
1683         $this->plugintype = $plugintype;
1684         $this->pluginname = $pluginname;
1685     }
1687     /**
1688      * Returns the normalized name of the plugin, eg mod_workshop
1689      *
1690      * @return string
1691      */
1692     public function get_component_name() {
1693         return $this->plugintype.'_'.$this->pluginname;
1694     }
1698 /**
1699  * Base class for all question type handlers
1700  */
1701 abstract class moodle1_qtype_handler extends moodle1_plugin_handler {
1703     /** @var moodle1_question_bank_handler */
1704     protected $qbankhandler;
1706     /**
1707      * Returns the list of paths within one <QUESTION> that this qtype needs to have included
1708      * in the grouped question structure
1709      *
1710      * @return array of strings
1711      */
1712     public function get_question_subpaths() {
1713         return array();
1714     }
1716     /**
1717      * Gives the qtype handler a chance to write converted data into questions.xml
1718      *
1719      * @param array $data grouped question data
1720      * @param array $raw grouped raw QUESTION data
1721      */
1722     public function process_question(array $data, array $raw) {
1723     }
1725     /**
1726      * Converts the answers and writes them into the questions.xml
1727      *
1728      * The structure "answers" is used by several qtypes. It contains data from {question_answers} table.
1729      *
1730      * @param array $answers as parsed by the grouped parser in moodle.xml
1731      * @param string $qtype containing the answers
1732      */
1733     protected function write_answers(array $answers, $qtype) {
1735         $this->xmlwriter->begin_tag('answers');
1736         foreach ($answers as $elementname => $elements) {
1737             foreach ($elements as $element) {
1738                 $answer = $this->convert_answer($element, $qtype);
1739                 $this->write_xml('answer', $answer, array('/answer/id'));
1740             }
1741         }
1742         $this->xmlwriter->end_tag('answers');
1743     }
1745     /**
1746      * Writes the grouped numerical_units structure
1747      *
1748      * @param array $numericalunits
1749      */
1750     protected function write_numerical_units(array $numericalunits) {
1752         $this->xmlwriter->begin_tag('numerical_units');
1753         foreach ($numericalunits as $elementname => $elements) {
1754             foreach ($elements as $element) {
1755                 $element['id'] = $this->converter->get_nextid();
1756                 $this->write_xml('numerical_unit', $element, array('/numerical_unit/id'));
1757             }
1758         }
1759         $this->xmlwriter->end_tag('numerical_units');
1760     }
1762     /**
1763      * Writes the numerical_options structure
1764      *
1765      * @see get_default_numerical_options()
1766      * @param array $numericaloption
1767      */
1768     protected function write_numerical_options(array $numericaloption) {
1770         $this->xmlwriter->begin_tag('numerical_options');
1771         if (!empty($numericaloption)) {
1772             $this->write_xml('numerical_option', $numericaloption, array('/numerical_option/id'));
1773         }
1774         $this->xmlwriter->end_tag('numerical_options');
1775     }
1777     /**
1778      * Returns default numerical_option structure
1779      *
1780      * This structure is not present in moodle.xml, we create a new artificial one here.
1781      *
1782      * @see write_numerical_options()
1783      * @param int $oldquestiontextformat
1784      * @return array
1785      */
1786     protected function get_default_numerical_options($oldquestiontextformat, $units) {
1787         global $CFG;
1789         // replay the upgrade step 2009100100 - new table
1790         $options = array(
1791             'id'                 => $this->converter->get_nextid(),
1792             'instructions'       => null,
1793             'instructionsformat' => 0,
1794             'showunits'          => 0,
1795             'unitsleft'          => 0,
1796             'unitgradingtype'    => 0,
1797             'unitpenalty'        => 0.1
1798         );
1800         // replay the upgrade step 2009100101
1801         if ($CFG->texteditors !== 'textarea' and $oldquestiontextformat == FORMAT_MOODLE) {
1802             $options['instructionsformat'] = FORMAT_HTML;
1803         } else {
1804             $options['instructionsformat'] = $oldquestiontextformat;
1805         }
1807         // Set a good default, depending on whether there are any units defined.
1808         if (empty($units)) {
1809             $options['showunits'] = 3;
1810         }
1812         return $options;
1813     }
1815     /**
1816      * Writes the dataset_definitions structure
1817      *
1818      * @param array $datasetdefinitions array of dataset_definition structures
1819      */
1820     protected function write_dataset_definitions(array $datasetdefinitions) {
1822         $this->xmlwriter->begin_tag('dataset_definitions');
1823         foreach ($datasetdefinitions as $datasetdefinition) {
1824             $this->xmlwriter->begin_tag('dataset_definition', array('id' => $this->converter->get_nextid()));
1825             foreach (array('category', 'name', 'type', 'options', 'itemcount') as $element) {
1826                 $this->xmlwriter->full_tag($element, $datasetdefinition[$element]);
1827             }
1828             $this->xmlwriter->begin_tag('dataset_items');
1829             if (!empty($datasetdefinition['dataset_items']['dataset_item'])) {
1830                 foreach ($datasetdefinition['dataset_items']['dataset_item'] as $datasetitem) {
1831                     $datasetitem['id'] = $this->converter->get_nextid();
1832                     $this->write_xml('dataset_item', $datasetitem, array('/dataset_item/id'));
1833                 }
1834             }
1835             $this->xmlwriter->end_tag('dataset_items');
1836             $this->xmlwriter->end_tag('dataset_definition');
1837         }
1838         $this->xmlwriter->end_tag('dataset_definitions');
1839     }
1841     /// implementation details follow //////////////////////////////////////////
1843     public function __construct(moodle1_question_bank_handler $qbankhandler, $qtype) {
1845         parent::__construct($qbankhandler->get_converter(), 'qtype', $qtype);
1846         $this->qbankhandler = $qbankhandler;
1847     }
1849     /**
1850      * @see self::get_question_subpaths()
1851      */
1852     final public function get_paths() {
1853         throw new moodle1_convert_exception('qtype_handler_get_paths');
1854     }
1856     /**
1857      * Question type handlers cannot open the xml_writer
1858      */
1859     final protected function open_xml_writer($filename) {
1860         throw new moodle1_convert_exception('opening_xml_writer_forbidden');
1861     }
1863     /**
1864      * Question type handlers cannot close the xml_writer
1865      */
1866     final protected function close_xml_writer() {
1867         throw new moodle1_convert_exception('opening_xml_writer_forbidden');
1868     }
1870     /**
1871      * Provides a xml_writer instance to this qtype converter
1872      *
1873      * @param xml_writer $xmlwriter
1874      */
1875     public function use_xml_writer(xml_writer $xmlwriter) {
1876         $this->xmlwriter = $xmlwriter;
1877     }
1879     /**
1880      * Converts <ANSWER> structure into the new <answer> one
1881      *
1882      * See question_backup_answers() in 1.9 and add_question_question_answers() in 2.0
1883      *
1884      * @param array $old the parsed answer array in moodle.xml
1885      * @param string $qtype the question type the answer is part of
1886      * @return array
1887      */
1888     private function convert_answer(array $old, $qtype) {
1889         global $CFG;
1891         $new                    = array();
1892         $new['id']              = $old['id'];
1893         $new['answertext']      = $old['answer_text'];
1894         $new['answerformat']    = 0;   // upgrade step 2010080900
1895         $new['fraction']        = $old['fraction'];
1896         $new['feedback']        = $old['feedback'];
1897         $new['feedbackformat']  = 0;   // upgrade step 2010080900
1899         // replay upgrade step 2010080901
1900         if ($qtype !== 'multichoice') {
1901             $new['answerformat'] = FORMAT_PLAIN;
1902         } else {
1903             $new['answerformat'] = FORMAT_MOODLE;
1904         }
1906         if ($CFG->texteditors !== 'textarea') {
1907             if ($qtype == 'essay') {
1908                 $new['feedback'] = text_to_html($new['feedback'], false, false, true);
1909             }
1910             $new['feedbackformat'] = FORMAT_HTML;
1912         } else {
1913             $new['feedbackformat'] = FORMAT_MOODLE;
1914         }
1916         return $new;
1917     }
1921 /**
1922  * Base class for activity module handlers
1923  */
1924 abstract class moodle1_mod_handler extends moodle1_plugin_handler {
1926     /**
1927      * Returns the name of the module, eg. 'forum'
1928      *
1929      * @return string
1930      */
1931     public function get_modname() {
1932         return $this->pluginname;
1933     }
1935     /**
1936      * Returns course module information for the given instance id
1937      *
1938      * The information for this instance id has been stashed by
1939      * {@link moodle1_course_outline_handler::process_course_module()}
1940      *
1941      * @param int $instance the module instance id
1942      * @param string $modname the module type, defaults to $this->pluginname
1943      * @return int
1944      */
1945     protected function get_cminfo($instance, $modname = null) {
1947         if (is_null($modname)) {
1948             $modname = $this->pluginname;
1949         }
1950         return $this->converter->get_stash('cminfo_'.$modname, $instance);
1951     }
1955 /**
1956  * Base class for all modules that are successors of the 1.9 resource module
1957  */
1958 abstract class moodle1_resource_successor_handler extends moodle1_mod_handler {
1960     /**
1961      * Resource successors do not attach to paths themselves, they are called explicitely
1962      * by moodle1_mod_resource_handler
1963      *
1964      * @return array
1965      */
1966     final public function get_paths() {
1967         return array();
1968     }
1970     /**
1971      * Converts /MOODLE_BACKUP/COURSE/MODULES/MOD/RESOURCE data
1972      *
1973      * Called by {@link moodle1_mod_resource_handler::process_resource()}
1974      *
1975      * @param array $data pre-cooked legacy resource data
1976      * @param array $raw raw legacy resource data
1977      */
1978     public function process_legacy_resource(array $data, array $raw = null) {
1979     }
1981     /**
1982      * Called when the parses reaches the end </MOD> resource tag
1983      *
1984      * @param array $data the data returned by {@link self::process_resource} or just pre-cooked
1985      */
1986     public function on_legacy_resource_end(array $data) {
1987     }
1990 /**
1991  * Base class for block handlers
1992  */
1993 abstract class moodle1_block_handler extends moodle1_plugin_handler {
1995     public function get_paths() {
1996         $blockname = strtoupper($this->pluginname);
1997         return array(
1998             new convert_path('block', "/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/{$blockname}"),
1999         );
2000     }
2002     public function process_block(array $data) {
2003         $newdata = $this->convert_common_block_data($data);
2005         $this->write_block_xml($newdata, $data);
2006         $this->write_inforef_xml($newdata, $data);
2007         $this->write_roles_xml($newdata, $data);
2009         return $data;
2010     }
2012     protected function convert_common_block_data(array $olddata) {
2013         $newdata = array();
2015         $newdata['blockname'] = $olddata['name'];
2016         $newdata['parentcontextid'] = $this->converter->get_contextid(CONTEXT_COURSE, 0);
2017         $newdata['showinsubcontexts'] = 0;
2018         $newdata['pagetypepattern'] = $olddata['pagetype'].='-*';
2019         $newdata['subpagepattern'] = null;
2020         $newdata['defaultregion'] = ($olddata['position']=='l')?'side-pre':'side-post';
2021         $newdata['defaultweight'] = $olddata['weight'];
2022         $newdata['configdata'] = $this->convert_configdata($olddata);
2024         return $newdata;
2025     }
2027     protected function convert_configdata(array $olddata) {
2028         return $olddata['configdata'];
2029     }
2031     protected function write_block_xml($newdata, $data) {
2032         $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']);
2034         $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/block.xml");
2035         $this->xmlwriter->begin_tag('block', array('id' => $data['id'], 'contextid' => $contextid));
2037         foreach ($newdata as $field => $value) {
2038             $this->xmlwriter->full_tag($field, $value);
2039         }
2041         $this->xmlwriter->begin_tag('block_positions');
2042         $this->xmlwriter->begin_tag('block_position', array('id' => 1));
2043         $this->xmlwriter->full_tag('contextid', $newdata['parentcontextid']);
2044         $this->xmlwriter->full_tag('pagetype', $data['pagetype']);
2045         $this->xmlwriter->full_tag('subpage', '');
2046         $this->xmlwriter->full_tag('visible', $data['visible']);
2047         $this->xmlwriter->full_tag('region', $newdata['defaultregion']);
2048         $this->xmlwriter->full_tag('weight', $newdata['defaultweight']);
2049         $this->xmlwriter->end_tag('block_position');
2050         $this->xmlwriter->end_tag('block_positions');
2051         $this->xmlwriter->end_tag('block');
2052         $this->close_xml_writer();
2053     }
2055     protected function write_inforef_xml($newdata, $data) {
2056         $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml");
2057         $this->xmlwriter->begin_tag('inforef');
2058         // Subclasses may provide inforef contents if needed
2059         $this->xmlwriter->end_tag('inforef');
2060         $this->close_xml_writer();
2061     }
2063     protected function write_roles_xml($newdata, $data) {
2064         // This is an empty shell, as the moodle1 converter doesn't handle user data.
2065         $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/roles.xml");
2066         $this->xmlwriter->begin_tag('roles');
2067         $this->xmlwriter->full_tag('role_overrides', '');
2068         $this->xmlwriter->full_tag('role_assignments', '');
2069         $this->xmlwriter->end_tag('roles');
2070         $this->close_xml_writer();
2071     }
2075 /**
2076  * Base class for block generic handler
2077  */
2078 class moodle1_block_generic_handler extends moodle1_block_handler {
2082 /**
2083  * Base class for the activity modules' subplugins
2084  */
2085 abstract class moodle1_submod_handler extends moodle1_plugin_handler {
2087     /** @var moodle1_mod_handler */
2088     protected $parenthandler;
2090     /**
2091      * @param moodle1_mod_handler $parenthandler the handler of a module we are subplugin of
2092      * @param string $subplugintype the type of the subplugin
2093      * @param string $subpluginname the name of the subplugin
2094      */
2095     public function __construct(moodle1_mod_handler $parenthandler, $subplugintype, $subpluginname) {
2096         $this->parenthandler = $parenthandler;
2097         parent::__construct($parenthandler->converter, $subplugintype, $subpluginname);
2098     }
2100     /**
2101      * Activity module subplugins can't declare any paths to handle
2102      *
2103      * The paths must be registered by the parent module and then re-dispatched to the
2104      * relevant subplugins for eventual processing.
2105      *
2106      * @return array empty array
2107      */
2108     final public function get_paths() {
2109         return array();
2110     }