c761ebad83beeb70b96593624a5560fc9038382a
[moodle.git] / backup / moodle2 / backup_stepslib.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 various backup steps that will be used by common tasks in backup
20  *
21  * @package     core_backup
22  * @subpackage  moodle2
23  * @category    backup
24  * @copyright   2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
25  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
28 defined('MOODLE_INTERNAL') || die();
30 /**
31  * Create the temp dir where backup/restore will happen and create temp ids table.
32  */
33 class create_and_clean_temp_stuff extends backup_execution_step {
35     protected function define_execution() {
36         $progress = $this->task->get_progress();
37         $progress->start_progress('Deleting backup directories');
38         backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
39         backup_helper::clear_backup_dir($this->get_backupid(), $progress);           // Empty temp dir, just in case
40         backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
41         backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
42         $progress->end_progress();
43     }
44 }
46 /**
47  * Delete the temp dir used by backup/restore (conditionally),
48  * delete old directories and drop temp ids table. Note we delete
49  * the directory but not the corresponding log file that will be
50  * there for, at least, 1 week - only delete_old_backup_dirs() or cron
51  * deletes log files (for easier access to them).
52  */
53 class drop_and_clean_temp_stuff extends backup_execution_step {
55     protected $skipcleaningtempdir = false;
57     protected function define_execution() {
58         global $CFG;
60         backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
61         backup_helper::delete_old_backup_dirs(strtotime('-1 week'));                // Delete > 1 week old temp dirs.
62         // Delete temp dir conditionally:
63         // 1) If $CFG->keeptempdirectoriesonbackup is not enabled
64         // 2) If backup temp dir deletion has been marked to be avoided
65         if (empty($CFG->keeptempdirectoriesonbackup) && !$this->skipcleaningtempdir) {
66             $progress = $this->task->get_progress();
67             $progress->start_progress('Deleting backup dir');
68             backup_helper::delete_backup_dir($this->get_backupid(), $progress); // Empty backup dir
69             $progress->end_progress();
70         }
71     }
73     public function skip_cleaning_temp_dir($skip) {
74         $this->skipcleaningtempdir = $skip;
75     }
76 }
78 /**
79  * Create the directory where all the task (activity/block...) information will be stored
80  */
81 class create_taskbasepath_directory extends backup_execution_step {
83     protected function define_execution() {
84         global $CFG;
85         $basepath = $this->task->get_taskbasepath();
86         if (!check_dir_exists($basepath, true, true)) {
87             throw new backup_step_exception('cannot_create_taskbasepath_directory', $basepath);
88         }
89     }
90 }
92 /**
93  * Abstract structure step, parent of all the activity structure steps. Used to wrap the
94  * activity structure definition within the main <activity ...> tag. Also provides
95  * subplugin support for activities (that must be properly declared)
96  */
97 abstract class backup_activity_structure_step extends backup_structure_step {
99     /**
100      * Add subplugin structure to any element in the activity backup tree
101      *
102      * @param string $subplugintype type of subplugin as defined in activity db/subplugins.php
103      * @param backup_nested_element $element element in the activity backup tree that
104      *                                       we are going to add subplugin information to
105      * @param bool $multiple to define if multiple subplugins can produce information
106      *                       for each instance of $element (true) or no (false)
107      * @return void
108      */
109     protected function add_subplugin_structure($subplugintype, $element, $multiple) {
111         global $CFG;
113         // Check the requested subplugintype is a valid one
114         $subpluginsfile = $CFG->dirroot . '/mod/' . $this->task->get_modulename() . '/db/subplugins.php';
115         if (!file_exists($subpluginsfile)) {
116              throw new backup_step_exception('activity_missing_subplugins_php_file', $this->task->get_modulename());
117         }
118         include($subpluginsfile);
119         if (!array_key_exists($subplugintype, $subplugins)) {
120              throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
121         }
123         // Arrived here, subplugin is correct, let's create the optigroup
124         $optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
125         $optigroup = new backup_optigroup($optigroupname, null, $multiple);
126         $element->add_child($optigroup); // Add optigroup to stay connected since beginning
128         // Get all the optigroup_elements, looking across all the subplugin dirs
129         $subpluginsdirs = core_component::get_plugin_list($subplugintype);
130         foreach ($subpluginsdirs as $name => $subpluginsdir) {
131             $classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
132             $backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
133             if (file_exists($backupfile)) {
134                 require_once($backupfile);
135                 $backupsubplugin = new $classname($subplugintype, $name, $optigroup, $this);
136                 // Add subplugin returned structure to optigroup
137                 $backupsubplugin->define_subplugin_structure($element->get_name());
138             }
139         }
140     }
142     /**
143      * As far as activity backup steps are implementing backup_subplugin stuff, they need to
144      * have the parent task available for wrapping purposes (get course/context....)
145      *
146      * @return backup_activity_task
147      */
148     public function get_task() {
149         return $this->task;
150     }
152     /**
153      * Wraps any activity backup structure within the common 'activity' element
154      * that will include common to all activities information like id, context...
155      *
156      * @param backup_nested_element $activitystructure the element to wrap
157      * @return backup_nested_element the $activitystructure wrapped by the common 'activity' element
158      */
159     protected function prepare_activity_structure($activitystructure) {
161         // Create the wrap element
162         $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);
164         // Build the tree
165         $activity->add_child($activitystructure);
167         // Set the source
168         $activityarr = array((object)array(
169             'id'         => $this->task->get_activityid(),
170             'moduleid'   => $this->task->get_moduleid(),
171             'modulename' => $this->task->get_modulename(),
172             'contextid'  => $this->task->get_contextid()));
174         $activity->set_source_array($activityarr);
176         // Return the root element (activity)
177         return $activity;
178     }
181 /**
182  * Abstract structure step, to be used by all the activities using core questions stuff
183  * (namely quiz module), supporting question plugins, states and sessions
184  */
185 abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
187     /**
188      * Attach to $element (usually attempts) the needed backup structures
189      * for question_usages and all the associated data.
190      *
191      * @param backup_nested_element $element the element that will contain all the question_usages data.
192      * @param string $usageidname the name of the element that holds the usageid.
193      *      This must be child of $element, and must be a final element.
194      * @param string $nameprefix this prefix is added to all the element names we create.
195      *      Element names in the XML must be unique, so if you are using usages in
196      *      two different ways, you must give a prefix to at least one of them. If
197      *      you only use one sort of usage, then you can just use the default empty prefix.
198      *      This should include a trailing underscore. For example "myprefix_"
199      */
200     protected function add_question_usages($element, $usageidname, $nameprefix = '') {
201         global $CFG;
202         require_once($CFG->dirroot . '/question/engine/lib.php');
204         // Check $element is one nested_backup_element
205         if (! $element instanceof backup_nested_element) {
206             throw new backup_step_exception('question_states_bad_parent_element', $element);
207         }
208         if (! $element->get_final_element($usageidname)) {
209             throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname);
210         }
212         $quba = new backup_nested_element($nameprefix . 'question_usage', array('id'),
213                 array('component', 'preferredbehaviour'));
215         $qas = new backup_nested_element($nameprefix . 'question_attempts');
216         $qa = new backup_nested_element($nameprefix . 'question_attempt', array('id'), array(
217                 'slot', 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction',
218                 'flagged', 'questionsummary', 'rightanswer', 'responsesummary',
219                 'timemodified'));
221         $steps = new backup_nested_element($nameprefix . 'steps');
222         $step = new backup_nested_element($nameprefix . 'step', array('id'), array(
223                 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid'));
225         $response = new backup_nested_element($nameprefix . 'response');
226         $variable = new backup_nested_element($nameprefix . 'variable', null,  array('name', 'value'));
228         // Build the tree
229         $element->add_child($quba);
230         $quba->add_child($qas);
231         $qas->add_child($qa);
232         $qa->add_child($steps);
233         $steps->add_child($step);
234         $step->add_child($response);
235         $response->add_child($variable);
237         // Set the sources
238         $quba->set_source_table('question_usages',
239                 array('id'                => '../' . $usageidname));
240         $qa->set_source_table('question_attempts', array('questionusageid' => backup::VAR_PARENTID), 'slot ASC');
241         $step->set_source_table('question_attempt_steps', array('questionattemptid' => backup::VAR_PARENTID), 'sequencenumber ASC');
242         $variable->set_source_table('question_attempt_step_data', array('attemptstepid' => backup::VAR_PARENTID));
244         // Annotate ids
245         $qa->annotate_ids('question', 'questionid');
246         $step->annotate_ids('user', 'userid');
248         // Annotate files
249         $fileareas = question_engine::get_all_response_file_areas();
250         foreach ($fileareas as $filearea) {
251             $step->annotate_files('question', $filearea, 'id');
252         }
253     }
257 /**
258  * backup structure step in charge of calculating the categories to be
259  * included in backup, based in the context being backuped (module/course)
260  * and the already annotated questions present in backup_ids_temp
261  */
262 class backup_calculate_question_categories extends backup_execution_step {
264     protected function define_execution() {
265         backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());
266     }
269 /**
270  * backup structure step in charge of deleting all the questions annotated
271  * in the backup_ids_temp table
272  */
273 class backup_delete_temp_questions extends backup_execution_step {
275     protected function define_execution() {
276         backup_question_dbops::delete_temp_questions($this->get_backupid());
277     }
280 /**
281  * Abstract structure step, parent of all the block structure steps. Used to wrap the
282  * block structure definition within the main <block ...> tag
283  */
284 abstract class backup_block_structure_step extends backup_structure_step {
286     protected function prepare_block_structure($blockstructure) {
288         // Create the wrap element
289         $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);
291         // Build the tree
292         $block->add_child($blockstructure);
294         // Set the source
295         $blockarr = array((object)array(
296             'id'         => $this->task->get_blockid(),
297             'blockname'  => $this->task->get_blockname(),
298             'contextid'  => $this->task->get_contextid()));
300         $block->set_source_array($blockarr);
302         // Return the root element (block)
303         return $block;
304     }
307 /**
308  * structure step that will generate the module.xml file for the activity,
309  * accumulating various information about the activity, annotating groupings
310  * and completion/avail conf
311  */
312 class backup_module_structure_step extends backup_structure_step {
314     protected function define_structure() {
315         global $DB;
317         // Define each element separated
319         $module = new backup_nested_element('module', array('id', 'version'), array(
320             'modulename', 'sectionid', 'sectionnumber', 'idnumber',
321             'added', 'score', 'indent', 'visible',
322             'visibleold', 'groupmode', 'groupingid',
323             'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
324             'availability', 'showdescription'));
326         // attach format plugin structure to $module element, only one allowed
327         $this->add_plugin_structure('format', $module, false);
329         // attach plagiarism plugin structure to $module element, there can be potentially
330         // many plagiarism plugins storing information about this course
331         $this->add_plugin_structure('plagiarism', $module, true);
333         // attach local plugin structure to $module, multiple allowed
334         $this->add_plugin_structure('local', $module, true);
336         // Set the sources
337         $concat = $DB->sql_concat("'mod_'", 'm.name');
338         $module->set_source_sql("
339             SELECT cm.*, cp.value AS version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
340               FROM {course_modules} cm
341               JOIN {modules} m ON m.id = cm.module
342               JOIN {config_plugins} cp ON cp.plugin = $concat AND cp.name = 'version'
343               JOIN {course_sections} s ON s.id = cm.section
344              WHERE cm.id = ?", array(backup::VAR_MODID));
346         // Define annotations
347         $module->annotate_ids('grouping', 'groupingid');
349         // Return the root element ($module)
350         return $module;
351     }
354 /**
355  * structure step that will generate the section.xml file for the section
356  * annotating files
357  */
358 class backup_section_structure_step extends backup_structure_step {
360     protected function define_structure() {
362         // Define each element separated
364         $section = new backup_nested_element('section', array('id'), array(
365                 'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible',
366                 'availabilityjson'));
368         // attach format plugin structure to $section element, only one allowed
369         $this->add_plugin_structure('format', $section, false);
371         // attach local plugin structure to $section element, multiple allowed
372         $this->add_plugin_structure('local', $section, true);
374         // Add nested elements for course_format_options table
375         $formatoptions = new backup_nested_element('course_format_options', array('id'), array(
376             'format', 'name', 'value'));
377         $section->add_child($formatoptions);
379         // Define sources.
380         $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
381         $formatoptions->set_source_sql('SELECT cfo.id, cfo.format, cfo.name, cfo.value
382               FROM {course} c
383               JOIN {course_format_options} cfo
384               ON cfo.courseid = c.id AND cfo.format = c.format
385               WHERE c.id = ? AND cfo.sectionid = ?',
386                 array(backup::VAR_COURSEID, backup::VAR_SECTIONID));
388         // Aliases
389         $section->set_source_alias('section', 'number');
390         // The 'availability' field needs to be renamed because it clashes with
391         // the old nested element structure for availability data.
392         $section->set_source_alias('availability', 'availabilityjson');
394         // Set annotations
395         $section->annotate_files('course', 'section', 'id');
397         return $section;
398     }
401 /**
402  * structure step that will generate the course.xml file for the course, including
403  * course category reference, tags, modules restriction information
404  * and some annotations (files & groupings)
405  */
406 class backup_course_structure_step extends backup_structure_step {
408     protected function define_structure() {
409         global $DB;
411         // Define each element separated
413         $course = new backup_nested_element('course', array('id', 'contextid'), array(
414             'shortname', 'fullname', 'idnumber',
415             'summary', 'summaryformat', 'format', 'showgrades',
416             'newsitems', 'startdate',
417             'marker', 'maxbytes', 'legacyfiles', 'showreports',
418             'visible', 'groupmode', 'groupmodeforce',
419             'defaultgroupingid', 'lang', 'theme',
420             'timecreated', 'timemodified',
421             'requested',
422             'enablecompletion', 'completionstartonenrol', 'completionnotify'));
424         $category = new backup_nested_element('category', array('id'), array(
425             'name', 'description'));
427         $tags = new backup_nested_element('tags');
429         $tag = new backup_nested_element('tag', array('id'), array(
430             'name', 'rawname'));
432         // attach format plugin structure to $course element, only one allowed
433         $this->add_plugin_structure('format', $course, false);
435         // attach theme plugin structure to $course element; multiple themes can
436         // save course data (in case of user theme, legacy theme, etc)
437         $this->add_plugin_structure('theme', $course, true);
439         // attach general report plugin structure to $course element; multiple
440         // reports can save course data if required
441         $this->add_plugin_structure('report', $course, true);
443         // attach course report plugin structure to $course element; multiple
444         // course reports can save course data if required
445         $this->add_plugin_structure('coursereport', $course, true);
447         // attach plagiarism plugin structure to $course element, there can be potentially
448         // many plagiarism plugins storing information about this course
449         $this->add_plugin_structure('plagiarism', $course, true);
451         // attach local plugin structure to $course element; multiple local plugins
452         // can save course data if required
453         $this->add_plugin_structure('local', $course, true);
455         // Build the tree
457         $course->add_child($category);
459         $course->add_child($tags);
460         $tags->add_child($tag);
462         // Set the sources
464         $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
465         $courserec->contextid = $this->task->get_contextid();
467         $formatoptions = course_get_format($courserec)->get_format_options();
468         $course->add_final_elements(array_keys($formatoptions));
469         foreach ($formatoptions as $key => $value) {
470             $courserec->$key = $value;
471         }
473         $course->set_source_array(array($courserec));
475         $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));
477         $category->set_source_array(array($categoryrec));
479         $tag->set_source_sql('SELECT t.id, t.name, t.rawname
480                                 FROM {tag} t
481                                 JOIN {tag_instance} ti ON ti.tagid = t.id
482                                WHERE ti.itemtype = ?
483                                  AND ti.itemid = ?', array(
484                                      backup_helper::is_sqlparam('course'),
485                                      backup::VAR_PARENTID));
487         // Some annotations
489         $course->annotate_ids('grouping', 'defaultgroupingid');
491         $course->annotate_files('course', 'summary', null);
492         $course->annotate_files('course', 'overviewfiles', null);
493         $course->annotate_files('course', 'legacy', null);
495         // Return root element ($course)
497         return $course;
498     }
501 /**
502  * structure step that will generate the enrolments.xml file for the given course
503  */
504 class backup_enrolments_structure_step extends backup_structure_step {
506     /**
507      * Skip enrolments on the front page.
508      * @return bool
509      */
510     protected function execute_condition() {
511         return ($this->get_courseid() != SITEID);
512     }
514     protected function define_structure() {
516         // To know if we are including users
517         $users = $this->get_setting_value('users');
519         // Define each element separated
521         $enrolments = new backup_nested_element('enrolments');
523         $enrols = new backup_nested_element('enrols');
525         $enrol = new backup_nested_element('enrol', array('id'), array(
526             'enrol', 'status', 'name', 'enrolperiod', 'enrolstartdate',
527             'enrolenddate', 'expirynotify', 'expirytreshold', 'notifyall',
528             'password', 'cost', 'currency', 'roleid',
529             'customint1', 'customint2', 'customint3', 'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
530             'customchar1', 'customchar2', 'customchar3',
531             'customdec1', 'customdec2',
532             'customtext1', 'customtext2', 'customtext3', 'customtext4',
533             'timecreated', 'timemodified'));
535         $userenrolments = new backup_nested_element('user_enrolments');
537         $enrolment = new backup_nested_element('enrolment', array('id'), array(
538             'status', 'userid', 'timestart', 'timeend', 'modifierid',
539             'timemodified'));
541         // Build the tree
542         $enrolments->add_child($enrols);
543         $enrols->add_child($enrol);
544         $enrol->add_child($userenrolments);
545         $userenrolments->add_child($enrolment);
547         // Define sources - the instances are restored using the same sortorder, we do not need to store it in xml and deal with it afterwards.
548         $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID), 'sortorder ASC');
550         // User enrolments only added only if users included
551         if ($users) {
552             $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));
553             $enrolment->annotate_ids('user', 'userid');
554         }
556         $enrol->annotate_ids('role', 'roleid');
558         // Add enrol plugin structure.
559         $this->add_plugin_structure('enrol', $enrol, false);
561         return $enrolments;
562     }
565 /**
566  * structure step that will generate the roles.xml file for the given context, observing
567  * the role_assignments setting to know if that part needs to be included
568  */
569 class backup_roles_structure_step extends backup_structure_step {
571     protected function define_structure() {
573         // To know if we are including role assignments
574         $roleassignments = $this->get_setting_value('role_assignments');
576         // Define each element separated
578         $roles = new backup_nested_element('roles');
580         $overrides = new backup_nested_element('role_overrides');
582         $override = new backup_nested_element('override', array('id'), array(
583             'roleid', 'capability', 'permission', 'timemodified',
584             'modifierid'));
586         $assignments = new backup_nested_element('role_assignments');
588         $assignment = new backup_nested_element('assignment', array('id'), array(
589             'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid',
590             'sortorder'));
592         // Build the tree
593         $roles->add_child($overrides);
594         $roles->add_child($assignments);
596         $overrides->add_child($override);
597         $assignments->add_child($assignment);
599         // Define sources
601         $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));
603         // Assignments only added if specified
604         if ($roleassignments) {
605             $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));
606         }
608         // Define id annotations
609         $override->annotate_ids('role', 'roleid');
611         $assignment->annotate_ids('role', 'roleid');
613         $assignment->annotate_ids('user', 'userid');
615         //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)
617         return $roles;
618     }
621 /**
622  * structure step that will generate the roles.xml containing the
623  * list of roles used along the whole backup process. Just raw
624  * list of used roles from role table
625  */
626 class backup_final_roles_structure_step extends backup_structure_step {
628     protected function define_structure() {
630         // Define elements
632         $rolesdef = new backup_nested_element('roles_definition');
634         $role = new backup_nested_element('role', array('id'), array(
635             'name', 'shortname', 'nameincourse', 'description',
636             'sortorder', 'archetype'));
638         // Build the tree
640         $rolesdef->add_child($role);
642         // Define sources
644         $role->set_source_sql("SELECT r.*, rn.name AS nameincourse
645                                  FROM {role} r
646                                  JOIN {backup_ids_temp} bi ON r.id = bi.itemid
647                             LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?
648                                 WHERE bi.backupid = ?
649                                   AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));
651         // Return main element (rolesdef)
652         return $rolesdef;
653     }
656 /**
657  * structure step that will generate the scales.xml containing the
658  * list of scales used along the whole backup process.
659  */
660 class backup_final_scales_structure_step extends backup_structure_step {
662     protected function define_structure() {
664         // Define elements
666         $scalesdef = new backup_nested_element('scales_definition');
668         $scale = new backup_nested_element('scale', array('id'), array(
669             'courseid', 'userid', 'name', 'scale',
670             'description', 'descriptionformat', 'timemodified'));
672         // Build the tree
674         $scalesdef->add_child($scale);
676         // Define sources
678         $scale->set_source_sql("SELECT s.*
679                                   FROM {scale} s
680                                   JOIN {backup_ids_temp} bi ON s.id = bi.itemid
681                                  WHERE bi.backupid = ?
682                                    AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));
684         // Annotate scale files (they store files in system context, so pass it instead of default one)
685         $scale->annotate_files('grade', 'scale', 'id', context_system::instance()->id);
687         // Return main element (scalesdef)
688         return $scalesdef;
689     }
692 /**
693  * structure step that will generate the outcomes.xml containing the
694  * list of outcomes used along the whole backup process.
695  */
696 class backup_final_outcomes_structure_step extends backup_structure_step {
698     protected function define_structure() {
700         // Define elements
702         $outcomesdef = new backup_nested_element('outcomes_definition');
704         $outcome = new backup_nested_element('outcome', array('id'), array(
705             'courseid', 'userid', 'shortname', 'fullname',
706             'scaleid', 'description', 'descriptionformat', 'timecreated',
707             'timemodified','usermodified'));
709         // Build the tree
711         $outcomesdef->add_child($outcome);
713         // Define sources
715         $outcome->set_source_sql("SELECT o.*
716                                     FROM {grade_outcomes} o
717                                     JOIN {backup_ids_temp} bi ON o.id = bi.itemid
718                                    WHERE bi.backupid = ?
719                                      AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));
721         // Annotate outcome files (they store files in system context, so pass it instead of default one)
722         $outcome->annotate_files('grade', 'outcome', 'id', context_system::instance()->id);
724         // Return main element (outcomesdef)
725         return $outcomesdef;
726     }
729 /**
730  * structure step in charge of constructing the filters.xml file for all the filters found
731  * in activity
732  */
733 class backup_filters_structure_step extends backup_structure_step {
735     protected function define_structure() {
737         // Define each element separated
739         $filters = new backup_nested_element('filters');
741         $actives = new backup_nested_element('filter_actives');
743         $active = new backup_nested_element('filter_active', null, array('filter', 'active'));
745         $configs = new backup_nested_element('filter_configs');
747         $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));
749         // Build the tree
751         $filters->add_child($actives);
752         $filters->add_child($configs);
754         $actives->add_child($active);
755         $configs->add_child($config);
757         // Define sources
759         list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());
761         $active->set_source_array($activearr);
762         $config->set_source_array($configarr);
764         // Return the root element (filters)
765         return $filters;
766     }
769 /**
770  * structure step in charge of constructing the comments.xml file for all the comments found
771  * in a given context
772  */
773 class backup_comments_structure_step extends backup_structure_step {
775     protected function define_structure() {
777         // Define each element separated
779         $comments = new backup_nested_element('comments');
781         $comment = new backup_nested_element('comment', array('id'), array(
782             'commentarea', 'itemid', 'content', 'format',
783             'userid', 'timecreated'));
785         // Build the tree
787         $comments->add_child($comment);
789         // Define sources
791         $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));
793         // Define id annotations
795         $comment->annotate_ids('user', 'userid');
797         // Return the root element (comments)
798         return $comments;
799     }
802 /**
803  * structure step in charge of constructing the badges.xml file for all the badges found
804  * in a given context
805  */
806 class backup_badges_structure_step extends backup_structure_step {
808     protected function execute_condition() {
809         // Check that all activities have been included.
810         if ($this->task->is_excluding_activities()) {
811             return false;
812         }
813         return true;
814     }
816     protected function define_structure() {
818         // Define each element separated.
820         $badges = new backup_nested_element('badges');
821         $badge = new backup_nested_element('badge', array('id'), array('name', 'description',
822                 'timecreated', 'timemodified', 'usercreated', 'usermodified', 'issuername',
823                 'issuerurl', 'issuercontact', 'expiredate', 'expireperiod', 'type', 'courseid',
824                 'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron'));
826         $criteria = new backup_nested_element('criteria');
827         $criterion = new backup_nested_element('criterion', array('id'), array('badgeid',
828                 'criteriatype', 'method'));
830         $parameters = new backup_nested_element('parameters');
831         $parameter = new backup_nested_element('parameter', array('id'), array('critid',
832                 'name', 'value', 'criteriatype'));
834         $manual_awards = new backup_nested_element('manual_awards');
835         $manual_award = new backup_nested_element('manual_award', array('id'), array('badgeid',
836                 'recipientid', 'issuerid', 'issuerrole', 'datemet'));
838         // Build the tree.
840         $badges->add_child($badge);
841         $badge->add_child($criteria);
842         $criteria->add_child($criterion);
843         $criterion->add_child($parameters);
844         $parameters->add_child($parameter);
845         $badge->add_child($manual_awards);
846         $manual_awards->add_child($manual_award);
848         // Define sources.
850         $badge->set_source_table('badge', array('courseid' => backup::VAR_COURSEID));
851         $criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID));
853         $parametersql = 'SELECT cp.*, c.criteriatype
854                              FROM {badge_criteria_param} cp JOIN {badge_criteria} c
855                                  ON cp.critid = c.id
856                              WHERE critid = :critid';
857         $parameterparams = array('critid' => backup::VAR_PARENTID);
858         $parameter->set_source_sql($parametersql, $parameterparams);
860         $manual_award->set_source_table('badge_manual_award', array('badgeid' => backup::VAR_PARENTID));
862         // Define id annotations.
864         $badge->annotate_ids('user', 'usercreated');
865         $badge->annotate_ids('user', 'usermodified');
866         $criterion->annotate_ids('badge', 'badgeid');
867         $parameter->annotate_ids('criterion', 'critid');
868         $badge->annotate_files('badges', 'badgeimage', 'id');
869         $manual_award->annotate_ids('badge', 'badgeid');
870         $manual_award->annotate_ids('user', 'recipientid');
871         $manual_award->annotate_ids('user', 'issuerid');
872         $manual_award->annotate_ids('role', 'issuerrole');
874         // Return the root element ($badges).
875         return $badges;
876     }
879 /**
880  * structure step in charge of constructing the calender.xml file for all the events found
881  * in a given context
882  */
883 class backup_calendarevents_structure_step extends backup_structure_step {
885     protected function define_structure() {
887         // Define each element separated
889         $events = new backup_nested_element('events');
891         $event = new backup_nested_element('event', array('id'), array(
892                 'name', 'description', 'format', 'courseid', 'groupid', 'userid',
893                 'repeatid', 'modulename', 'instance', 'eventtype', 'timestart',
894                 'timeduration', 'visible', 'uuid', 'sequence', 'timemodified'));
896         // Build the tree
897         $events->add_child($event);
899         // Define sources
900         if ($this->name == 'course_calendar') {
901             $calendar_items_sql ="SELECT * FROM {event}
902                         WHERE courseid = :courseid
903                         AND (eventtype = 'course' OR eventtype = 'group')";
904             $calendar_items_params = array('courseid'=>backup::VAR_COURSEID);
905             $event->set_source_sql($calendar_items_sql, $calendar_items_params);
906         } else {
907             $event->set_source_table('event', array('courseid' => backup::VAR_COURSEID, 'instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME));
908         }
910         // Define id annotations
912         $event->annotate_ids('user', 'userid');
913         $event->annotate_ids('group', 'groupid');
914         $event->annotate_files('calendar', 'event_description', 'id');
916         // Return the root element (events)
917         return $events;
918     }
921 /**
922  * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course
923  * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step
924  */
925 class backup_gradebook_structure_step extends backup_structure_step {
927     /**
928      * We need to decide conditionally, based on dynamic information
929      * about the execution of this step. Only will be executed if all
930      * the module gradeitems have been already included in backup
931      */
932     protected function execute_condition() {
933         $courseid = $this->get_courseid();
934         if ($courseid == SITEID) {
935             return false;
936         }
938         return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
939     }
941     protected function define_structure() {
943         // are we including user info?
944         $userinfo = $this->get_setting_value('users');
946         $gradebook = new backup_nested_element('gradebook');
948         //grade_letters are done in backup_activity_grades_structure_step()
950         //calculated grade items
951         $grade_items = new backup_nested_element('grade_items');
952         $grade_item = new backup_nested_element('grade_item', array('id'), array(
953             'categoryid', 'itemname', 'itemtype', 'itemmodule',
954             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
955             'calculation', 'gradetype', 'grademax', 'grademin',
956             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
957             'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
958             'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
959             'needsupdate', 'timecreated', 'timemodified'));
961         $grade_grades = new backup_nested_element('grade_grades');
962         $grade_grade = new backup_nested_element('grade_grade', array('id'), array(
963             'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
964             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
965             'locked', 'locktime', 'exported', 'overridden',
966             'excluded', 'feedback', 'feedbackformat', 'information',
967             'informationformat', 'timecreated', 'timemodified',
968             'aggregationstatus', 'aggregationweight'));
970         //grade_categories
971         $grade_categories = new backup_nested_element('grade_categories');
972         $grade_category   = new backup_nested_element('grade_category', array('id'), array(
973                 //'courseid',
974                 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
975                 'droplow', 'aggregateonlygraded', 'aggregateoutcomes',
976                 'timecreated', 'timemodified', 'hidden'));
978         $letters = new backup_nested_element('grade_letters');
979         $letter = new backup_nested_element('grade_letter', 'id', array(
980             'lowerboundary', 'letter'));
982         $grade_settings = new backup_nested_element('grade_settings');
983         $grade_setting = new backup_nested_element('grade_setting', 'id', array(
984             'name', 'value'));
987         // Build the tree
988         $gradebook->add_child($grade_categories);
989         $grade_categories->add_child($grade_category);
991         $gradebook->add_child($grade_items);
992         $grade_items->add_child($grade_item);
993         $grade_item->add_child($grade_grades);
994         $grade_grades->add_child($grade_grade);
996         $gradebook->add_child($letters);
997         $letters->add_child($letter);
999         $gradebook->add_child($grade_settings);
1000         $grade_settings->add_child($grade_setting);
1002         // Define sources
1004         //Include manual, category and the course grade item
1005         $grade_items_sql ="SELECT * FROM {grade_items}
1006                            WHERE courseid = :courseid
1007                            AND (itemtype='manual' OR itemtype='course' OR itemtype='category')";
1008         $grade_items_params = array('courseid'=>backup::VAR_COURSEID);
1009         $grade_item->set_source_sql($grade_items_sql, $grade_items_params);
1011         if ($userinfo) {
1012             $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
1013         }
1015         $grade_category_sql = "SELECT gc.*, gi.sortorder
1016                                FROM {grade_categories} gc
1017                                JOIN {grade_items} gi ON (gi.iteminstance = gc.id)
1018                                WHERE gc.courseid = :courseid
1019                                AND (gi.itemtype='course' OR gi.itemtype='category')
1020                                ORDER BY gc.parent ASC";//need parent categories before their children
1021         $grade_category_params = array('courseid'=>backup::VAR_COURSEID);
1022         $grade_category->set_source_sql($grade_category_sql, $grade_category_params);
1024         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
1026         $grade_setting->set_source_table('grade_settings', array('courseid' => backup::VAR_COURSEID));
1028         // Annotations (both as final as far as they are going to be exported in next steps)
1029         $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
1030         $grade_item->annotate_ids('outcomefinal', 'outcomeid');
1032         //just in case there are any users not already annotated by the activities
1033         $grade_grade->annotate_ids('userfinal', 'userid');
1035         // Return the root element
1036         return $gradebook;
1037     }
1040 /**
1041  * Step in charge of constructing the grade_history.xml file containing the grade histories.
1042  */
1043 class backup_grade_history_structure_step extends backup_structure_step {
1045     /**
1046      * Limit the execution.
1047      *
1048      * This applies the same logic than the one applied to {@link backup_gradebook_structure_step},
1049      * because we do not want to save the history of items which are not backed up. At least for now.
1050      */
1051     protected function execute_condition() {
1052         $courseid = $this->get_courseid();
1053         if ($courseid == SITEID) {
1054             return false;
1055         }
1057         return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
1058     }
1060     protected function define_structure() {
1062         // Settings to use.
1063         $userinfo = $this->get_setting_value('users');
1064         $history = $this->get_setting_value('grade_histories');
1066         // Create the nested elements.
1067         $bookhistory = new backup_nested_element('grade_history');
1068         $grades = new backup_nested_element('grade_grades');
1069         $grade = new backup_nested_element('grade_grade', array('id'), array(
1070             'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
1071             'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
1072             'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
1073             'excluded', 'feedback', 'feedbackformat', 'information',
1074             'informationformat', 'timemodified'));
1076         // Build the tree.
1077         $bookhistory->add_child($grades);
1078         $grades->add_child($grade);
1080         // This only happens if we are including user info and history.
1081         if ($userinfo && $history) {
1082             // Only keep the history of grades related to items which have been backed up, The query is
1083             // similar (but not identical) to the one used in backup_gradebook_structure_step::define_structure().
1084             $gradesql = "SELECT ggh.*
1085                            FROM {grade_grades_history} ggh
1086                            JOIN {grade_items} gi ON ggh.itemid = gi.id
1087                           WHERE gi.courseid = :courseid
1088                             AND (gi.itemtype = 'manual' OR gi.itemtype = 'course' OR gi.itemtype = 'category')";
1089             $grade->set_source_sql($gradesql, array('courseid' => backup::VAR_COURSEID));
1090         }
1092         // Annotations. (Final annotations as this step is part of the final task).
1093         $grade->annotate_ids('scalefinal', 'rawscaleid');
1094         $grade->annotate_ids('userfinal', 'loggeduser');
1095         $grade->annotate_ids('userfinal', 'userid');
1096         $grade->annotate_ids('userfinal', 'usermodified');
1098         // Return the root element.
1099         return $bookhistory;
1100     }
1104 /**
1105  * structure step in charge if constructing the completion.xml file for all the users completion
1106  * information in a given activity
1107  */
1108 class backup_userscompletion_structure_step extends backup_structure_step {
1110     /**
1111      * Skip completion on the front page.
1112      * @return bool
1113      */
1114     protected function execute_condition() {
1115         return ($this->get_courseid() != SITEID);
1116     }
1118     protected function define_structure() {
1120         // Define each element separated
1122         $completions = new backup_nested_element('completions');
1124         $completion = new backup_nested_element('completion', array('id'), array(
1125             'userid', 'completionstate', 'viewed', 'timemodified'));
1127         // Build the tree
1129         $completions->add_child($completion);
1131         // Define sources
1133         $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
1135         // Define id annotations
1137         $completion->annotate_ids('user', 'userid');
1139         // Return the root element (completions)
1140         return $completions;
1141     }
1144 /**
1145  * structure step in charge of constructing the main groups.xml file for all the groups and
1146  * groupings information already annotated
1147  */
1148 class backup_groups_structure_step extends backup_structure_step {
1150     protected function define_structure() {
1152         // To know if we are including users.
1153         $userinfo = $this->get_setting_value('users');
1154         // To know if we are including groups and groupings.
1155         $groupinfo = $this->get_setting_value('groups');
1157         // Define each element separated
1159         $groups = new backup_nested_element('groups');
1161         $group = new backup_nested_element('group', array('id'), array(
1162             'name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey',
1163             'picture', 'hidepicture', 'timecreated', 'timemodified'));
1165         $members = new backup_nested_element('group_members');
1167         $member = new backup_nested_element('group_member', array('id'), array(
1168             'userid', 'timeadded', 'component', 'itemid'));
1170         $groupings = new backup_nested_element('groupings');
1172         $grouping = new backup_nested_element('grouping', 'id', array(
1173             'name', 'idnumber', 'description', 'descriptionformat', 'configdata',
1174             'timecreated', 'timemodified'));
1176         $groupinggroups = new backup_nested_element('grouping_groups');
1178         $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
1179             'groupid', 'timeadded'));
1181         // Build the tree
1183         $groups->add_child($group);
1184         $groups->add_child($groupings);
1186         $group->add_child($members);
1187         $members->add_child($member);
1189         $groupings->add_child($grouping);
1190         $grouping->add_child($groupinggroups);
1191         $groupinggroups->add_child($groupinggroup);
1193         // Define sources
1195         // This only happens if we are including groups/groupings.
1196         if ($groupinfo) {
1197             $group->set_source_sql("
1198                 SELECT g.*
1199                   FROM {groups} g
1200                   JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1201                  WHERE bi.backupid = ?
1202                    AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
1204             $grouping->set_source_sql("
1205                 SELECT g.*
1206                   FROM {groupings} g
1207                   JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1208                  WHERE bi.backupid = ?
1209                    AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
1210             $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
1212             // This only happens if we are including users.
1213             if ($userinfo) {
1214                 $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
1215             }
1216         }
1218         // Define id annotations (as final)
1220         $member->annotate_ids('userfinal', 'userid');
1222         // Define file annotations
1224         $group->annotate_files('group', 'description', 'id');
1225         $group->annotate_files('group', 'icon', 'id');
1226         $grouping->annotate_files('grouping', 'description', 'id');
1228         // Return the root element (groups)
1229         return $groups;
1230     }
1233 /**
1234  * structure step in charge of constructing the main users.xml file for all the users already
1235  * annotated (final). Includes custom profile fields, preferences, tags, role assignments and
1236  * overrides.
1237  */
1238 class backup_users_structure_step extends backup_structure_step {
1240     protected function define_structure() {
1241         global $CFG;
1243         // To know if we are anonymizing users
1244         $anonymize = $this->get_setting_value('anonymize');
1245         // To know if we are including role assignments
1246         $roleassignments = $this->get_setting_value('role_assignments');
1248         // Define each element separate.
1250         $users = new backup_nested_element('users');
1252         // Create the array of user fields by hand, as far as we have various bits to control
1253         // anonymize option, password backup, mnethostid...
1255         // First, the fields not needing anonymization nor special handling
1256         $normalfields = array(
1257             'confirmed', 'policyagreed', 'deleted',
1258             'lang', 'theme', 'timezone', 'firstaccess',
1259             'lastaccess', 'lastlogin', 'currentlogin',
1260             'mailformat', 'maildigest', 'maildisplay',
1261             'autosubscribe', 'trackforums', 'timecreated',
1262             'timemodified', 'trustbitmask');
1264         // Then, the fields potentially needing anonymization
1265         $anonfields = array(
1266             'username', 'idnumber', 'email', 'icq', 'skype',
1267             'yahoo', 'aim', 'msn', 'phone1',
1268             'phone2', 'institution', 'department', 'address',
1269             'city', 'country', 'lastip', 'picture',
1270             'url', 'description', 'descriptionformat', 'imagealt', 'auth');
1271         $anonfields = array_merge($anonfields, get_all_user_name_fields());
1273         // Add anonymized fields to $userfields with custom final element
1274         foreach ($anonfields as $field) {
1275             if ($anonymize) {
1276                 $userfields[] = new anonymizer_final_element($field);
1277             } else {
1278                 $userfields[] = $field; // No anonymization, normally added
1279             }
1280         }
1282         // mnethosturl requires special handling (custom final element)
1283         $userfields[] = new mnethosturl_final_element('mnethosturl');
1285         // password added conditionally
1286         if (!empty($CFG->includeuserpasswordsinbackup)) {
1287             $userfields[] = 'password';
1288         }
1290         // Merge all the fields
1291         $userfields = array_merge($userfields, $normalfields);
1293         $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1295         $customfields = new backup_nested_element('custom_fields');
1297         $customfield = new backup_nested_element('custom_field', array('id'), array(
1298             'field_name', 'field_type', 'field_data'));
1300         $tags = new backup_nested_element('tags');
1302         $tag = new backup_nested_element('tag', array('id'), array(
1303             'name', 'rawname'));
1305         $preferences = new backup_nested_element('preferences');
1307         $preference = new backup_nested_element('preference', array('id'), array(
1308             'name', 'value'));
1310         $roles = new backup_nested_element('roles');
1312         $overrides = new backup_nested_element('role_overrides');
1314         $override = new backup_nested_element('override', array('id'), array(
1315             'roleid', 'capability', 'permission', 'timemodified',
1316             'modifierid'));
1318         $assignments = new backup_nested_element('role_assignments');
1320         $assignment = new backup_nested_element('assignment', array('id'), array(
1321             'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
1322             'sortorder'));
1324         // Build the tree
1326         $users->add_child($user);
1328         $user->add_child($customfields);
1329         $customfields->add_child($customfield);
1331         $user->add_child($tags);
1332         $tags->add_child($tag);
1334         $user->add_child($preferences);
1335         $preferences->add_child($preference);
1337         $user->add_child($roles);
1339         $roles->add_child($overrides);
1340         $roles->add_child($assignments);
1342         $overrides->add_child($override);
1343         $assignments->add_child($assignment);
1345         // Define sources
1347         $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
1348                                  FROM {user} u
1349                                  JOIN {backup_ids_temp} bi ON bi.itemid = u.id
1350                             LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . '
1351                             LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
1352                                 WHERE bi.backupid = ?
1353                                   AND bi.itemname = ?', array(
1354                                       backup_helper::is_sqlparam($this->get_backupid()),
1355                                       backup_helper::is_sqlparam('userfinal')));
1357         // All the rest on information is only added if we arent
1358         // in an anonymized backup
1359         if (!$anonymize) {
1360             $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data
1361                                             FROM {user_info_field} f
1362                                             JOIN {user_info_data} d ON d.fieldid = f.id
1363                                            WHERE d.userid = ?', array(backup::VAR_PARENTID));
1365             $customfield->set_source_alias('shortname', 'field_name');
1366             $customfield->set_source_alias('datatype',  'field_type');
1367             $customfield->set_source_alias('data',      'field_data');
1369             $tag->set_source_sql('SELECT t.id, t.name, t.rawname
1370                                     FROM {tag} t
1371                                     JOIN {tag_instance} ti ON ti.tagid = t.id
1372                                    WHERE ti.itemtype = ?
1373                                      AND ti.itemid = ?', array(
1374                                          backup_helper::is_sqlparam('user'),
1375                                          backup::VAR_PARENTID));
1377             $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1379             $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1381             // Assignments only added if specified
1382             if ($roleassignments) {
1383                 $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1384             }
1386             // Define id annotations (as final)
1387             $override->annotate_ids('rolefinal', 'roleid');
1388         }
1390         // Return root element (users)
1391         return $users;
1392     }
1395 /**
1396  * structure step in charge of constructing the block.xml file for one
1397  * given block (instance and positions). If the block has custom DB structure
1398  * that will go to a separate file (different step defined in block class)
1399  */
1400 class backup_block_instance_structure_step extends backup_structure_step {
1402     protected function define_structure() {
1403         global $DB;
1405         // Define each element separated
1407         $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
1408             'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1409             'subpagepattern', 'defaultregion', 'defaultweight', 'configdata'));
1411         $positions = new backup_nested_element('block_positions');
1413         $position = new backup_nested_element('block_position', array('id'), array(
1414             'contextid', 'pagetype', 'subpage', 'visible',
1415             'region', 'weight'));
1417         // Build the tree
1419         $block->add_child($positions);
1420         $positions->add_child($position);
1422         // Transform configdata information if needed (process links and friends)
1423         $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
1424         if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
1425             $configdata = (array)unserialize(base64_decode($blockrec->configdata));
1426             foreach ($configdata as $attribute => $value) {
1427                 if (in_array($attribute, $attrstotransform)) {
1428                     $configdata[$attribute] = $this->contenttransformer->process($value);
1429                 }
1430             }
1431             $blockrec->configdata = base64_encode(serialize((object)$configdata));
1432         }
1433         $blockrec->contextid = $this->task->get_contextid();
1434         // Get the version of the block
1435         $blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version');
1437         // Define sources
1439         $block->set_source_array(array($blockrec));
1441         $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
1443         // File anotations (for fileareas specified on each block)
1444         foreach ($this->task->get_fileareas() as $filearea) {
1445             $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);
1446         }
1448         // Return the root element (block)
1449         return $block;
1450     }
1453 /**
1454  * structure step in charge of constructing the logs.xml file for all the log records found
1455  * in course. Note that we are sending to backup ALL the log records having cmid = 0. That
1456  * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do
1457  * that just in case they become restored some day in the future
1458  */
1459 class backup_course_logs_structure_step extends backup_structure_step {
1461     protected function define_structure() {
1463         // Define each element separated
1465         $logs = new backup_nested_element('logs');
1467         $log = new backup_nested_element('log', array('id'), array(
1468             'time', 'userid', 'ip', 'module',
1469             'action', 'url', 'info'));
1471         // Build the tree
1473         $logs->add_child($log);
1475         // Define sources (all the records belonging to the course, having cmid = 0)
1477         $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0)));
1479         // Annotations
1480         // NOTE: We don't annotate users from logs as far as they MUST be
1481         //       always annotated by the course (enrol, ras... whatever)
1483         // Return the root element (logs)
1485         return $logs;
1486     }
1489 /**
1490  * structure step in charge of constructing the logs.xml file for all the log records found
1491  * in activity
1492  */
1493 class backup_activity_logs_structure_step extends backup_structure_step {
1495     protected function define_structure() {
1497         // Define each element separated
1499         $logs = new backup_nested_element('logs');
1501         $log = new backup_nested_element('log', array('id'), array(
1502             'time', 'userid', 'ip', 'module',
1503             'action', 'url', 'info'));
1505         // Build the tree
1507         $logs->add_child($log);
1509         // Define sources
1511         $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1513         // Annotations
1514         // NOTE: We don't annotate users from logs as far as they MUST be
1515         //       always annotated by the activity (true participants).
1517         // Return the root element (logs)
1519         return $logs;
1520     }
1523 /**
1524  * structure in charge of constructing the inforef.xml file for all the items we want
1525  * to have referenced there (users, roles, files...)
1526  */
1527 class backup_inforef_structure_step extends backup_structure_step {
1529     protected function define_structure() {
1531         // Items we want to include in the inforef file.
1532         $items = backup_helper::get_inforef_itemnames();
1534         // Build the tree
1536         $inforef = new backup_nested_element('inforef');
1538         // For each item, conditionally, if there are already records, build element
1539         foreach ($items as $itemname) {
1540             if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {
1541                 $elementroot = new backup_nested_element($itemname . 'ref');
1542                 $element = new backup_nested_element($itemname, array(), array('id'));
1543                 $inforef->add_child($elementroot);
1544                 $elementroot->add_child($element);
1545                 $element->set_source_sql("
1546                     SELECT itemid AS id
1547                      FROM {backup_ids_temp}
1548                     WHERE backupid = ?
1549                       AND itemname = ?",
1550                    array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
1551             }
1552         }
1554         // We don't annotate anything there, but rely in the next step
1555         // (move_inforef_annotations_to_final) that will change all the
1556         // already saved 'inforref' entries to their 'final' annotations.
1557         return $inforef;
1558     }
1561 /**
1562  * This step will get all the annotations already processed to inforef.xml file and
1563  * transform them into 'final' annotations.
1564  */
1565 class move_inforef_annotations_to_final extends backup_execution_step {
1567     protected function define_execution() {
1569         // Items we want to include in the inforef file
1570         $items = backup_helper::get_inforef_itemnames();
1571         $progress = $this->task->get_progress();
1572         $progress->start_progress($this->get_name(), count($items));
1573         $done = 1;
1574         foreach ($items as $itemname) {
1575             // Delegate to dbops
1576             backup_structure_dbops::move_annotations_to_final($this->get_backupid(),
1577                     $itemname, $progress);
1578             $progress->progress($done++);
1579         }
1580         $progress->end_progress();
1581     }
1584 /**
1585  * structure in charge of constructing the files.xml file with all the
1586  * annotated (final) files along the process. At, the same time, and
1587  * using one specialised nested_element, will copy them form moodle storage
1588  * to backup storage
1589  */
1590 class backup_final_files_structure_step extends backup_structure_step {
1592     protected function define_structure() {
1594         // Define elements
1596         $files = new backup_nested_element('files');
1598         $file = new file_nested_element('file', array('id'), array(
1599             'contenthash', 'contextid', 'component', 'filearea', 'itemid',
1600             'filepath', 'filename', 'userid', 'filesize',
1601             'mimetype', 'status', 'timecreated', 'timemodified',
1602             'source', 'author', 'license', 'sortorder',
1603             'repositorytype', 'repositoryid', 'reference'));
1605         // Build the tree
1607         $files->add_child($file);
1609         // Define sources
1611         $file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference
1612                                  FROM {files} f
1613                                       LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid
1614                                       LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid
1615                                       LEFT JOIN {repository} r ON r.id = ri.typeid
1616                                       JOIN {backup_ids_temp} bi ON f.id = bi.itemid
1617                                 WHERE bi.backupid = ?
1618                                   AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
1620         return $files;
1621     }
1624 /**
1625  * Structure step in charge of creating the main moodle_backup.xml file
1626  * where all the information related to the backup, settings, license and
1627  * other information needed on restore is added*/
1628 class backup_main_structure_step extends backup_structure_step {
1630     protected function define_structure() {
1632         global $CFG;
1634         $info = array();
1636         $info['name'] = $this->get_setting_value('filename');
1637         $info['moodle_version'] = $CFG->version;
1638         $info['moodle_release'] = $CFG->release;
1639         $info['backup_version'] = $CFG->backup_version;
1640         $info['backup_release'] = $CFG->backup_release;
1641         $info['backup_date']    = time();
1642         $info['backup_uniqueid']= $this->get_backupid();
1643         $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
1644         $info['include_files'] = backup_controller_dbops::backup_includes_files($this->get_backupid());
1645         $info['include_file_references_to_external_content'] =
1646                 backup_controller_dbops::backup_includes_file_references($this->get_backupid());
1647         $info['original_wwwroot']=$CFG->wwwroot;
1648         $info['original_site_identifier_hash'] = md5(get_site_identifier());
1649         $info['original_course_id'] = $this->get_courseid();
1650         $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
1651         $info['original_course_format'] = $originalcourseinfo->format;
1652         $info['original_course_fullname']  = $originalcourseinfo->fullname;
1653         $info['original_course_shortname'] = $originalcourseinfo->shortname;
1654         $info['original_course_startdate'] = $originalcourseinfo->startdate;
1655         $info['original_course_contextid'] = context_course::instance($this->get_courseid())->id;
1656         $info['original_system_contextid'] = context_system::instance()->id;
1658         // Get more information from controller
1659         list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
1660                 $this->get_backupid(), $this->get_task()->get_progress());
1662         // Define elements
1664         $moodle_backup = new backup_nested_element('moodle_backup');
1666         $information = new backup_nested_element('information', null, array(
1667             'name', 'moodle_version', 'moodle_release', 'backup_version',
1668             'backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot',
1669             'original_site_identifier_hash', 'original_course_id', 'original_course_format',
1670             'original_course_fullname', 'original_course_shortname', 'original_course_startdate',
1671             'original_course_contextid', 'original_system_contextid'));
1673         $details = new backup_nested_element('details');
1675         $detail = new backup_nested_element('detail', array('backup_id'), array(
1676             'type', 'format', 'interactive', 'mode',
1677             'execution', 'executiontime'));
1679         $contents = new backup_nested_element('contents');
1681         $activities = new backup_nested_element('activities');
1683         $activity = new backup_nested_element('activity', null, array(
1684             'moduleid', 'sectionid', 'modulename', 'title',
1685             'directory'));
1687         $sections = new backup_nested_element('sections');
1689         $section = new backup_nested_element('section', null, array(
1690             'sectionid', 'title', 'directory'));
1692         $course = new backup_nested_element('course', null, array(
1693             'courseid', 'title', 'directory'));
1695         $settings = new backup_nested_element('settings');
1697         $setting = new backup_nested_element('setting', null, array(
1698             'level', 'section', 'activity', 'name', 'value'));
1700         // Build the tree
1702         $moodle_backup->add_child($information);
1704         $information->add_child($details);
1705         $details->add_child($detail);
1707         $information->add_child($contents);
1708         if (!empty($cinfo['activities'])) {
1709             $contents->add_child($activities);
1710             $activities->add_child($activity);
1711         }
1712         if (!empty($cinfo['sections'])) {
1713             $contents->add_child($sections);
1714             $sections->add_child($section);
1715         }
1716         if (!empty($cinfo['course'])) {
1717             $contents->add_child($course);
1718         }
1720         $information->add_child($settings);
1721         $settings->add_child($setting);
1724         // Set the sources
1726         $information->set_source_array(array((object)$info));
1728         $detail->set_source_array($dinfo);
1730         $activity->set_source_array($cinfo['activities']);
1732         $section->set_source_array($cinfo['sections']);
1734         $course->set_source_array($cinfo['course']);
1736         $setting->set_source_array($sinfo);
1738         // Prepare some information to be sent to main moodle_backup.xml file
1739         return $moodle_backup;
1740     }
1744 /**
1745  * Execution step that will generate the final zip (.mbz) file with all the contents
1746  */
1747 class backup_zip_contents extends backup_execution_step implements file_progress {
1748     /**
1749      * @var bool True if we have started tracking progress
1750      */
1751     protected $startedprogress;
1753     protected function define_execution() {
1755         // Get basepath
1756         $basepath = $this->get_basepath();
1758         // Get the list of files in directory
1759         $filestemp = get_directory_list($basepath, '', false, true, true);
1760         $files = array();
1761         foreach ($filestemp as $file) { // Add zip paths and fs paths to all them
1762             $files[$file] = $basepath . '/' . $file;
1763         }
1765         // Add the log file if exists
1766         $logfilepath = $basepath . '.log';
1767         if (file_exists($logfilepath)) {
1768              $files['moodle_backup.log'] = $logfilepath;
1769         }
1771         // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1772         $zipfile = $basepath . '/backup.mbz';
1774         // Get the zip packer
1775         $zippacker = get_file_packer('application/vnd.moodle.backup');
1777         // Track overall progress for the 2 long-running steps (archive to
1778         // pathname, get backup information).
1779         $reporter = $this->task->get_progress();
1780         $reporter->start_progress('backup_zip_contents', 2);
1782         // Zip files
1783         $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this);
1785         // If any sub-progress happened, end it.
1786         if ($this->startedprogress) {
1787             $this->task->get_progress()->end_progress();
1788             $this->startedprogress = false;
1789         } else {
1790             // No progress was reported, manually move it on to the next overall task.
1791             $reporter->progress(1);
1792         }
1794         // Something went wrong.
1795         if ($result === false) {
1796             @unlink($zipfile);
1797             throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip');
1798         }
1799         // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid.
1800         try {
1801             backup_general_helper::get_backup_information_from_mbz($zipfile, $this);
1802         } catch (backup_helper_exception $e) {
1803             @unlink($zipfile);
1804             throw new backup_step_exception('error_zip_packing', '', $e->debuginfo);
1805         }
1807         // If any sub-progress happened, end it.
1808         if ($this->startedprogress) {
1809             $this->task->get_progress()->end_progress();
1810             $this->startedprogress = false;
1811         } else {
1812             $reporter->progress(2);
1813         }
1814         $reporter->end_progress();
1815     }
1817     /**
1818      * Implementation for file_progress interface to display unzip progress.
1819      *
1820      * @param int $progress Current progress
1821      * @param int $max Max value
1822      */
1823     public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
1824         $reporter = $this->task->get_progress();
1826         // Start tracking progress if necessary.
1827         if (!$this->startedprogress) {
1828             $reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE)
1829                     ? \core\progress\base::INDETERMINATE : $max);
1830             $this->startedprogress = true;
1831         }
1833         // Pass progress through to whatever handles it.
1834         $reporter->progress(($progress == file_progress::INDETERMINATE)
1835                 ? \core\progress\base::INDETERMINATE : $progress);
1836      }
1839 /**
1840  * This step will send the generated backup file to its final destination
1841  */
1842 class backup_store_backup_file extends backup_execution_step {
1844     protected function define_execution() {
1846         // Get basepath
1847         $basepath = $this->get_basepath();
1849         // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1850         $zipfile = $basepath . '/backup.mbz';
1852         $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid());
1853         // Perform storage and return it (TODO: shouldn't be array but proper result object)
1854         return array(
1855             'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile,
1856                     $this->task->get_progress()),
1857             'include_file_references_to_external_content' => $has_file_references
1858         );
1859     }
1863 /**
1864  * This step will search for all the activity (not calculations, categories nor aggregations) grade items
1865  * and put them to the backup_ids tables, to be used later as base to backup them
1866  */
1867 class backup_activity_grade_items_to_ids extends backup_execution_step {
1869     protected function define_execution() {
1871         // Fetch all activity grade items
1872         if ($items = grade_item::fetch_all(array(
1873                          'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),
1874                          'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {
1875             // Annotate them in backup_ids
1876             foreach ($items as $item) {
1877                 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);
1878             }
1879         }
1880     }
1884 /**
1885  * This step allows enrol plugins to annotate custom fields.
1886  *
1887  * @package   core_backup
1888  * @copyright 2014 University of Wisconsin
1889  * @author    Matt Petro
1890  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1891  */
1892 class backup_enrolments_execution_step extends backup_execution_step {
1894     /**
1895      * Function that will contain all the code to be executed.
1896      */
1897     protected function define_execution() {
1898         global $DB;
1900         $plugins = enrol_get_plugins(true);
1901         $enrols = $DB->get_records('enrol', array(
1902                 'courseid' => $this->task->get_courseid()));
1904         // Allow each enrol plugin to add annotations.
1905         foreach ($enrols as $enrol) {
1906             if (isset($plugins[$enrol->enrol])) {
1907                 $plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol);
1908             }
1909         }
1910     }
1912     /**
1913      * Annotate a single name/id pair.
1914      * This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}.
1915      *
1916      * @param string $itemname
1917      * @param int $itemid
1918      */
1919     public function annotate_id($itemname, $itemid) {
1920         backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid);
1921     }
1924 /**
1925  * This step will annotate all the groups and groupings belonging to the course
1926  */
1927 class backup_annotate_course_groups_and_groupings extends backup_execution_step {
1929     protected function define_execution() {
1930         global $DB;
1932         // Get all the course groups
1933         if ($groups = $DB->get_records('groups', array(
1934                 'courseid' => $this->task->get_courseid()))) {
1935             foreach ($groups as $group) {
1936                 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id);
1937             }
1938         }
1940         // Get all the course groupings
1941         if ($groupings = $DB->get_records('groupings', array(
1942                 'courseid' => $this->task->get_courseid()))) {
1943             foreach ($groupings as $grouping) {
1944                 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id);
1945             }
1946         }
1947     }
1950 /**
1951  * This step will annotate all the groups belonging to already annotated groupings
1952  */
1953 class backup_annotate_groups_from_groupings extends backup_execution_step {
1955     protected function define_execution() {
1956         global $DB;
1958         // Fetch all the annotated groupings
1959         if ($groupings = $DB->get_records('backup_ids_temp', array(
1960                 'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
1961             foreach ($groupings as $grouping) {
1962                 if ($groups = $DB->get_records('groupings_groups', array(
1963                         'groupingid' => $grouping->itemid))) {
1964                     foreach ($groups as $group) {
1965                         backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
1966                     }
1967                 }
1968             }
1969         }
1970     }
1973 /**
1974  * This step will annotate all the scales belonging to already annotated outcomes
1975  */
1976 class backup_annotate_scales_from_outcomes extends backup_execution_step {
1978     protected function define_execution() {
1979         global $DB;
1981         // Fetch all the annotated outcomes
1982         if ($outcomes = $DB->get_records('backup_ids_temp', array(
1983                 'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
1984             foreach ($outcomes as $outcome) {
1985                 if ($scale = $DB->get_record('grade_outcomes', array(
1986                         'id' => $outcome->itemid))) {
1987                     // Annotate as scalefinal because it's > 0
1988                     backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
1989                 }
1990             }
1991         }
1992     }
1995 /**
1996  * This step will generate all the file annotations for the already
1997  * annotated (final) question_categories. It calculates the different
1998  * contexts that are being backup and, annotates all the files
1999  * on every context belonging to the "question" component. As far as
2000  * we are always including *complete* question banks it is safe and
2001  * optimal to do that in this (one pass) way
2002  */
2003 class backup_annotate_all_question_files extends backup_execution_step {
2005     protected function define_execution() {
2006         global $DB;
2008         // Get all the different contexts for the final question_categories
2009         // annotated along the whole backup
2010         $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
2011                                         FROM {question_categories} qc
2012                                         JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
2013                                        WHERE bi.backupid = ?
2014                                          AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
2015         // To know about qtype specific components/fileareas
2016         $components = backup_qtype_plugin::get_components_and_fileareas();
2017         // Let's loop
2018         foreach($rs as $record) {
2019             // Backup all the file areas the are managed by the core question component.
2020             // That is, by the question_type base class. In particular, we don't want
2021             // to include files belonging to responses here.
2022             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'questiontext', null);
2023             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'generalfeedback', null);
2024             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answer', null);
2025             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answerfeedback', null);
2026             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'hint', null);
2027             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'correctfeedback', null);
2028             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'partiallycorrectfeedback', null);
2029             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'incorrectfeedback', null);
2031             // For files belonging to question types, we make the leap of faith that
2032             // all the files belonging to the question type are part of the question definition,
2033             // so we can just backup all the files in bulk, without specifying each
2034             // file area name separately.
2035             foreach ($components as $component => $fileareas) {
2036                 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null);
2037             }
2038         }
2039         $rs->close();
2040     }
2043 /**
2044  * structure step in charge of constructing the questions.xml file for all the
2045  * question categories and questions required by the backup
2046  * and letters related to one activity
2047  */
2048 class backup_questions_structure_step extends backup_structure_step {
2050     protected function define_structure() {
2052         // Define each element separated
2054         $qcategories = new backup_nested_element('question_categories');
2056         $qcategory = new backup_nested_element('question_category', array('id'), array(
2057             'name', 'contextid', 'contextlevel', 'contextinstanceid',
2058             'info', 'infoformat', 'stamp', 'parent',
2059             'sortorder'));
2061         $questions = new backup_nested_element('questions');
2063         $question = new backup_nested_element('question', array('id'), array(
2064             'parent', 'name', 'questiontext', 'questiontextformat',
2065             'generalfeedback', 'generalfeedbackformat', 'defaultmark', 'penalty',
2066             'qtype', 'length', 'stamp', 'version',
2067             'hidden', 'timecreated', 'timemodified', 'createdby', 'modifiedby'));
2069         // attach qtype plugin structure to $question element, only one allowed
2070         $this->add_plugin_structure('qtype', $question, false);
2072         // attach local plugin stucture to $question element, multiple allowed
2073         $this->add_plugin_structure('local', $question, true);
2075         $qhints = new backup_nested_element('question_hints');
2077         $qhint = new backup_nested_element('question_hint', array('id'), array(
2078             'hint', 'hintformat', 'shownumcorrect', 'clearwrong', 'options'));
2080         $tags = new backup_nested_element('tags');
2082         $tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));
2084         // Build the tree
2086         $qcategories->add_child($qcategory);
2087         $qcategory->add_child($questions);
2088         $questions->add_child($question);
2089         $question->add_child($qhints);
2090         $qhints->add_child($qhint);
2092         $question->add_child($tags);
2093         $tags->add_child($tag);
2095         // Define the sources
2097         $qcategory->set_source_sql("
2098             SELECT gc.*, contextlevel, instanceid AS contextinstanceid
2099               FROM {question_categories} gc
2100               JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
2101               JOIN {context} co ON co.id = gc.contextid
2102              WHERE bi.backupid = ?
2103                AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID));
2105         $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
2107         $qhint->set_source_sql('
2108                 SELECT *
2109                 FROM {question_hints}
2110                 WHERE questionid = :questionid
2111                 ORDER BY id',
2112                 array('questionid' => backup::VAR_PARENTID));
2114         $tag->set_source_sql("SELECT t.id, t.name, t.rawname
2115                               FROM {tag} t
2116                               JOIN {tag_instance} ti ON ti.tagid = t.id
2117                               WHERE ti.itemid = ?
2118                               AND ti.itemtype = 'question'", array(backup::VAR_PARENTID));
2120         // don't need to annotate ids nor files
2121         // (already done by {@link backup_annotate_all_question_files}
2123         return $qcategories;
2124     }
2129 /**
2130  * This step will generate all the file  annotations for the already
2131  * annotated (final) users. Need to do this here because each user
2132  * has its own context and structure tasks only are able to handle
2133  * one context. Also, this step will guarantee that every user has
2134  * its context created (req for other steps)
2135  */
2136 class backup_annotate_all_user_files extends backup_execution_step {
2138     protected function define_execution() {
2139         global $DB;
2141         // List of fileareas we are going to annotate
2142         $fileareas = array('profile', 'icon');
2144         // Fetch all annotated (final) users
2145         $rs = $DB->get_recordset('backup_ids_temp', array(
2146             'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
2147         $progress = $this->task->get_progress();
2148         $progress->start_progress($this->get_name());
2149         foreach ($rs as $record) {
2150             $userid = $record->itemid;
2151             $userctx = context_user::instance($userid, IGNORE_MISSING);
2152             if (!$userctx) {
2153                 continue; // User has not context, sure it's a deleted user, so cannot have files
2154             }
2155             // Proceed with every user filearea
2156             foreach ($fileareas as $filearea) {
2157                 // We don't need to specify itemid ($userid - 5th param) as far as by
2158                 // context we can get all the associated files. See MDL-22092
2159                 backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null);
2160                 $progress->progress();
2161             }
2162         }
2163         $progress->end_progress();
2164         $rs->close();
2165     }
2169 /**
2170  * Defines the backup step for advanced grading methods attached to the activity module
2171  */
2172 class backup_activity_grading_structure_step extends backup_structure_step {
2174     /**
2175      * Include the grading.xml only if the module supports advanced grading
2176      */
2177     protected function execute_condition() {
2179         // No grades on the front page.
2180         if ($this->get_courseid() == SITEID) {
2181             return false;
2182         }
2184         return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false);
2185     }
2187     /**
2188      * Declares the gradable areas structures and data sources
2189      */
2190     protected function define_structure() {
2192         // To know if we are including userinfo
2193         $userinfo = $this->get_setting_value('userinfo');
2195         // Define the elements
2197         $areas = new backup_nested_element('areas');
2199         $area = new backup_nested_element('area', array('id'), array(
2200             'areaname', 'activemethod'));
2202         $definitions = new backup_nested_element('definitions');
2204         $definition = new backup_nested_element('definition', array('id'), array(
2205             'method', 'name', 'description', 'descriptionformat', 'status',
2206             'timecreated', 'timemodified', 'options'));
2208         $instances = new backup_nested_element('instances');
2210         $instance = new backup_nested_element('instance', array('id'), array(
2211             'raterid', 'itemid', 'rawgrade', 'status', 'feedback',
2212             'feedbackformat', 'timemodified'));
2214         // Build the tree including the method specific structures
2215         // (beware - the order of how gradingform plugins structures are attached is important)
2216         $areas->add_child($area);
2217         // attach local plugin stucture to $area element, multiple allowed
2218         $this->add_plugin_structure('local', $area, true);
2219         $area->add_child($definitions);
2220         $definitions->add_child($definition);
2221         $this->add_plugin_structure('gradingform', $definition, true);
2222         // attach local plugin stucture to $definition element, multiple allowed
2223         $this->add_plugin_structure('local', $definition, true);
2224         $definition->add_child($instances);
2225         $instances->add_child($instance);
2226         $this->add_plugin_structure('gradingform', $instance, false);
2227         // attach local plugin stucture to $instance element, multiple allowed
2228         $this->add_plugin_structure('local', $instance, true);
2230         // Define data sources
2232         $area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID,
2233             'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename())));
2235         $definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID));
2237         if ($userinfo) {
2238             $instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID));
2239         }
2241         // Annotate references
2242         $definition->annotate_files('grading', 'description', 'id');
2243         $instance->annotate_ids('user', 'raterid');
2245         // Return the root element
2246         return $areas;
2247     }
2251 /**
2252  * structure step in charge of constructing the grades.xml file for all the grade items
2253  * and letters related to one activity
2254  */
2255 class backup_activity_grades_structure_step extends backup_structure_step {
2257     /**
2258      * No grades on the front page.
2259      * @return bool
2260      */
2261     protected function execute_condition() {
2262         return ($this->get_courseid() != SITEID);
2263     }
2265     protected function define_structure() {
2267         // To know if we are including userinfo
2268         $userinfo = $this->get_setting_value('userinfo');
2270         // Define each element separated
2272         $book = new backup_nested_element('activity_gradebook');
2274         $items = new backup_nested_element('grade_items');
2276         $item = new backup_nested_element('grade_item', array('id'), array(
2277             'categoryid', 'itemname', 'itemtype', 'itemmodule',
2278             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
2279             'calculation', 'gradetype', 'grademax', 'grademin',
2280             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
2281             'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
2282             'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
2283             'needsupdate', 'timecreated', 'timemodified'));
2285         $grades = new backup_nested_element('grade_grades');
2287         $grade = new backup_nested_element('grade_grade', array('id'), array(
2288             'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
2289             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
2290             'locked', 'locktime', 'exported', 'overridden',
2291             'excluded', 'feedback', 'feedbackformat', 'information',
2292             'informationformat', 'timecreated', 'timemodified',
2293             'aggregationstatus', 'aggregationweight'));
2295         $letters = new backup_nested_element('grade_letters');
2297         $letter = new backup_nested_element('grade_letter', 'id', array(
2298             'lowerboundary', 'letter'));
2300         // Build the tree
2302         $book->add_child($items);
2303         $items->add_child($item);
2305         $item->add_child($grades);
2306         $grades->add_child($grade);
2308         $book->add_child($letters);
2309         $letters->add_child($letter);
2311         // Define sources
2313         $item->set_source_sql("SELECT gi.*
2314                                FROM {grade_items} gi
2315                                JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
2316                                WHERE bi.backupid = ?
2317                                AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2319         // This only happens if we are including user info
2320         if ($userinfo) {
2321             $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
2322         }
2324         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
2326         // Annotations
2328         $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
2329         $item->annotate_ids('outcome', 'outcomeid');
2331         $grade->annotate_ids('user', 'userid');
2332         $grade->annotate_ids('user', 'usermodified');
2334         // Return the root element (book)
2336         return $book;
2337     }
2340 /**
2341  * Structure step in charge of constructing the grade history of an activity.
2342  *
2343  * This step is added to the task regardless of the setting 'grade_histories'.
2344  * The reason is to allow for a more flexible step in case the logic needs to be
2345  * split accross different settings to control the history of items and/or grades.
2346  */
2347 class backup_activity_grade_history_structure_step extends backup_structure_step {
2349     /**
2350      * No grades on the front page.
2351      * @return bool
2352      */
2353     protected function execute_condition() {
2354         return ($this->get_courseid() != SITEID);
2355     }
2357     protected function define_structure() {
2359         // Settings to use.
2360         $userinfo = $this->get_setting_value('userinfo');
2361         $history = $this->get_setting_value('grade_histories');
2363         // Create the nested elements.
2364         $bookhistory = new backup_nested_element('grade_history');
2365         $grades = new backup_nested_element('grade_grades');
2366         $grade = new backup_nested_element('grade_grade', array('id'), array(
2367             'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
2368             'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
2369             'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
2370             'excluded', 'feedback', 'feedbackformat', 'information',
2371             'informationformat', 'timemodified'));
2373         // Build the tree.
2374         $bookhistory->add_child($grades);
2375         $grades->add_child($grade);
2377         // This only happens if we are including user info and history.
2378         if ($userinfo && $history) {
2379             // Define sources. Only select the history related to existing activity items.
2380             $grade->set_source_sql("SELECT ggh.*
2381                                      FROM {grade_grades_history} ggh
2382                                      JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid
2383                                     WHERE bi.backupid = ?
2384                                       AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2385         }
2387         // Annotations.
2388         $grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0.
2389         $grade->annotate_ids('user', 'loggeduser');
2390         $grade->annotate_ids('user', 'userid');
2391         $grade->annotate_ids('user', 'usermodified');
2393         // Return the root element.
2394         return $bookhistory;
2395     }
2398 /**
2399  * Backups up the course completion information for the course.
2400  */
2401 class backup_course_completion_structure_step extends backup_structure_step {
2403     protected function execute_condition() {
2405         // No completion on front page.
2406         if ($this->get_courseid() == SITEID) {
2407             return false;
2408         }
2410         // Check that all activities have been included
2411         if ($this->task->is_excluding_activities()) {
2412             return false;
2413         }
2414         return true;
2415     }
2417     /**
2418      * The structure of the course completion backup
2419      *
2420      * @return backup_nested_element
2421      */
2422     protected function define_structure() {
2424         // To know if we are including user completion info
2425         $userinfo = $this->get_setting_value('userscompletion');
2427         $cc = new backup_nested_element('course_completion');
2429         $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
2430             'course', 'criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod',
2431             'timeend', 'gradepass', 'role', 'roleshortname'
2432         ));
2434         $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
2436         $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
2437             'criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted'
2438         ));
2440         $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
2441             'userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate'
2442         ));
2444         $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
2445             'course','criteriatype','method','value'
2446         ));
2448         $cc->add_child($criteria);
2449             $criteria->add_child($criteriacompletions);
2450                 $criteriacompletions->add_child($criteriacomplete);
2451         $cc->add_child($coursecompletions);
2452         $cc->add_child($aggregatemethod);
2454         // We need some extra data for the restore.
2455         // - courseinstances shortname rather than an ID.
2456         // - roleshortname in case restoring on a different site.
2457         $sourcesql = "SELECT ccc.*, c.shortname AS courseinstanceshortname, r.shortname AS roleshortname
2458                         FROM {course_completion_criteria} ccc
2459                    LEFT JOIN {course} c ON c.id = ccc.courseinstance
2460                    LEFT JOIN {role} r ON r.id = ccc.role
2461                        WHERE ccc.course = ?";
2462         $criteria->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
2464         $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
2466         if ($userinfo) {
2467             $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
2468             $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
2469         }
2471         $criteria->annotate_ids('role', 'role');
2472         $criteriacomplete->annotate_ids('user', 'userid');
2473         $coursecompletions->annotate_ids('user', 'userid');
2475         return $cc;
2477     }