MDL-32119 course completion: Removed unused database structure
[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,
32  * delete old directories and create temp ids table
33  */
34 class create_and_clean_temp_stuff extends backup_execution_step {
36     protected function define_execution() {
37         backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
38         backup_helper::clear_backup_dir($this->get_backupid());           // Empty temp dir, just in case
39         backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60));    // Delete > 4 hours temp dirs
40         backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
41     }
42 }
44 /**
45  * delete the temp dir used by backup/restore (conditionally),
46  * delete old directories and drop tem ids table. Note we delete
47  * the directory but not the corresponding log file that will be
48  * there for, at least, 4 hours - only delete_old_backup_dirs()
49  * deletes log files (for easier access to them)
50  */
51 class drop_and_clean_temp_stuff extends backup_execution_step {
53     protected $skipcleaningtempdir = false;
55     protected function define_execution() {
56         global $CFG;
58         backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
59         backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60));              // Delete > 4 hours temp dirs
60         // Delete temp dir conditionally:
61         // 1) If $CFG->keeptempdirectoriesonbackup is not enabled
62         // 2) If backup temp dir deletion has been marked to be avoided
63         if (empty($CFG->keeptempdirectoriesonbackup) && !$this->skipcleaningtempdir) {
64             backup_helper::delete_backup_dir($this->get_backupid()); // Empty backup dir
65         }
66     }
68     public function skip_cleaning_temp_dir($skip) {
69         $this->skipcleaningtempdir = $skip;
70     }
71 }
73 /**
74  * Create the directory where all the task (activity/block...) information will be stored
75  */
76 class create_taskbasepath_directory extends backup_execution_step {
78     protected function define_execution() {
79         global $CFG;
80         $basepath = $this->task->get_taskbasepath();
81         if (!check_dir_exists($basepath, true, true)) {
82             throw new backup_step_exception('cannot_create_taskbasepath_directory', $basepath);
83         }
84     }
85 }
87 /**
88  * Abstract structure step, parent of all the activity structure steps. Used to wrap the
89  * activity structure definition within the main <activity ...> tag. Also provides
90  * subplugin support for activities (that must be properly declared)
91  */
92 abstract class backup_activity_structure_step extends backup_structure_step {
94     /**
95      * Add subplugin structure to any element in the activity backup tree
96      *
97      * @param string $subplugintype type of subplugin as defined in activity db/subplugins.php
98      * @param backup_nested_element $element element in the activity backup tree that
99      *                                       we are going to add subplugin information to
100      * @param bool $multiple to define if multiple subplugins can produce information
101      *                       for each instance of $element (true) or no (false)
102      * @return void
103      */
104     protected function add_subplugin_structure($subplugintype, $element, $multiple) {
106         global $CFG;
108         // Check the requested subplugintype is a valid one
109         $subpluginsfile = $CFG->dirroot . '/mod/' . $this->task->get_modulename() . '/db/subplugins.php';
110         if (!file_exists($subpluginsfile)) {
111              throw new backup_step_exception('activity_missing_subplugins_php_file', $this->task->get_modulename());
112         }
113         include($subpluginsfile);
114         if (!array_key_exists($subplugintype, $subplugins)) {
115              throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
116         }
118         // Arrived here, subplugin is correct, let's create the optigroup
119         $optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
120         $optigroup = new backup_optigroup($optigroupname, null, $multiple);
121         $element->add_child($optigroup); // Add optigroup to stay connected since beginning
123         // Get all the optigroup_elements, looking across all the subplugin dirs
124         $subpluginsdirs = get_plugin_list($subplugintype);
125         foreach ($subpluginsdirs as $name => $subpluginsdir) {
126             $classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
127             $backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
128             if (file_exists($backupfile)) {
129                 require_once($backupfile);
130                 $backupsubplugin = new $classname($subplugintype, $name, $optigroup, $this);
131                 // Add subplugin returned structure to optigroup
132                 $backupsubplugin->define_subplugin_structure($element->get_name());
133             }
134         }
135     }
137     /**
138      * As far as activity backup steps are implementing backup_subplugin stuff, they need to
139      * have the parent task available for wrapping purposes (get course/context....)
140      *
141      * @return backup_activity_task
142      */
143     public function get_task() {
144         return $this->task;
145     }
147     /**
148      * Wraps any activity backup structure within the common 'activity' element
149      * that will include common to all activities information like id, context...
150      *
151      * @param backup_nested_element $activitystructure the element to wrap
152      * @return backup_nested_element the $activitystructure wrapped by the common 'activity' element
153      */
154     protected function prepare_activity_structure($activitystructure) {
156         // Create the wrap element
157         $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);
159         // Build the tree
160         $activity->add_child($activitystructure);
162         // Set the source
163         $activityarr = array((object)array(
164             'id'         => $this->task->get_activityid(),
165             'moduleid'   => $this->task->get_moduleid(),
166             'modulename' => $this->task->get_modulename(),
167             'contextid'  => $this->task->get_contextid()));
169         $activity->set_source_array($activityarr);
171         // Return the root element (activity)
172         return $activity;
173     }
176 /**
177  * Abstract structure step, to be used by all the activities using core questions stuff
178  * (namely quiz module), supporting question plugins, states and sessions
179  */
180 abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
182     /**
183      * Attach to $element (usually attempts) the needed backup structures
184      * for question_usages and all the associated data.
185      */
186     protected function add_question_usages($element, $usageidname) {
187         global $CFG;
188         require_once($CFG->dirroot . '/question/engine/lib.php');
190         // Check $element is one nested_backup_element
191         if (! $element instanceof backup_nested_element) {
192             throw new backup_step_exception('question_states_bad_parent_element', $element);
193         }
194         if (! $element->get_final_element($usageidname)) {
195             throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname);
196         }
198         $quba = new backup_nested_element('question_usage', array('id'),
199                 array('component', 'preferredbehaviour'));
201         $qas = new backup_nested_element('question_attempts');
202         $qa = new backup_nested_element('question_attempt', array('id'), array(
203                 'slot', 'behaviour', 'questionid', 'maxmark', 'minfraction',
204                 'flagged', 'questionsummary', 'rightanswer', 'responsesummary',
205                 'timemodified'));
207         $steps = new backup_nested_element('steps');
208         $step = new backup_nested_element('step', array('id'), array(
209                 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid'));
211         $response = new backup_nested_element('response');
212         $variable = new backup_nested_element('variable', null,  array('name', 'value'));
214         // Build the tree
215         $element->add_child($quba);
216         $quba->add_child($qas);
217         $qas->add_child($qa);
218         $qa->add_child($steps);
219         $steps->add_child($step);
220         $step->add_child($response);
221         $response->add_child($variable);
223         // Set the sources
224         $quba->set_source_table('question_usages',
225                 array('id'                => '../' . $usageidname));
226         $qa->set_source_sql('
227                 SELECT *
228                 FROM {question_attempts}
229                 WHERE questionusageid = :questionusageid
230                 ORDER BY slot',
231                 array('questionusageid'   => backup::VAR_PARENTID));
232         $step->set_source_sql('
233                 SELECT *
234                 FROM {question_attempt_steps}
235                 WHERE questionattemptid = :questionattemptid
236                 ORDER BY sequencenumber',
237                 array('questionattemptid' => backup::VAR_PARENTID));
238         $variable->set_source_table('question_attempt_step_data',
239                 array('attemptstepid'     => backup::VAR_PARENTID));
241         // Annotate ids
242         $qa->annotate_ids('question', 'questionid');
243         $step->annotate_ids('user', 'userid');
245         // Annotate files
246         $fileareas = question_engine::get_all_response_file_areas();
247         foreach ($fileareas as $filearea) {
248             $step->annotate_files('question', $filearea, 'id');
249         }
250     }
254 /**
255  * backup structure step in charge of calculating the categories to be
256  * included in backup, based in the context being backuped (module/course)
257  * and the already annotated questions present in backup_ids_temp
258  */
259 class backup_calculate_question_categories extends backup_execution_step {
261     protected function define_execution() {
262         backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());
263     }
266 /**
267  * backup structure step in charge of deleting all the questions annotated
268  * in the backup_ids_temp table
269  */
270 class backup_delete_temp_questions extends backup_execution_step {
272     protected function define_execution() {
273         backup_question_dbops::delete_temp_questions($this->get_backupid());
274     }
277 /**
278  * Abstract structure step, parent of all the block structure steps. Used to wrap the
279  * block structure definition within the main <block ...> tag
280  */
281 abstract class backup_block_structure_step extends backup_structure_step {
283     protected function prepare_block_structure($blockstructure) {
285         // Create the wrap element
286         $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);
288         // Build the tree
289         $block->add_child($blockstructure);
291         // Set the source
292         $blockarr = array((object)array(
293             'id'         => $this->task->get_blockid(),
294             'blockname'  => $this->task->get_blockname(),
295             'contextid'  => $this->task->get_contextid()));
297         $block->set_source_array($blockarr);
299         // Return the root element (block)
300         return $block;
301     }
304 /**
305  * structure step that will generate the module.xml file for the activity,
306  * accumulating various information about the activity, annotating groupings
307  * and completion/avail conf
308  */
309 class backup_module_structure_step extends backup_structure_step {
311     protected function define_structure() {
313         // Define each element separated
315         $module = new backup_nested_element('module', array('id', 'version'), array(
316             'modulename', 'sectionid', 'sectionnumber', 'idnumber',
317             'added', 'score', 'indent', 'visible',
318             'visibleold', 'groupmode', 'groupingid', 'groupmembersonly',
319             'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
320             'availablefrom', 'availableuntil', 'showavailability', 'showdescription'));
322         $availinfo = new backup_nested_element('availability_info');
323         $availability = new backup_nested_element('availability', array('id'), array(
324             'sourcecmid', 'requiredcompletion', 'gradeitemid', 'grademin', 'grademax'));
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         // Define the tree
334         $module->add_child($availinfo);
335         $availinfo->add_child($availability);
337         // Set the sources
339         $module->set_source_sql('
340             SELECT cm.*, m.version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
341               FROM {course_modules} cm
342               JOIN {modules} m ON m.id = cm.module
343               JOIN {course_sections} s ON s.id = cm.section
344              WHERE cm.id = ?', array(backup::VAR_MODID));
346         $availability->set_source_table('course_modules_availability', array('coursemoduleid' => backup::VAR_MODID));
348         // Define annotations
349         $module->annotate_ids('grouping', 'groupingid');
351         // Return the root element ($module)
352         return $module;
353     }
356 /**
357  * structure step that will generate the section.xml file for the section
358  * annotating files
359  */
360 class backup_section_structure_step extends backup_structure_step {
362     protected function define_structure() {
364         // Define each element separated
366         $section = new backup_nested_element('section', array('id'), array(
367                 'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible',
368                 'availablefrom', 'availableuntil', 'showavailability', 'groupingid'));
370         // attach format plugin structure to $section element, only one allowed
371         $this->add_plugin_structure('format', $section, false);
373         // Add nested elements for _availability table
374         $avail = new backup_nested_element('availability', array('id'), array(
375                 'sourcecmid', 'requiredcompletion', 'gradeitemid', 'grademin', 'grademax'));
376         $section->add_child($avail);
378         // Define sources
379         $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
380         $avail->set_source_table('course_sections_availability', array('coursesectionid' => backup::VAR_SECTIONID));
382         // Aliases
383         $section->set_source_alias('section', 'number');
385         // Set annotations
386         $section->annotate_ids('grouping', 'groupingid');
387         $section->annotate_files('course', 'section', 'id');
389         return $section;
390     }
393 /**
394  * structure step that will generate the course.xml file for the course, including
395  * course category reference, tags, modules restriction information
396  * and some annotations (files & groupings)
397  */
398 class backup_course_structure_step extends backup_structure_step {
400     protected function define_structure() {
401         global $DB;
403         // Define each element separated
405         $course = new backup_nested_element('course', array('id', 'contextid'), array(
406             'shortname', 'fullname', 'idnumber',
407             'summary', 'summaryformat', 'format', 'coursedisplay', 'showgrades',
408             'newsitems', 'startdate', 'numsections',
409             'marker', 'maxbytes', 'legacyfiles', 'showreports',
410             'visible', 'hiddensections', 'groupmode', 'groupmodeforce',
411             'defaultgroupingid', 'lang', 'theme',
412             'timecreated', 'timemodified',
413             'requested',
414             'enablecompletion', 'completionstartonenrol', 'completionnotify'));
416         $category = new backup_nested_element('category', array('id'), array(
417             'name', 'description'));
419         $tags = new backup_nested_element('tags');
421         $tag = new backup_nested_element('tag', array('id'), array(
422             'name', 'rawname'));
424         // attach format plugin structure to $course element, only one allowed
425         $this->add_plugin_structure('format', $course, false);
427         // attach theme plugin structure to $course element; multiple themes can
428         // save course data (in case of user theme, legacy theme, etc)
429         $this->add_plugin_structure('theme', $course, true);
431         // attach general report plugin structure to $course element; multiple
432         // reports can save course data if required
433         $this->add_plugin_structure('report', $course, true);
435         // attach course report plugin structure to $course element; multiple
436         // course reports can save course data if required
437         $this->add_plugin_structure('coursereport', $course, true);
439         // attach plagiarism plugin structure to $course element, there can be potentially
440         // many plagiarism plugins storing information about this course
441         $this->add_plugin_structure('plagiarism', $course, true);
443         // Build the tree
445         $course->add_child($category);
447         $course->add_child($tags);
448         $tags->add_child($tag);
450         // Set the sources
452         $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
453         $courserec->contextid = $this->task->get_contextid();
455         $course->set_source_array(array($courserec));
457         $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));
459         $category->set_source_array(array($categoryrec));
461         $tag->set_source_sql('SELECT t.id, t.name, t.rawname
462                                 FROM {tag} t
463                                 JOIN {tag_instance} ti ON ti.tagid = t.id
464                                WHERE ti.itemtype = ?
465                                  AND ti.itemid = ?', array(
466                                      backup_helper::is_sqlparam('course'),
467                                      backup::VAR_PARENTID));
469         // Some annotations
471         $course->annotate_ids('grouping', 'defaultgroupingid');
473         $course->annotate_files('course', 'summary', null);
474         $course->annotate_files('course', 'legacy', null);
476         // Return root element ($course)
478         return $course;
479     }
482 /**
483  * structure step that will generate the enrolments.xml file for the given course
484  */
485 class backup_enrolments_structure_step extends backup_structure_step {
487     protected function define_structure() {
489         // To know if we are including users
490         $users = $this->get_setting_value('users');
492         // Define each element separated
494         $enrolments = new backup_nested_element('enrolments');
496         $enrols = new backup_nested_element('enrols');
498         $enrol = new backup_nested_element('enrol', array('id'), array(
499             'enrol', 'status', 'sortorder', 'name', 'enrolperiod', 'enrolstartdate',
500             'enrolenddate', 'expirynotify', 'expirytreshold', 'notifyall',
501             'password', 'cost', 'currency', 'roleid', 'customint1', 'customint2', 'customint3',
502             'customint4', 'customchar1', 'customchar2', 'customdec1', 'customdec2',
503             'customtext1', 'customtext2', 'timecreated', 'timemodified'));
505         $userenrolments = new backup_nested_element('user_enrolments');
507         $enrolment = new backup_nested_element('enrolment', array('id'), array(
508             'status', 'userid', 'timestart', 'timeend', 'modifierid',
509             'timemodified'));
511         // Build the tree
512         $enrolments->add_child($enrols);
513         $enrols->add_child($enrol);
514         $enrol->add_child($userenrolments);
515         $userenrolments->add_child($enrolment);
517         // Define sources
519         $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID));
521         // User enrolments only added only if users included
522         if ($users) {
523             $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));
524             $enrolment->annotate_ids('user', 'userid');
525         }
527         $enrol->annotate_ids('role', 'roleid');
529         //TODO: let plugins annotate custom fields too and add more children
531         return $enrolments;
532     }
535 /**
536  * structure step that will generate the roles.xml file for the given context, observing
537  * the role_assignments setting to know if that part needs to be included
538  */
539 class backup_roles_structure_step extends backup_structure_step {
541     protected function define_structure() {
543         // To know if we are including role assignments
544         $roleassignments = $this->get_setting_value('role_assignments');
546         // Define each element separated
548         $roles = new backup_nested_element('roles');
550         $overrides = new backup_nested_element('role_overrides');
552         $override = new backup_nested_element('override', array('id'), array(
553             'roleid', 'capability', 'permission', 'timemodified',
554             'modifierid'));
556         $assignments = new backup_nested_element('role_assignments');
558         $assignment = new backup_nested_element('assignment', array('id'), array(
559             'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid',
560             'sortorder'));
562         // Build the tree
563         $roles->add_child($overrides);
564         $roles->add_child($assignments);
566         $overrides->add_child($override);
567         $assignments->add_child($assignment);
569         // Define sources
571         $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));
573         // Assignments only added if specified
574         if ($roleassignments) {
575             $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));
576         }
578         // Define id annotations
579         $override->annotate_ids('role', 'roleid');
581         $assignment->annotate_ids('role', 'roleid');
583         $assignment->annotate_ids('user', 'userid');
585         //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)
587         return $roles;
588     }
591 /**
592  * structure step that will generate the roles.xml containing the
593  * list of roles used along the whole backup process. Just raw
594  * list of used roles from role table
595  */
596 class backup_final_roles_structure_step extends backup_structure_step {
598     protected function define_structure() {
600         // Define elements
602         $rolesdef = new backup_nested_element('roles_definition');
604         $role = new backup_nested_element('role', array('id'), array(
605             'name', 'shortname', 'nameincourse', 'description',
606             'sortorder', 'archetype'));
608         // Build the tree
610         $rolesdef->add_child($role);
612         // Define sources
614         $role->set_source_sql("SELECT r.*, rn.name AS nameincourse
615                                  FROM {role} r
616                                  JOIN {backup_ids_temp} bi ON r.id = bi.itemid
617                             LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?
618                                 WHERE bi.backupid = ?
619                                   AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));
621         // Return main element (rolesdef)
622         return $rolesdef;
623     }
626 /**
627  * structure step that will generate the scales.xml containing the
628  * list of scales used along the whole backup process.
629  */
630 class backup_final_scales_structure_step extends backup_structure_step {
632     protected function define_structure() {
634         // Define elements
636         $scalesdef = new backup_nested_element('scales_definition');
638         $scale = new backup_nested_element('scale', array('id'), array(
639             'courseid', 'userid', 'name', 'scale',
640             'description', 'descriptionformat', 'timemodified'));
642         // Build the tree
644         $scalesdef->add_child($scale);
646         // Define sources
648         $scale->set_source_sql("SELECT s.*
649                                   FROM {scale} s
650                                   JOIN {backup_ids_temp} bi ON s.id = bi.itemid
651                                  WHERE bi.backupid = ?
652                                    AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));
654         // Annotate scale files (they store files in system context, so pass it instead of default one)
655         $scale->annotate_files('grade', 'scale', 'id', get_context_instance(CONTEXT_SYSTEM)->id);
657         // Return main element (scalesdef)
658         return $scalesdef;
659     }
662 /**
663  * structure step that will generate the outcomes.xml containing the
664  * list of outcomes used along the whole backup process.
665  */
666 class backup_final_outcomes_structure_step extends backup_structure_step {
668     protected function define_structure() {
670         // Define elements
672         $outcomesdef = new backup_nested_element('outcomes_definition');
674         $outcome = new backup_nested_element('outcome', array('id'), array(
675             'courseid', 'userid', 'shortname', 'fullname',
676             'scaleid', 'description', 'descriptionformat', 'timecreated',
677             'timemodified','usermodified'));
679         // Build the tree
681         $outcomesdef->add_child($outcome);
683         // Define sources
685         $outcome->set_source_sql("SELECT o.*
686                                     FROM {grade_outcomes} o
687                                     JOIN {backup_ids_temp} bi ON o.id = bi.itemid
688                                    WHERE bi.backupid = ?
689                                      AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));
691         // Annotate outcome files (they store files in system context, so pass it instead of default one)
692         $outcome->annotate_files('grade', 'outcome', 'id', get_context_instance(CONTEXT_SYSTEM)->id);
694         // Return main element (outcomesdef)
695         return $outcomesdef;
696     }
699 /**
700  * structure step in charge of constructing the filters.xml file for all the filters found
701  * in activity
702  */
703 class backup_filters_structure_step extends backup_structure_step {
705     protected function define_structure() {
707         // Define each element separated
709         $filters = new backup_nested_element('filters');
711         $actives = new backup_nested_element('filter_actives');
713         $active = new backup_nested_element('filter_active', null, array('filter', 'active'));
715         $configs = new backup_nested_element('filter_configs');
717         $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));
719         // Build the tree
721         $filters->add_child($actives);
722         $filters->add_child($configs);
724         $actives->add_child($active);
725         $configs->add_child($config);
727         // Define sources
729         list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());
731         $active->set_source_array($activearr);
732         $config->set_source_array($configarr);
734         // Return the root element (filters)
735         return $filters;
736     }
739 /**
740  * structure step in charge of constructing the comments.xml file for all the comments found
741  * in a given context
742  */
743 class backup_comments_structure_step extends backup_structure_step {
745     protected function define_structure() {
747         // Define each element separated
749         $comments = new backup_nested_element('comments');
751         $comment = new backup_nested_element('comment', array('id'), array(
752             'commentarea', 'itemid', 'content', 'format',
753             'userid', 'timecreated'));
755         // Build the tree
757         $comments->add_child($comment);
759         // Define sources
761         $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));
763         // Define id annotations
765         $comment->annotate_ids('user', 'userid');
767         // Return the root element (comments)
768         return $comments;
769     }
772 /**
773  * structure step in charge of constructing the calender.xml file for all the events found
774  * in a given context
775  */
776 class backup_calendarevents_structure_step extends backup_structure_step {
778     protected function define_structure() {
780         // Define each element separated
782         $events = new backup_nested_element('events');
784         $event = new backup_nested_element('event', array('id'), array(
785                 'name', 'description', 'format', 'courseid', 'groupid', 'userid',
786                 'repeatid', 'modulename', 'instance', 'eventtype', 'timestart',
787                 'timeduration', 'visible', 'uuid', 'sequence', 'timemodified'));
789         // Build the tree
790         $events->add_child($event);
792         // Define sources
793         if ($this->name == 'course_calendar') {
794             $calendar_items_sql ="SELECT * FROM {event}
795                         WHERE courseid = :courseid
796                         AND (eventtype = 'course' OR eventtype = 'group')";
797             $calendar_items_params = array('courseid'=>backup::VAR_COURSEID);
798             $event->set_source_sql($calendar_items_sql, $calendar_items_params);
799         } else {
800             $event->set_source_table('event', array('courseid' => backup::VAR_COURSEID, 'instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME));
801         }
803         // Define id annotations
805         $event->annotate_ids('user', 'userid');
806         $event->annotate_ids('group', 'groupid');
807         $event->annotate_files('calendar', 'event_description', 'id');
809         // Return the root element (events)
810         return $events;
811     }
814 /**
815  * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course
816  * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step
817  */
818 class backup_gradebook_structure_step extends backup_structure_step {
820     /**
821      * We need to decide conditionally, based on dynamic information
822      * about the execution of this step. Only will be executed if all
823      * the module gradeitems have been already included in backup
824      */
825     protected function execute_condition() {
826         return backup_plan_dbops::require_gradebook_backup($this->get_courseid(), $this->get_backupid());
827     }
829     protected function define_structure() {
831         // are we including user info?
832         $userinfo = $this->get_setting_value('users');
834         $gradebook = new backup_nested_element('gradebook');
836         //grade_letters are done in backup_activity_grades_structure_step()
838         //calculated grade items
839         $grade_items = new backup_nested_element('grade_items');
840         $grade_item = new backup_nested_element('grade_item', array('id'), array(
841             'categoryid', 'itemname', 'itemtype', 'itemmodule',
842             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
843             'calculation', 'gradetype', 'grademax', 'grademin',
844             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
845             'plusfactor', 'aggregationcoef', 'sortorder', 'display',
846             'decimals', 'hidden', 'locked', 'locktime',
847             'needsupdate', 'timecreated', 'timemodified'));
849         $grade_grades = new backup_nested_element('grade_grades');
850         $grade_grade = new backup_nested_element('grade_grade', array('id'), array(
851             'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
852             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
853             'locked', 'locktime', 'exported', 'overridden',
854             'excluded', 'feedback', 'feedbackformat', 'information',
855             'informationformat', 'timecreated', 'timemodified'));
857         //grade_categories
858         $grade_categories = new backup_nested_element('grade_categories');
859         $grade_category   = new backup_nested_element('grade_category', array('id'), array(
860                 //'courseid',
861                 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
862                 'dropload', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
863                 'timecreated', 'timemodified', 'hidden'));
865         $letters = new backup_nested_element('grade_letters');
866         $letter = new backup_nested_element('grade_letter', 'id', array(
867             'lowerboundary', 'letter'));
869         $grade_settings = new backup_nested_element('grade_settings');
870         $grade_setting = new backup_nested_element('grade_setting', 'id', array(
871             'name', 'value'));
874         // Build the tree
875         $gradebook->add_child($grade_categories);
876         $grade_categories->add_child($grade_category);
878         $gradebook->add_child($grade_items);
879         $grade_items->add_child($grade_item);
880         $grade_item->add_child($grade_grades);
881         $grade_grades->add_child($grade_grade);
883         $gradebook->add_child($letters);
884         $letters->add_child($letter);
886         $gradebook->add_child($grade_settings);
887         $grade_settings->add_child($grade_setting);
889         // Define sources
891         //Include manual, category and the course grade item
892         $grade_items_sql ="SELECT * FROM {grade_items}
893                            WHERE courseid = :courseid
894                            AND (itemtype='manual' OR itemtype='course' OR itemtype='category')";
895         $grade_items_params = array('courseid'=>backup::VAR_COURSEID);
896         $grade_item->set_source_sql($grade_items_sql, $grade_items_params);
898         if ($userinfo) {
899             $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
900         }
902         $grade_category_sql = "SELECT gc.*, gi.sortorder
903                                FROM {grade_categories} gc
904                                JOIN {grade_items} gi ON (gi.iteminstance = gc.id)
905                                WHERE gc.courseid = :courseid
906                                AND (gi.itemtype='course' OR gi.itemtype='category')
907                                ORDER BY gc.parent ASC";//need parent categories before their children
908         $grade_category_params = array('courseid'=>backup::VAR_COURSEID);
909         $grade_category->set_source_sql($grade_category_sql, $grade_category_params);
911         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
913         $grade_setting->set_source_table('grade_settings', array('courseid' => backup::VAR_COURSEID));
915         // Annotations (both as final as far as they are going to be exported in next steps)
916         $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
917         $grade_item->annotate_ids('outcomefinal', 'outcomeid');
919         //just in case there are any users not already annotated by the activities
920         $grade_grade->annotate_ids('userfinal', 'userid');
922         // Return the root element
923         return $gradebook;
924     }
927 /**
928  * structure step in charge if constructing the completion.xml file for all the users completion
929  * information in a given activity
930  */
931 class backup_userscompletion_structure_step extends backup_structure_step {
933     protected function define_structure() {
935         // Define each element separated
937         $completions = new backup_nested_element('completions');
939         $completion = new backup_nested_element('completion', array('id'), array(
940             'userid', 'completionstate', 'viewed', 'timemodified'));
942         // Build the tree
944         $completions->add_child($completion);
946         // Define sources
948         $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
950         // Define id annotations
952         $completion->annotate_ids('user', 'userid');
954         // Return the root element (completions)
955         return $completions;
956     }
959 /**
960  * structure step in charge of constructing the main groups.xml file for all the groups and
961  * groupings information already annotated
962  */
963 class backup_groups_structure_step extends backup_structure_step {
965     protected function define_structure() {
967         // To know if we are including users
968         $users = $this->get_setting_value('users');
970         // Define each element separated
972         $groups = new backup_nested_element('groups');
974         $group = new backup_nested_element('group', array('id'), array(
975             'name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey',
976             'picture', 'hidepicture', 'timecreated', 'timemodified'));
978         $members = new backup_nested_element('group_members');
980         $member = new backup_nested_element('group_member', array('id'), array(
981             'userid', 'timeadded'));
983         $groupings = new backup_nested_element('groupings');
985         $grouping = new backup_nested_element('grouping', 'id', array(
986             'name', 'idnumber', 'description', 'descriptionformat', 'configdata',
987             'timecreated', 'timemodified'));
989         $groupinggroups = new backup_nested_element('grouping_groups');
991         $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
992             'groupid', 'timeadded'));
994         // Build the tree
996         $groups->add_child($group);
997         $groups->add_child($groupings);
999         $group->add_child($members);
1000         $members->add_child($member);
1002         $groupings->add_child($grouping);
1003         $grouping->add_child($groupinggroups);
1004         $groupinggroups->add_child($groupinggroup);
1006         // Define sources
1008         $group->set_source_sql("
1009             SELECT g.*
1010               FROM {groups} g
1011               JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1012              WHERE bi.backupid = ?
1013                AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
1015         // This only happens if we are including users
1016         if ($users) {
1017             $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
1018         }
1020         $grouping->set_source_sql("
1021             SELECT g.*
1022               FROM {groupings} g
1023               JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1024              WHERE bi.backupid = ?
1025                AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
1027         $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
1029         // Define id annotations (as final)
1031         $member->annotate_ids('userfinal', 'userid');
1033         // Define file annotations
1035         $group->annotate_files('group', 'description', 'id');
1036         $group->annotate_files('group', 'icon', 'id');
1037         $grouping->annotate_files('grouping', 'description', 'id');
1039         // Return the root element (groups)
1040         return $groups;
1041     }
1044 /**
1045  * structure step in charge of constructing the main users.xml file for all the users already
1046  * annotated (final). Includes custom profile fields, preferences, tags, role assignments and
1047  * overrides.
1048  */
1049 class backup_users_structure_step extends backup_structure_step {
1051     protected function define_structure() {
1052         global $CFG;
1054         // To know if we are anonymizing users
1055         $anonymize = $this->get_setting_value('anonymize');
1056         // To know if we are including role assignments
1057         $roleassignments = $this->get_setting_value('role_assignments');
1059         // Define each element separated
1061         $users = new backup_nested_element('users');
1063         // Create the array of user fields by hand, as far as we have various bits to control
1064         // anonymize option, password backup, mnethostid...
1066         // First, the fields not needing anonymization nor special handling
1067         $normalfields = array(
1068             'confirmed', 'policyagreed', 'deleted',
1069             'lang', 'theme', 'timezone', 'firstaccess',
1070             'lastaccess', 'lastlogin', 'currentlogin',
1071             'mailformat', 'maildigest', 'maildisplay', 'htmleditor',
1072             'ajax', 'autosubscribe', 'trackforums', 'timecreated',
1073             'timemodified', 'trustbitmask', 'screenreader');
1075         // Then, the fields potentially needing anonymization
1076         $anonfields = array(
1077             'username', 'idnumber', 'firstname', 'lastname',
1078             'email', 'icq', 'skype',
1079             'yahoo', 'aim', 'msn', 'phone1',
1080             'phone2', 'institution', 'department', 'address',
1081             'city', 'country', 'lastip', 'picture',
1082             'url', 'description', 'descriptionformat', 'imagealt', 'auth');
1084         // Add anonymized fields to $userfields with custom final element
1085         foreach ($anonfields as $field) {
1086             if ($anonymize) {
1087                 $userfields[] = new anonymizer_final_element($field);
1088             } else {
1089                 $userfields[] = $field; // No anonymization, normally added
1090             }
1091         }
1093         // mnethosturl requires special handling (custom final element)
1094         $userfields[] = new mnethosturl_final_element('mnethosturl');
1096         // password added conditionally
1097         if (!empty($CFG->includeuserpasswordsinbackup)) {
1098             $userfields[] = 'password';
1099         }
1101         // Merge all the fields
1102         $userfields = array_merge($userfields, $normalfields);
1104         $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1106         $customfields = new backup_nested_element('custom_fields');
1108         $customfield = new backup_nested_element('custom_field', array('id'), array(
1109             'field_name', 'field_type', 'field_data'));
1111         $tags = new backup_nested_element('tags');
1113         $tag = new backup_nested_element('tag', array('id'), array(
1114             'name', 'rawname'));
1116         $preferences = new backup_nested_element('preferences');
1118         $preference = new backup_nested_element('preference', array('id'), array(
1119             'name', 'value'));
1121         $roles = new backup_nested_element('roles');
1123         $overrides = new backup_nested_element('role_overrides');
1125         $override = new backup_nested_element('override', array('id'), array(
1126             'roleid', 'capability', 'permission', 'timemodified',
1127             'modifierid'));
1129         $assignments = new backup_nested_element('role_assignments');
1131         $assignment = new backup_nested_element('assignment', array('id'), array(
1132             'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
1133             'sortorder'));
1135         // Build the tree
1137         $users->add_child($user);
1139         $user->add_child($customfields);
1140         $customfields->add_child($customfield);
1142         $user->add_child($tags);
1143         $tags->add_child($tag);
1145         $user->add_child($preferences);
1146         $preferences->add_child($preference);
1148         $user->add_child($roles);
1150         $roles->add_child($overrides);
1151         $roles->add_child($assignments);
1153         $overrides->add_child($override);
1154         $assignments->add_child($assignment);
1156         // Define sources
1158         $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
1159                                  FROM {user} u
1160                                  JOIN {backup_ids_temp} bi ON bi.itemid = u.id
1161                             LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . '
1162                             LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
1163                                 WHERE bi.backupid = ?
1164                                   AND bi.itemname = ?', array(
1165                                       backup_helper::is_sqlparam($this->get_backupid()),
1166                                       backup_helper::is_sqlparam('userfinal')));
1168         // All the rest on information is only added if we arent
1169         // in an anonymized backup
1170         if (!$anonymize) {
1171             $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data
1172                                             FROM {user_info_field} f
1173                                             JOIN {user_info_data} d ON d.fieldid = f.id
1174                                            WHERE d.userid = ?', array(backup::VAR_PARENTID));
1176             $customfield->set_source_alias('shortname', 'field_name');
1177             $customfield->set_source_alias('datatype',  'field_type');
1178             $customfield->set_source_alias('data',      'field_data');
1180             $tag->set_source_sql('SELECT t.id, t.name, t.rawname
1181                                     FROM {tag} t
1182                                     JOIN {tag_instance} ti ON ti.tagid = t.id
1183                                    WHERE ti.itemtype = ?
1184                                      AND ti.itemid = ?', array(
1185                                          backup_helper::is_sqlparam('user'),
1186                                          backup::VAR_PARENTID));
1188             $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1190             $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1192             // Assignments only added if specified
1193             if ($roleassignments) {
1194                 $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1195             }
1197             // Define id annotations (as final)
1198             $override->annotate_ids('rolefinal', 'roleid');
1199         }
1201         // Return root element (users)
1202         return $users;
1203     }
1206 /**
1207  * structure step in charge of constructing the block.xml file for one
1208  * given block (instance and positions). If the block has custom DB structure
1209  * that will go to a separate file (different step defined in block class)
1210  */
1211 class backup_block_instance_structure_step extends backup_structure_step {
1213     protected function define_structure() {
1214         global $DB;
1216         // Define each element separated
1218         $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
1219             'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1220             'subpagepattern', 'defaultregion', 'defaultweight', 'configdata'));
1222         $positions = new backup_nested_element('block_positions');
1224         $position = new backup_nested_element('block_position', array('id'), array(
1225             'contextid', 'pagetype', 'subpage', 'visible',
1226             'region', 'weight'));
1228         // Build the tree
1230         $block->add_child($positions);
1231         $positions->add_child($position);
1233         // Transform configdata information if needed (process links and friends)
1234         $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
1235         if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
1236             $configdata = (array)unserialize(base64_decode($blockrec->configdata));
1237             foreach ($configdata as $attribute => $value) {
1238                 if (in_array($attribute, $attrstotransform)) {
1239                     $configdata[$attribute] = $this->contenttransformer->process($value);
1240                 }
1241             }
1242             $blockrec->configdata = base64_encode(serialize((object)$configdata));
1243         }
1244         $blockrec->contextid = $this->task->get_contextid();
1245         // Get the version of the block
1246         $blockrec->version = $DB->get_field('block', 'version', array('name' => $this->task->get_blockname()));
1248         // Define sources
1250         $block->set_source_array(array($blockrec));
1252         $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
1254         // File anotations (for fileareas specified on each block)
1255         foreach ($this->task->get_fileareas() as $filearea) {
1256             $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);
1257         }
1259         // Return the root element (block)
1260         return $block;
1261     }
1264 /**
1265  * structure step in charge of constructing the logs.xml file for all the log records found
1266  * in course. Note that we are sending to backup ALL the log records having cmid = 0. That
1267  * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do
1268  * that just in case they become restored some day in the future
1269  */
1270 class backup_course_logs_structure_step extends backup_structure_step {
1272     protected function define_structure() {
1274         // Define each element separated
1276         $logs = new backup_nested_element('logs');
1278         $log = new backup_nested_element('log', array('id'), array(
1279             'time', 'userid', 'ip', 'module',
1280             'action', 'url', 'info'));
1282         // Build the tree
1284         $logs->add_child($log);
1286         // Define sources (all the records belonging to the course, having cmid = 0)
1288         $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0)));
1290         // Annotations
1291         // NOTE: We don't annotate users from logs as far as they MUST be
1292         //       always annotated by the course (enrol, ras... whatever)
1294         // Return the root element (logs)
1296         return $logs;
1297     }
1300 /**
1301  * structure step in charge of constructing the logs.xml file for all the log records found
1302  * in activity
1303  */
1304 class backup_activity_logs_structure_step extends backup_structure_step {
1306     protected function define_structure() {
1308         // Define each element separated
1310         $logs = new backup_nested_element('logs');
1312         $log = new backup_nested_element('log', array('id'), array(
1313             'time', 'userid', 'ip', 'module',
1314             'action', 'url', 'info'));
1316         // Build the tree
1318         $logs->add_child($log);
1320         // Define sources
1322         $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1324         // Annotations
1325         // NOTE: We don't annotate users from logs as far as they MUST be
1326         //       always annotated by the activity (true participants).
1328         // Return the root element (logs)
1330         return $logs;
1331     }
1334 /**
1335  * structure in charge of constructing the inforef.xml file for all the items we want
1336  * to have referenced there (users, roles, files...)
1337  */
1338 class backup_inforef_structure_step extends backup_structure_step {
1340     protected function define_structure() {
1342         // Items we want to include in the inforef file.
1343         $items = backup_helper::get_inforef_itemnames();
1345         // Build the tree
1347         $inforef = new backup_nested_element('inforef');
1349         // For each item, conditionally, if there are already records, build element
1350         foreach ($items as $itemname) {
1351             if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {
1352                 $elementroot = new backup_nested_element($itemname . 'ref');
1353                 $element = new backup_nested_element($itemname, array(), array('id'));
1354                 $inforef->add_child($elementroot);
1355                 $elementroot->add_child($element);
1356                 $element->set_source_sql("
1357                     SELECT itemid AS id
1358                      FROM {backup_ids_temp}
1359                     WHERE backupid = ?
1360                       AND itemname = ?",
1361                    array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
1362             }
1363         }
1365         // We don't annotate anything there, but rely in the next step
1366         // (move_inforef_annotations_to_final) that will change all the
1367         // already saved 'inforref' entries to their 'final' annotations.
1368         return $inforef;
1369     }
1372 /**
1373  * This step will get all the annotations already processed to inforef.xml file and
1374  * transform them into 'final' annotations.
1375  */
1376 class move_inforef_annotations_to_final extends backup_execution_step {
1378     protected function define_execution() {
1380         // Items we want to include in the inforef file
1381         $items = backup_helper::get_inforef_itemnames();
1382         foreach ($items as $itemname) {
1383             // Delegate to dbops
1384             backup_structure_dbops::move_annotations_to_final($this->get_backupid(), $itemname);
1385         }
1386     }
1389 /**
1390  * structure in charge of constructing the files.xml file with all the
1391  * annotated (final) files along the process. At, the same time, and
1392  * using one specialised nested_element, will copy them form moodle storage
1393  * to backup storage
1394  */
1395 class backup_final_files_structure_step extends backup_structure_step {
1397     protected function define_structure() {
1399         // Define elements
1401         $files = new backup_nested_element('files');
1403         $file = new file_nested_element('file', array('id'), array(
1404             'contenthash', 'contextid', 'component', 'filearea', 'itemid',
1405             'filepath', 'filename', 'userid', 'filesize',
1406             'mimetype', 'status', 'timecreated', 'timemodified',
1407             'source', 'author', 'license', 'sortorder', 'reference', 'repositoryid'));
1409         // Build the tree
1411         $files->add_child($file);
1413         // Define sources
1415         $file->set_source_sql("SELECT f.*, r.repositoryid, r.reference
1416                                  FROM {files} f
1417                                  JOIN {files_reference} r
1418                                       ON r.id = f.referencefileid
1419                                  JOIN {backup_ids_temp} bi
1420                                       ON f.id = bi.itemid
1421                                 WHERE bi.backupid = ?
1422                                   AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
1424         return $files;
1425     }
1428 /**
1429  * Structure step in charge of creating the main moodle_backup.xml file
1430  * where all the information related to the backup, settings, license and
1431  * other information needed on restore is added*/
1432 class backup_main_structure_step extends backup_structure_step {
1434     protected function define_structure() {
1436         global $CFG;
1438         $info = array();
1440         $info['name'] = $this->get_setting_value('filename');
1441         $info['moodle_version'] = $CFG->version;
1442         $info['moodle_release'] = $CFG->release;
1443         $info['backup_version'] = $CFG->backup_version;
1444         $info['backup_release'] = $CFG->backup_release;
1445         $info['backup_date']    = time();
1446         $info['backup_uniqueid']= $this->get_backupid();
1447         $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
1448         $info['include_file_references_to_external_content'] =
1449                 backup_controller_dbops::backup_includes_file_references($this->get_backupid());
1450         $info['original_wwwroot']=$CFG->wwwroot;
1451         $info['original_site_identifier_hash'] = md5(get_site_identifier());
1452         $info['original_course_id'] = $this->get_courseid();
1453         $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
1454         $info['original_course_fullname']  = $originalcourseinfo->fullname;
1455         $info['original_course_shortname'] = $originalcourseinfo->shortname;
1456         $info['original_course_startdate'] = $originalcourseinfo->startdate;
1457         $info['original_course_contextid'] = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
1458         $info['original_system_contextid'] = get_context_instance(CONTEXT_SYSTEM)->id;
1460         // Get more information from controller
1461         list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($this->get_backupid());
1463         // Define elements
1465         $moodle_backup = new backup_nested_element('moodle_backup');
1467         $information = new backup_nested_element('information', null, array(
1468             'name', 'moodle_version', 'moodle_release', 'backup_version',
1469             'backup_release', 'backup_date', 'mnet_remoteusers', 'include_file_references_to_external_content', 'original_wwwroot',
1470             'original_site_identifier_hash', 'original_course_id',
1471             'original_course_fullname', 'original_course_shortname', 'original_course_startdate',
1472             'original_course_contextid', 'original_system_contextid'));
1474         $details = new backup_nested_element('details');
1476         $detail = new backup_nested_element('detail', array('backup_id'), array(
1477             'type', 'format', 'interactive', 'mode',
1478             'execution', 'executiontime'));
1480         $contents = new backup_nested_element('contents');
1482         $activities = new backup_nested_element('activities');
1484         $activity = new backup_nested_element('activity', null, array(
1485             'moduleid', 'sectionid', 'modulename', 'title',
1486             'directory'));
1488         $sections = new backup_nested_element('sections');
1490         $section = new backup_nested_element('section', null, array(
1491             'sectionid', 'title', 'directory'));
1493         $course = new backup_nested_element('course', null, array(
1494             'courseid', 'title', 'directory'));
1496         $settings = new backup_nested_element('settings');
1498         $setting = new backup_nested_element('setting', null, array(
1499             'level', 'section', 'activity', 'name', 'value'));
1501         // Build the tree
1503         $moodle_backup->add_child($information);
1505         $information->add_child($details);
1506         $details->add_child($detail);
1508         $information->add_child($contents);
1509         if (!empty($cinfo['activities'])) {
1510             $contents->add_child($activities);
1511             $activities->add_child($activity);
1512         }
1513         if (!empty($cinfo['sections'])) {
1514             $contents->add_child($sections);
1515             $sections->add_child($section);
1516         }
1517         if (!empty($cinfo['course'])) {
1518             $contents->add_child($course);
1519         }
1521         $information->add_child($settings);
1522         $settings->add_child($setting);
1525         // Set the sources
1527         $information->set_source_array(array((object)$info));
1529         $detail->set_source_array($dinfo);
1531         $activity->set_source_array($cinfo['activities']);
1533         $section->set_source_array($cinfo['sections']);
1535         $course->set_source_array($cinfo['course']);
1537         $setting->set_source_array($sinfo);
1539         // Prepare some information to be sent to main moodle_backup.xml file
1540         return $moodle_backup;
1541     }
1545 /**
1546  * Execution step that will generate the final zip (.mbz) file with all the contents
1547  */
1548 class backup_zip_contents extends backup_execution_step {
1550     protected function define_execution() {
1552         // Get basepath
1553         $basepath = $this->get_basepath();
1555         // Get the list of files in directory
1556         $filestemp = get_directory_list($basepath, '', false, true, true);
1557         $files = array();
1558         foreach ($filestemp as $file) { // Add zip paths and fs paths to all them
1559             $files[$file] = $basepath . '/' . $file;
1560         }
1562         // Add the log file if exists
1563         $logfilepath = $basepath . '.log';
1564         if (file_exists($logfilepath)) {
1565              $files['moodle_backup.log'] = $logfilepath;
1566         }
1568         // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1569         $zipfile = $basepath . '/backup.mbz';
1571         // Get the zip packer
1572         $zippacker = get_file_packer('application/zip');
1574         // Zip files
1575         $zippacker->archive_to_pathname($files, $zipfile);
1576     }
1579 /**
1580  * This step will send the generated backup file to its final destination
1581  */
1582 class backup_store_backup_file extends backup_execution_step {
1584     protected function define_execution() {
1586         // Get basepath
1587         $basepath = $this->get_basepath();
1589         // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1590         $zipfile = $basepath . '/backup.mbz';
1592         $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid());
1593         // Perform storage and return it (TODO: shouldn't be array but proper result object)
1594         return array(
1595             'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile),
1596             'include_file_references_to_external_content' => $has_file_references
1597         );
1598     }
1602 /**
1603  * This step will search for all the activity (not calculations, categories nor aggregations) grade items
1604  * and put them to the backup_ids tables, to be used later as base to backup them
1605  */
1606 class backup_activity_grade_items_to_ids extends backup_execution_step {
1608     protected function define_execution() {
1610         // Fetch all activity grade items
1611         if ($items = grade_item::fetch_all(array(
1612                          'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),
1613                          'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {
1614             // Annotate them in backup_ids
1615             foreach ($items as $item) {
1616                 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);
1617             }
1618         }
1619     }
1622 /**
1623  * This step will annotate all the groups and groupings belonging to the course
1624  */
1625 class backup_annotate_course_groups_and_groupings extends backup_execution_step {
1627     protected function define_execution() {
1628         global $DB;
1630         // Get all the course groups
1631         if ($groups = $DB->get_records('groups', array(
1632                 'courseid' => $this->task->get_courseid()))) {
1633             foreach ($groups as $group) {
1634                 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id);
1635             }
1636         }
1638         // Get all the course groupings
1639         if ($groupings = $DB->get_records('groupings', array(
1640                 'courseid' => $this->task->get_courseid()))) {
1641             foreach ($groupings as $grouping) {
1642                 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id);
1643             }
1644         }
1645     }
1648 /**
1649  * This step will annotate all the groups belonging to already annotated groupings
1650  */
1651 class backup_annotate_groups_from_groupings extends backup_execution_step {
1653     protected function define_execution() {
1654         global $DB;
1656         // Fetch all the annotated groupings
1657         if ($groupings = $DB->get_records('backup_ids_temp', array(
1658                 'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
1659             foreach ($groupings as $grouping) {
1660                 if ($groups = $DB->get_records('groupings_groups', array(
1661                         'groupingid' => $grouping->itemid))) {
1662                     foreach ($groups as $group) {
1663                         backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
1664                     }
1665                 }
1666             }
1667         }
1668     }
1671 /**
1672  * This step will annotate all the scales belonging to already annotated outcomes
1673  */
1674 class backup_annotate_scales_from_outcomes extends backup_execution_step {
1676     protected function define_execution() {
1677         global $DB;
1679         // Fetch all the annotated outcomes
1680         if ($outcomes = $DB->get_records('backup_ids_temp', array(
1681                 'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
1682             foreach ($outcomes as $outcome) {
1683                 if ($scale = $DB->get_record('grade_outcomes', array(
1684                         'id' => $outcome->itemid))) {
1685                     // Annotate as scalefinal because it's > 0
1686                     backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
1687                 }
1688             }
1689         }
1690     }
1693 /**
1694  * This step will generate all the file annotations for the already
1695  * annotated (final) question_categories. It calculates the different
1696  * contexts that are being backup and, annotates all the files
1697  * on every context belonging to the "question" component. As far as
1698  * we are always including *complete* question banks it is safe and
1699  * optimal to do that in this (one pass) way
1700  */
1701 class backup_annotate_all_question_files extends backup_execution_step {
1703     protected function define_execution() {
1704         global $DB;
1706         // Get all the different contexts for the final question_categories
1707         // annotated along the whole backup
1708         $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
1709                                         FROM {question_categories} qc
1710                                         JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
1711                                        WHERE bi.backupid = ?
1712                                          AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
1713         // To know about qtype specific components/fileareas
1714         $components = backup_qtype_plugin::get_components_and_fileareas();
1715         // Let's loop
1716         foreach($rs as $record) {
1717             // We don't need to specify filearea nor itemid as far as by
1718             // component and context it's enough to annotate the whole bank files
1719             // This backups "questiontext", "generalfeedback" and "answerfeedback" fileareas (all them
1720             // belonging to the "question" component
1721             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', null, null);
1722             // Again, it is enough to pick files only by context and component
1723             // Do it for qtype specific components
1724             foreach ($components as $component => $fileareas) {
1725                 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null);
1726             }
1727         }
1728         $rs->close();
1729     }
1732 /**
1733  * structure step in charge of constructing the questions.xml file for all the
1734  * question categories and questions required by the backup
1735  * and letters related to one activity
1736  */
1737 class backup_questions_structure_step extends backup_structure_step {
1739     protected function define_structure() {
1741         // Define each element separated
1743         $qcategories = new backup_nested_element('question_categories');
1745         $qcategory = new backup_nested_element('question_category', array('id'), array(
1746             'name', 'contextid', 'contextlevel', 'contextinstanceid',
1747             'info', 'infoformat', 'stamp', 'parent',
1748             'sortorder'));
1750         $questions = new backup_nested_element('questions');
1752         $question = new backup_nested_element('question', array('id'), array(
1753             'parent', 'name', 'questiontext', 'questiontextformat',
1754             'generalfeedback', 'generalfeedbackformat', 'defaultmark', 'penalty',
1755             'qtype', 'length', 'stamp', 'version',
1756             'hidden', 'timecreated', 'timemodified', 'createdby', 'modifiedby'));
1758         // attach qtype plugin structure to $question element, only one allowed
1759         $this->add_plugin_structure('qtype', $question, false);
1761         $qhints = new backup_nested_element('question_hints');
1763         $qhint = new backup_nested_element('question_hint', array('id'), array(
1764             'hint', 'hintformat', 'shownumcorrect', 'clearwrong', 'options'));
1766         // Build the tree
1768         $qcategories->add_child($qcategory);
1769         $qcategory->add_child($questions);
1770         $questions->add_child($question);
1771         $question->add_child($qhints);
1772         $qhints->add_child($qhint);
1774         // Define the sources
1776         $qcategory->set_source_sql("
1777             SELECT gc.*, contextlevel, instanceid AS contextinstanceid
1778               FROM {question_categories} gc
1779               JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
1780               JOIN {context} co ON co.id = gc.contextid
1781              WHERE bi.backupid = ?
1782                AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID));
1784         $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
1786         $qhint->set_source_sql('
1787                 SELECT *
1788                 FROM {question_hints}
1789                 WHERE questionid = :questionid
1790                 ORDER BY id',
1791                 array('questionid' => backup::VAR_PARENTID));
1793         // don't need to annotate ids nor files
1794         // (already done by {@link backup_annotate_all_question_files}
1796         return $qcategories;
1797     }
1802 /**
1803  * This step will generate all the file  annotations for the already
1804  * annotated (final) users. Need to do this here because each user
1805  * has its own context and structure tasks only are able to handle
1806  * one context. Also, this step will guarantee that every user has
1807  * its context created (req for other steps)
1808  */
1809 class backup_annotate_all_user_files extends backup_execution_step {
1811     protected function define_execution() {
1812         global $DB;
1814         // List of fileareas we are going to annotate
1815         $fileareas = array('profile', 'icon');
1817         // Fetch all annotated (final) users
1818         $rs = $DB->get_recordset('backup_ids_temp', array(
1819             'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
1820         foreach ($rs as $record) {
1821             $userid = $record->itemid;
1822             $userctx = get_context_instance(CONTEXT_USER, $userid);
1823             if (!$userctx) {
1824                 continue; // User has not context, sure it's a deleted user, so cannot have files
1825             }
1826             // Proceed with every user filearea
1827             foreach ($fileareas as $filearea) {
1828                 // We don't need to specify itemid ($userid - 5th param) as far as by
1829                 // context we can get all the associated files. See MDL-22092
1830                 backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null);
1831             }
1832         }
1833         $rs->close();
1834     }
1838 /**
1839  * Defines the backup step for advanced grading methods attached to the activity module
1840  */
1841 class backup_activity_grading_structure_step extends backup_structure_step {
1843     /**
1844      * Include the grading.xml only if the module supports advanced grading
1845      */
1846     protected function execute_condition() {
1847         return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false);
1848     }
1850     /**
1851      * Declares the gradable areas structures and data sources
1852      */
1853     protected function define_structure() {
1855         // To know if we are including userinfo
1856         $userinfo = $this->get_setting_value('userinfo');
1858         // Define the elements
1860         $areas = new backup_nested_element('areas');
1862         $area = new backup_nested_element('area', array('id'), array(
1863             'areaname', 'activemethod'));
1865         $definitions = new backup_nested_element('definitions');
1867         $definition = new backup_nested_element('definition', array('id'), array(
1868             'method', 'name', 'description', 'descriptionformat', 'status',
1869             'timecreated', 'timemodified', 'options'));
1871         $instances = new backup_nested_element('instances');
1873         $instance = new backup_nested_element('instance', array('id'), array(
1874             'raterid', 'itemid', 'rawgrade', 'status', 'feedback',
1875             'feedbackformat', 'timemodified'));
1877         // Build the tree including the method specific structures
1878         // (beware - the order of how gradingform plugins structures are attached is important)
1879         $areas->add_child($area);
1880         $area->add_child($definitions);
1881         $definitions->add_child($definition);
1882         $this->add_plugin_structure('gradingform', $definition, true);
1883         $definition->add_child($instances);
1884         $instances->add_child($instance);
1885         $this->add_plugin_structure('gradingform', $instance, false);
1887         // Define data sources
1889         $area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID,
1890             'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename())));
1892         $definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID));
1894         if ($userinfo) {
1895             $instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID));
1896         }
1898         // Annotate references
1899         $definition->annotate_files('grading', 'description', 'id');
1900         $instance->annotate_ids('user', 'raterid');
1902         // Return the root element
1903         return $areas;
1904     }
1908 /**
1909  * structure step in charge of constructing the grades.xml file for all the grade items
1910  * and letters related to one activity
1911  */
1912 class backup_activity_grades_structure_step extends backup_structure_step {
1914     protected function define_structure() {
1916         // To know if we are including userinfo
1917         $userinfo = $this->get_setting_value('userinfo');
1919         // Define each element separated
1921         $book = new backup_nested_element('activity_gradebook');
1923         $items = new backup_nested_element('grade_items');
1925         $item = new backup_nested_element('grade_item', array('id'), array(
1926             'categoryid', 'itemname', 'itemtype', 'itemmodule',
1927             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
1928             'calculation', 'gradetype', 'grademax', 'grademin',
1929             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
1930             'plusfactor', 'aggregationcoef', 'sortorder', 'display',
1931             'decimals', 'hidden', 'locked', 'locktime',
1932             'needsupdate', 'timecreated', 'timemodified'));
1934         $grades = new backup_nested_element('grade_grades');
1936         $grade = new backup_nested_element('grade_grade', array('id'), array(
1937             'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
1938             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
1939             'locked', 'locktime', 'exported', 'overridden',
1940             'excluded', 'feedback', 'feedbackformat', 'information',
1941             'informationformat', 'timecreated', 'timemodified'));
1943         $letters = new backup_nested_element('grade_letters');
1945         $letter = new backup_nested_element('grade_letter', 'id', array(
1946             'lowerboundary', 'letter'));
1948         // Build the tree
1950         $book->add_child($items);
1951         $items->add_child($item);
1953         $item->add_child($grades);
1954         $grades->add_child($grade);
1956         $book->add_child($letters);
1957         $letters->add_child($letter);
1959         // Define sources
1961         $item->set_source_sql("SELECT gi.*
1962                                FROM {grade_items} gi
1963                                JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
1964                                WHERE bi.backupid = ?
1965                                AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
1967         // This only happens if we are including user info
1968         if ($userinfo) {
1969             $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
1970         }
1972         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
1974         // Annotations
1976         $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
1977         $item->annotate_ids('outcome', 'outcomeid');
1979         $grade->annotate_ids('user', 'userid');
1980         $grade->annotate_ids('user', 'usermodified');
1982         // Return the root element (book)
1984         return $book;
1985     }
1988 /**
1989  * Backups up the course completion information for the course.
1990  */
1991 class backup_course_completion_structure_step extends backup_structure_step {
1993     protected function execute_condition() {
1994         // Check that all activities have been included
1995         if ($this->task->is_excluding_activities()) {
1996             return false;
1997         }
1998         return true;
1999     }
2001     /**
2002      * The structure of the course completion backup
2003      *
2004      * @return backup_nested_element
2005      */
2006     protected function define_structure() {
2008         // To know if we are including user completion info
2009         $userinfo = $this->get_setting_value('userscompletion');
2011         $cc = new backup_nested_element('course_completion');
2013         $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
2014             'course','criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod', 'timeend', 'gradepass', 'role'
2015         ));
2017         $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
2019         $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
2020             'criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted'
2021         ));
2023         $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
2024             'userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate'
2025         ));
2027         $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
2028             'course','criteriatype','method','value'
2029         ));
2031         $cc->add_child($criteria);
2032             $criteria->add_child($criteriacompletions);
2033                 $criteriacompletions->add_child($criteriacomplete);
2034         $cc->add_child($coursecompletions);
2035         $cc->add_child($aggregatemethod);
2037         // We need to get the courseinstances shortname rather than an ID for restore
2038         $criteria->set_source_sql("SELECT ccc.*, c.shortname AS courseinstanceshortname
2039                                    FROM {course_completion_criteria} ccc
2040                                    LEFT JOIN {course} c ON c.id = ccc.courseinstance
2041                                    WHERE ccc.course = ?", array(backup::VAR_COURSEID));
2044         $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
2046         if ($userinfo) {
2047             $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
2048             $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
2049         }
2051         $criteria->annotate_ids('role', 'role');
2052         $criteriacomplete->annotate_ids('user', 'userid');
2053         $coursecompletions->annotate_ids('user', 'userid');
2055         return $cc;
2057     }