MDL-22138 backup - I know smaller cathedrals than this, yay quizzes!
[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  * @package moodlecore
20  * @subpackage backup-moodle2
21  * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 /**
26  * Define all the backup steps that will be used by common tasks in backup
27  */
29 /**
30  * create the temp dir where backup/restore will happen,
31  * delete old directories and create temp ids table
32  */
33 class create_and_clean_temp_stuff extends backup_execution_step {
35     protected function define_execution() {
36         backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
37         backup_helper::clear_backup_dir($this->get_backupid());           // Empty temp dir, just in case
38         backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60));    // Delete > 4 hours temp dirs
39         backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
40     }
41 }
43 /**
44  * delete the temp dir used by backup/restore (conditionally),
45  * delete old directories and drop tem ids table. Note we delete
46  * the directory but not the corresponding log file that will be
47  * there for, at least, 4 hours - only delete_old_backup_dirs()
48  * deletes log files (for easier access to them)
49  */
50 class drop_and_clean_temp_stuff extends backup_execution_step {
52     protected $skipcleaningtempdir = false;
54     protected function define_execution() {
55         global $CFG;
57         backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
58         backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60));              // Delete > 4 hours temp dirs
59         // Delete temp dir conditionally:
60         // 1) If $CFG->keeptempdirectoriesonbackup is not enabled
61         // 2) If backup temp dir deletion has been marked to be avoided
62         if (empty($CFG->keeptempdirectoriesonbackup) && !$this->skipcleaningtempdir) {
63             backup_helper::delete_backup_dir($this->get_backupid()); // Empty backup dir
64         }
65     }
67     public function skip_cleaning_temp_dir($skip) {
68         $this->skipcleaningtempdir = $skip;
69     }
70 }
72 /**
73  * Create the directory where all the task (activity/block...) information will be stored
74  */
75 class create_taskbasepath_directory extends backup_execution_step {
77     protected function define_execution() {
78         global $CFG;
79         $basepath = $this->task->get_taskbasepath();
80         if (!check_dir_exists($basepath, true, true)) {
81             throw new backup_step_exception('cannot_create_taskbasepath_directory', $basepath);
82         }
83     }
84 }
86 /**
87  * Abstract structure step, parent of all the activity structure steps. Used to wrap the
88  * activity structure definition within the main <activity ...> tag. Also provides
89  * subplugin support for activities (that must be properly declared)
90  */
91 abstract class backup_activity_structure_step extends backup_structure_step {
93     /**
94      * Add subplugin structure to any element in the activity backup tree
95      *
96      * @param string $subplugintype type of subplugin as defined in activity db/subplugins.php
97      * @param backup_nested_element $element element in the activity backup tree that
98      *                                       we are going to add subplugin information to
99      * @param bool $multiple to define if multiple subplugins can produce information
100      *                       for each instance of $element (true) or no (false)
101      */
102     protected function add_subplugin_structure($subplugintype, $element, $multiple) {
104         global $CFG;
106         // Check the requested subplugintype is a valid one
107         $subpluginsfile = $CFG->dirroot . '/mod/' . $this->task->get_modulename() . '/db/subplugins.php';
108         if (!file_exists($subpluginsfile)) {
109              throw new backup_step_exception('activity_missing_subplugins_php_file', $this->task->get_modulename());
110         }
111         include($subpluginsfile);
112         if (!array_key_exists($subplugintype, $subplugins)) {
113              throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
114         }
116         // Arrived here, subplugin is correct, let's create the optigroup
117         $optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
118         $optigroup = new backup_optigroup($optigroupname, null, $multiple);
119         $element->add_child($optigroup); // Add optigroup to stay connected since beginning
121         // Get all the optigroup_elements, looking across all the subplugin dirs
122         $subpluginsdirs = get_plugin_list($subplugintype);
123         foreach ($subpluginsdirs as $name => $subpluginsdir) {
124             $classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
125             $backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
126             if (file_exists($backupfile)) {
127                 require_once($backupfile);
128                 $backupsubplugin = new $classname($subplugintype, $name, $optigroup);
129                 // Add subplugin returned structure to optigroup
130                 $backupsubplugin->define_subplugin_structure($element->get_name());
131             }
132         }
133     }
135     /**
136      * Wraps any activity backup structure within the common 'activity' element
137      * that will include common to all activities information like id, context...
138      */
139     protected function prepare_activity_structure($activitystructure) {
141         // Create the wrap element
142         $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);
144         // Build the tree
145         $activity->add_child($activitystructure);
147         // Set the source
148         $activityarr = array((object)array(
149             'id'         => $this->task->get_activityid(),
150             'moduleid'   => $this->task->get_moduleid(),
151             'modulename' => $this->task->get_modulename(),
152             'contextid'  => $this->task->get_contextid()));
154         $activity->set_source_array($activityarr);
156         // Return the root element (activity)
157         return $activity;
158     }
161 /**
162  * Abstract structure step, to be used by all the activities using core questions stuff
163  * (namely quiz module), supporting question plugins, states and sessions
164  */
165 abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
167     /**
168      * Attach to $element (usually attempts) the needed backup structures
169      * for question_states for a given question_attempt
170      */
171     protected function add_question_attempts_states($element, $questionattemptname) {
172         // Check $element is one nested_backup_element
173         if (! $element instanceof backup_nested_element) {
174             throw new backup_step_exception('question_states_bad_parent_element', $element);
175         }
176         // Check that the $questionattemptname is final element in $element
177         if (! $element->get_final_element($questionattemptname)) {
178             throw new backup_step_exception('question_states_bad_question_attempt_element', $questionattemptname);
179         }
181         // TODO: Some day we should stop these "encrypted" state->answers and
182         // TODO: delegate to qtypes plugin to proper XML writting the needed info on each question
184         // TODO: Should be doing here some introspection in the "answer" element, based on qtype,
185         // TODO: to know which real questions are being used (for randoms and other qtypes...)
186         // TODO: Not needed if consistency is guaranteed, but it isn't right now :-(
188         // Define the elements
189         $states = new backup_nested_element('states');
190         $state = new backup_nested_element('state', array('id'), array(
191             'question', 'seq_number', 'answer', 'timestamp',
192             'event', 'grade', 'raw_grade', 'penalty'));
194         // Build the tree
195         $element->add_child($states);
196         $states->add_child($state);
198         // Set the sources
199         $state->set_source_table('question_states', array('attempt' => '../../' . $questionattemptname));
201         // Annotate ids
202         $state->annotate_ids('question', 'question');
203     }
205     /**
206      * Attach to $element (usually attempts) the needed backup structures
207      * for question_sessions for a given question_attempt
208      */
209     protected function add_question_attempts_sessions($element, $questionattemptname) {
210         // Check $element is one nested_backup_element
211         if (! $element instanceof backup_nested_element) {
212             throw new backup_step_exception('question_sessions_bad_parent_element', $element);
213         }
214         // Check that the $questionattemptname is final element in $element
215         if (! $element->get_final_element($questionattemptname)) {
216             throw new backup_step_exception('question_sessions_bad_question_attempt_element', $questionattemptname);
217         }
219         // Define the elements
220         $sessions = new backup_nested_element('sessions');
221         $session = new backup_nested_element('session', array('id'), array(
222             'questionid', 'newest', 'newgraded', 'sumpenalty',
223             'manualcomment', 'manualcommentformat', 'flagged'));
225         // Build the tree
226         $element->add_child($sessions);
227         $sessions->add_child($session);
229         // Set the sources
230         $session->set_source_table('question_sessions', array('attemptid' => '../../' . $questionattemptname));
232         // Annotate ids
233         $session->annotate_ids('question', 'questionid');
235         // Annotate files
236         // Note: question_sessions haven't files associated. On purpose manualcomment is lacking
237         // support for them, so we don't need to annotated them here.
238     }
241 /**
242  * backup structure step in charge of calculating the categories to be
243  * included in backup, based in the context being backuped (module/course)
244  * and the already annotated questions present in backup_ids_temp
245  */
246 class backup_calculate_question_categories extends backup_execution_step {
248     protected function define_execution() {
249         backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());
250     }
253 /**
254  * backup structure step in charge of deleting all the questions annotated
255  * in the backup_ids_temp table
256  */
257 class backup_delete_temp_questions extends backup_execution_step {
259     protected function define_execution() {
260         backup_question_dbops::delete_temp_questions($this->get_backupid());
261     }
264 /**
265  * Abstract structure step, parent of all the block structure steps. Used to wrap the
266  * block structure definition within the main <block ...> tag
267  */
268 abstract class backup_block_structure_step extends backup_structure_step {
270     protected function prepare_block_structure($blockstructure) {
272         // Create the wrap element
273         $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);
275         // Build the tree
276         $block->add_child($blockstructure);
278         // Set the source
279         $blockarr = array((object)array(
280             'id'         => $this->task->get_blockid(),
281             'blockname'  => $this->task->get_blockname(),
282             'contextid'  => $this->task->get_contextid()));
284         $block->set_source_array($blockarr);
286         // Return the root element (block)
287         return $block;
288     }
291 /**
292  * structure step that will generate the module.xml file for the activity,
293  * accumulating various information about the activity, annotating groupings
294  * and completion/avail conf
295  */
296 class backup_module_structure_step extends backup_structure_step {
298     protected function define_structure() {
300         // Define each element separated
302         $module = new backup_nested_element('module', array('id', 'version'), array(
303             'modulename', 'sectionid', 'sectionnumber', 'idnumber',
304             'added', 'score', 'indent', 'visible',
305             'visibleold', 'groupmode', 'groupingid', 'groupmembersonly',
306             'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
307             'availablefrom', 'availableuntil', 'showavailability'));
309         $availinfo = new backup_nested_element('availability_info');
310         $availability = new backup_nested_element('availability', array('id'), array(
311             'sourcecmid', 'requiredcompletion', 'gradeitemid', 'grademin', 'grademax'));
313         // Define the tree
314         $module->add_child($availinfo);
315         $availinfo->add_child($availability);
317         // Set the sources
319         $module->set_source_sql('
320             SELECT cm.*, m.version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
321               FROM {course_modules} cm
322               JOIN {modules} m ON m.id = cm.module
323               JOIN {course_sections} s ON s.id = cm.section
324              WHERE cm.id = ?', array(backup::VAR_MODID));
326         $availability->set_source_table('course_modules_availability', array('coursemoduleid' => backup::VAR_MODID));
328         // Define annotations
329         $module->annotate_ids('grouping', 'groupingid');
331         // Return the root element ($module)
332         return $module;
333     }
336 /**
337  * structure step that will generate the section.xml file for the section
338  * annotating files
339  */
340 class backup_section_structure_step extends backup_structure_step {
342     protected function define_structure() {
344         // Define each element separated
346         $section = new backup_nested_element('section', array('id'), array(
347             'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible'));
349         // Define sources
351         $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
353         // Aliases
354         $section->set_source_alias('section', 'number');
356         // Set annotations
357         $section->annotate_files('course', 'section', 'id');
359         return $section;
360     }
363 /**
364  * structure step that will generate the course.xml file for the course, including
365  * course category reference, tags, modules restriction information
366  * and some annotations (files & groupings)
367  */
368 class backup_course_structure_step extends backup_structure_step {
370     protected function define_structure() {
371         global $DB;
373         // Define each element separated
375         $course = new backup_nested_element('course', array('id', 'contextid'), array(
376             'shortname', 'fullname', 'idnumber',
377             'summary', 'summaryformat', 'format', 'showgrades',
378             'newsitems', 'startdate', 'numsections',
379             'marker', 'maxbytes', 'legacyfiles', 'showreports',
380             'visible', 'hiddensections', 'groupmode', 'groupmodeforce',
381             'defaultgroupingid', 'lang', 'theme',
382             'timecreated', 'timemodified',
383             'requested', 'restrictmodules',
384             'enablecompletion', 'completionstartonenrol', 'completionnotify'));
386         $category = new backup_nested_element('category', array('id'), array(
387             'name', 'description'));
389         $tags = new backup_nested_element('tags');
391         $tag = new backup_nested_element('tag', array('id'), array(
392             'name', 'rawname'));
394         $allowedmodules = new backup_nested_element('allowed_modules');
396         $module = new backup_nested_element('module', array(), array('modulename'));
398         // Build the tree
400         $course->add_child($category);
402         $course->add_child($tags);
403         $tags->add_child($tag);
405         $course->add_child($allowedmodules);
406         $allowedmodules->add_child($module);
408         // Set the sources
410         $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
411         $courserec->contextid = $this->task->get_contextid();
413         $course->set_source_array(array($courserec));
415         $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));
417         $category->set_source_array(array($categoryrec));
419         $tag->set_source_sql('SELECT t.id, t.name, t.rawname
420                                 FROM {tag} t
421                                 JOIN {tag_instance} ti ON ti.tagid = t.id
422                                WHERE ti.itemtype = ?
423                                  AND ti.itemid = ?', array(
424                                      backup_helper::is_sqlparam('course'),
425                                      backup::VAR_PARENTID));
427         $module->set_source_sql('SELECT m.name AS modulename
428                                    FROM {modules} m
429                                    JOIN {course_allowed_modules} cam ON m.id = cam.module
430                                   WHERE course = ?', array(backup::VAR_COURSEID));
432         // Some annotations
434         $course->annotate_ids('grouping', 'defaultgroupingid');
436         $course->annotate_files('course', 'summary', null);
437         $course->annotate_files('course', 'legacy', null);
439         // Return root element ($course)
441         return $course;
442     }
445 /**
446  * structure step that will generate the enrolments.xml file for the given course
447  */
448 class backup_enrolments_structure_step extends backup_structure_step {
450     protected function define_structure() {
452         // To know if we are including users
453         $users = $this->get_setting_value('users');
455         // Define each element separated
457         $enrolments = new backup_nested_element('enrolments');
459         $enrols = new backup_nested_element('enrols');
461         $enrol = new backup_nested_element('enrol', array('id'), array(
462             'enrol', 'status', 'sortorder', 'name', 'enrolperiod', 'enrolstartdate',
463             'enrolenddate', 'expirynotify', 'expirytreshold', 'notifyall',
464             'password', 'cost', 'currency', 'roleid', 'customint1', 'customint2', 'customint3',
465             'customint4', 'customchar1', 'customchar2', 'customdec1', 'customdec2',
466             'customtext1', 'customtext2', 'timecreated', 'timemodified'));
468         $userenrolments = new backup_nested_element('user_enrolments');
470         $enrolment = new backup_nested_element('enrolment', array('id'), array(
471             'status', 'userid', 'timestart', 'timeend', 'modifierid',
472             'timemodified'));
474         // Build the tree
475         $enrolments->add_child($enrols);
476         $enrols->add_child($enrol);
477         $enrol->add_child($userenrolments);
478         $userenrolments->add_child($enrolment);
480         // Define sources
482         $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID));
484         // User enrolments only added only if users included
485         if ($users) {
486             $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));
487             $enrolment->annotate_ids('user', 'userid');
488         }
490         $enrol->annotate_ids('role', 'roleid');
492         //TODO: let plugins annotate custom fields too and add more children
494         return $enrolments;
495     }
498 /**
499  * structure step that will generate the roles.xml file for the given context, observing
500  * the role_assignments setting to know if that part needs to be included
501  */
502 class backup_roles_structure_step extends backup_structure_step {
504     protected function define_structure() {
506         // To know if we are including role assignments
507         $roleassignments = $this->get_setting_value('role_assignments');
509         // Define each element separated
511         $roles = new backup_nested_element('roles');
513         $overrides = new backup_nested_element('role_overrides');
515         $override = new backup_nested_element('override', array('id'), array(
516             'roleid', 'capability', 'permission', 'timemodified',
517             'modifierid'));
519         $assignments = new backup_nested_element('role_assignments');
521         $assignment = new backup_nested_element('assignment', array('id'), array(
522             'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid',
523             'sortorder'));
525         // Build the tree
526         $roles->add_child($overrides);
527         $roles->add_child($assignments);
529         $overrides->add_child($override);
530         $assignments->add_child($assignment);
532         // Define sources
534         $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));
536         // Assignments only added if specified
537         if ($roleassignments) {
538             $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));
539         }
541         // Define id annotations
542         $override->annotate_ids('role', 'roleid');
544         $assignment->annotate_ids('role', 'roleid');
546         $assignment->annotate_ids('user', 'userid');
548         //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)
550         return $roles;
551     }
554 /**
555  * structure step that will generate the roles.xml containing the
556  * list of roles used along the whole backup process. Just raw
557  * list of used roles from role table
558  */
559 class backup_final_roles_structure_step extends backup_structure_step {
561     protected function define_structure() {
563         // Define elements
565         $rolesdef = new backup_nested_element('roles_definition');
567         $role = new backup_nested_element('role', array('id'), array(
568             'name', 'shortname', 'nameincourse', 'description',
569             'sortorder', 'archetype'));
571         // Build the tree
573         $rolesdef->add_child($role);
575         // Define sources
577         $role->set_source_sql("SELECT r.*, rn.name AS nameincourse
578                                  FROM {role} r
579                                  JOIN {backup_ids_temp} bi ON r.id = bi.itemid
580                             LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?
581                                 WHERE bi.backupid = ?
582                                   AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));
584         // Return main element (rolesdef)
585         return $rolesdef;
586     }
589 /**
590  * structure step that will generate the scales.xml containing the
591  * list of scales used along the whole backup process.
592  */
593 class backup_final_scales_structure_step extends backup_structure_step {
595     protected function define_structure() {
597         // Define elements
599         $scalesdef = new backup_nested_element('scales_definition');
601         $scale = new backup_nested_element('scale', array('id'), array(
602             'courseid', 'userid', 'name', 'scale',
603             'description', 'descriptionformat', 'timemodified'));
605         // Build the tree
607         $scalesdef->add_child($scale);
609         // Define sources
611         $scale->set_source_sql("SELECT s.*
612                                   FROM {scale} s
613                                   JOIN {backup_ids_temp} bi ON s.id = bi.itemid
614                                  WHERE bi.backupid = ?
615                                    AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));
617         // Annotate scale files (they store files in system context, so pass it instead of default one)
618         $scale->annotate_files('grade', 'scale', 'id', get_context_instance(CONTEXT_SYSTEM)->id);
620         // Return main element (scalesdef)
621         return $scalesdef;
622     }
625 /**
626  * structure step that will generate the outcomes.xml containing the
627  * list of outcomes used along the whole backup process.
628  */
629 class backup_final_outcomes_structure_step extends backup_structure_step {
631     protected function define_structure() {
633         // Define elements
635         $outcomesdef = new backup_nested_element('outcomes_definition');
637         $outcome = new backup_nested_element('outcome', array('id'), array(
638             'courseid', 'userid', 'shortname', 'fullname',
639             'scaleid', 'description', 'descriptionformat', 'timecreated',
640             'timemodified','usermodified'));
642         // Build the tree
644         $outcomesdef->add_child($outcome);
646         // Define sources
648         $outcome->set_source_sql("SELECT o.*
649                                     FROM {grade_outcomes} o
650                                     JOIN {backup_ids_temp} bi ON o.id = bi.itemid
651                                    WHERE bi.backupid = ?
652                                      AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));
654         // Annotate outcome files (they store files in system context, so pass it instead of default one)
655         $outcome->annotate_files('grade', 'outcome', 'id', get_context_instance(CONTEXT_SYSTEM)->id);
657         // Return main element (outcomesdef)
658         return $outcomesdef;
659     }
662 /**
663  * structure step in charge of constructing the filters.xml file for all the filters found
664  * in activity
665  */
666 class backup_filters_structure_step extends backup_structure_step {
668     protected function define_structure() {
670         // Define each element separated
672         $filters = new backup_nested_element('filters');
674         $actives = new backup_nested_element('filter_actives');
676         $active = new backup_nested_element('filter_active', null, array('filter', 'active'));
678         $configs = new backup_nested_element('filter_configs');
680         $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));
682         // Build the tree
684         $filters->add_child($actives);
685         $filters->add_child($configs);
687         $actives->add_child($active);
688         $configs->add_child($config);
690         // Define sources
692         list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());
694         $active->set_source_array($activearr);
695         $config->set_source_array($configarr);
697         // Return the root element (filters)
698         return $filters;
699     }
702 /**
703  * structure step in charge of constructing the comments.xml file for all the comments found
704  * in a given context
705  */
706 class backup_comments_structure_step extends backup_structure_step {
708     protected function define_structure() {
710         // Define each element separated
712         $comments = new backup_nested_element('comments');
714         $comment = new backup_nested_element('comment', array('id'), array(
715             'commentarea', 'itemid', 'content', 'format',
716             'userid', 'timecreated'));
718         // Build the tree
720         $comments->add_child($comment);
722         // Define sources
724         $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));
726         // Define id annotations
728         $comment->annotate_ids('user', 'userid');
730         // Return the root element (comments)
731         return $comments;
732     }
735 /**
736  * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course
737  * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step
738  */
739 class backup_gradebook_structure_step extends backup_structure_step {
741     /**
742      * We need to decide conditionally, based on dynamic information
743      * about the execution of this step. Only will be executed if all
744      * the module gradeitems have been already included in backup
745      */
746     protected function execute_condition() {
747         return backup_plan_dbops::require_gradebook_backup($this->get_courseid(), $this->get_backupid());
748     }
750     protected function define_structure() {
752         // are we including user info?
753         $userinfo = $this->get_setting_value('users');
755         $gradebook = new backup_nested_element('gradebook');
757         //grade_letters are done in backup_activity_grades_structure_step()
759         //calculated grade items
760         $grade_items = new backup_nested_element('grade_items');
761         $grade_item = new backup_nested_element('grade_item', array('id'), array(
762             'categoryid', 'itemname', 'itemtype', 'itemmodule',
763             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
764             'calculation', 'gradetype', 'grademax', 'grademin',
765             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
766             'plusfactor', 'aggregationcoef', 'sortorder', 'display',
767             'decimals', 'hidden', 'locked', 'locktime',
768             'needsupdate', 'timecreated', 'timemodified'));
770         $grade_grades = new backup_nested_element('grade_grades');
771         $grade_grade = new backup_nested_element('grade_grade', array('id'), array(
772             'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
773             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
774             'locked', 'locktime', 'exported', 'overridden',
775             'excluded', 'feedback', 'feedbackformat', 'information',
776             'informationformat', 'timecreated', 'timemodified'));
778         //grade_categories
779         $grade_categories = new backup_nested_element('grade_categories');
780         $grade_category   = new backup_nested_element('grade_category', array('id'), array(
781                 //'courseid', 
782                 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
783                 'dropload', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
784                 'timecreated', 'timemodified', 'hidden'));
786         $letters = new backup_nested_element('grade_letters');
787         $letter = new backup_nested_element('grade_letter', 'id', array(
788             'lowerboundary', 'letter'));
790         $grade_settings = new backup_nested_element('grade_settings');
791         $grade_setting = new backup_nested_element('grade_setting', 'id', array(
792             'name', 'value'));
795         // Build the tree
796         $gradebook->add_child($grade_categories);
797         $grade_categories->add_child($grade_category);
799         $gradebook->add_child($grade_items);
800         $grade_items->add_child($grade_item);
801         $grade_item->add_child($grade_grades);
802         $grade_grades->add_child($grade_grade);
804         $gradebook->add_child($letters);
805         $letters->add_child($letter);
807         $gradebook->add_child($grade_settings);
808         $grade_settings->add_child($grade_setting);
810         // Define sources
812         //Include manual, category and the course grade item
813         $grade_items_sql ="SELECT * FROM {grade_items}
814                            WHERE courseid = :courseid
815                            AND (itemtype='manual' OR itemtype='course' OR itemtype='category')";
816         $grade_items_params = array('courseid'=>backup::VAR_COURSEID);
817         $grade_item->set_source_sql($grade_items_sql, $grade_items_params);
819         if ($userinfo) {
820             $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
821         }
823         $grade_category_sql = "SELECT gc.*, gi.sortorder
824                                FROM {grade_categories} gc
825                                JOIN {grade_items} gi ON (gi.iteminstance = gc.id)
826                                WHERE gc.courseid = :courseid
827                                AND (gi.itemtype='course' OR gi.itemtype='category')
828                                ORDER BY gc.parent ASC";//need parent categories before their children
829         $grade_category_params = array('courseid'=>backup::VAR_COURSEID);
830         $grade_category->set_source_sql($grade_category_sql, $grade_category_params);
832         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
834         $grade_setting->set_source_table('grade_settings', array('courseid' => backup::VAR_COURSEID));
836         // Annotations (both as final as far as they are going to be exported in next steps)
837         $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
838         $grade_item->annotate_ids('outcomefinal', 'outcomeid');
840         //just in case there are any users not already annotated by the activities
841         $grade_grade->annotate_ids('userfinal', 'userid');
843         // Return the root element
844         return $gradebook;
845     }
848 /**
849  * structure step in charge if constructing the completion.xml file for all the users completion
850  * information in a given activity
851  */
852 class backup_userscompletion_structure_step extends backup_structure_step {
854     protected function define_structure() {
856         // Define each element separated
858         $completions = new backup_nested_element('completions');
860         $completion = new backup_nested_element('completion', array('id'), array(
861             'userid', 'completionstate', 'viewed', 'timemodified'));
863         // Build the tree
865         $completions->add_child($completion);
867         // Define sources
869         $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
871         // Define id annotations
873         $completion->annotate_ids('user', 'userid');
875         // Return the root element (completions)
876         return $completions;
877     }
880 /**
881  * structure step in charge of constructing the main groups.xml file for all the groups and
882  * groupings information already annotated
883  */
884 class backup_groups_structure_step extends backup_structure_step {
886     protected function define_structure() {
888         // To know if we are including users
889         $users = $this->get_setting_value('users');
891         // Define each element separated
893         $groups = new backup_nested_element('groups');
895         $group = new backup_nested_element('group', array('id'), array(
896             'name', 'description', 'descriptionformat', 'enrolmentkey',
897             'picture', 'hidepicture', 'timecreated', 'timemodified'));
899         $members = new backup_nested_element('group_members');
901         $member = new backup_nested_element('group_member', array('id'), array(
902             'userid', 'timeadded'));
904         $groupings = new backup_nested_element('groupings');
906         $grouping = new backup_nested_element('grouping', 'id', array(
907             'name', 'description', 'descriptionformat', 'configdata',
908             'timecreated', 'timemodified'));
910         $groupinggroups = new backup_nested_element('grouping_groups');
912         $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
913             'groupid', 'timeadded'));
915         // Build the tree
917         $groups->add_child($group);
918         $groups->add_child($groupings);
920         $group->add_child($members);
921         $members->add_child($member);
923         $groupings->add_child($grouping);
924         $grouping->add_child($groupinggroups);
925         $groupinggroups->add_child($groupinggroup);
927         // Define sources
929         $group->set_source_sql("
930             SELECT g.*
931               FROM {groups} g
932               JOIN {backup_ids_temp} bi ON g.id = bi.itemid
933              WHERE bi.backupid = ?
934                AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
936         // This only happens if we are including users
937         if ($users) {
938             $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
939         }
941         $grouping->set_source_sql("
942             SELECT g.*
943               FROM {groupings} g
944               JOIN {backup_ids_temp} bi ON g.id = bi.itemid
945              WHERE bi.backupid = ?
946                AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
948         $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
950         // Define id annotations (as final)
952         $member->annotate_ids('userfinal', 'userid');
954         // Define file annotations
956         $group->annotate_files('group', 'description', 'id');
957         $group->annotate_files('group', 'icon', 'id');
958         $grouping->annotate_files('grouping', 'description', 'id');
960         // Return the root element (groups)
961         return $groups;
962     }
965 /**
966  * structure step in charge of constructing the main users.xml file for all the users already
967  * annotated (final). Includes custom profile fields, preferences, tags, role assignments and
968  * overrides.
969  */
970 class backup_users_structure_step extends backup_structure_step {
972     protected function define_structure() {
973         global $CFG;
975         // To know if we are anonymizing users
976         $anonymize = $this->get_setting_value('anonymize');
977         // To know if we are including role assignments
978         $roleassignments = $this->get_setting_value('role_assignments');
980         // Define each element separated
982         $users = new backup_nested_element('users');
984         // Create the array of user fields by hand, as far as we have various bits to control
985         // anonymize option, password backup, mnethostid...
987         // First, the fields not needing anonymization nor special handling
988         $normalfields = array(
989             'confirmed', 'policyagreed', 'deleted',
990             'lang', 'theme', 'timezone', 'firstaccess',
991             'lastaccess', 'lastlogin', 'currentlogin',
992             'mailformat', 'maildigest', 'maildisplay', 'htmleditor',
993             'ajax', 'autosubscribe', 'trackforums', 'timecreated',
994             'timemodified', 'trustbitmask', 'screenreader');
996         // Then, the fields potentially needing anonymization
997         $anonfields = array(
998             'username', 'idnumber', 'firstname', 'lastname',
999             'email', 'emailstop', 'icq', 'skype',
1000             'yahoo', 'aim', 'msn', 'phone1',
1001             'phone2', 'institution', 'department', 'address',
1002             'city', 'country', 'lastip', 'picture',
1003             'url', 'description', 'descriptionformat', 'imagealt', 'auth');
1005         // Add anonymized fields to $userfields with custom final element
1006         foreach ($anonfields as $field) {
1007             if ($anonymize) {
1008                 $userfields[] = new anonymizer_final_element($field);
1009             } else {
1010                 $userfields[] = $field; // No anonymization, normally added
1011             }
1012         }
1014         // mnethosturl requires special handling (custom final element)
1015         $userfields[] = new mnethosturl_final_element('mnethosturl');
1017         // password added conditionally
1018         if (!empty($CFG->includeuserpasswordsinbackup)) {
1019             $userfields[] = 'password';
1020         }
1022         // Merge all the fields
1023         $userfields = array_merge($userfields, $normalfields);
1025         $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1027         $customfields = new backup_nested_element('custom_fields');
1029         $customfield = new backup_nested_element('custom_field', array('id'), array(
1030             'field_name', 'field_type', 'field_data'));
1032         $tags = new backup_nested_element('tags');
1034         $tag = new backup_nested_element('tag', array('id'), array(
1035             'name', 'rawname'));
1037         $preferences = new backup_nested_element('preferences');
1039         $preference = new backup_nested_element('preference', array('id'), array(
1040             'name', 'value'));
1042         $roles = new backup_nested_element('roles');
1044         $overrides = new backup_nested_element('role_overrides');
1046         $override = new backup_nested_element('override', array('id'), array(
1047             'roleid', 'capability', 'permission', 'timemodified',
1048             'modifierid'));
1050         $assignments = new backup_nested_element('role_assignments');
1052         $assignment = new backup_nested_element('assignment', array('id'), array(
1053             'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
1054             'sortorder'));
1056         // Build the tree
1058         $users->add_child($user);
1060         $user->add_child($customfields);
1061         $customfields->add_child($customfield);
1063         $user->add_child($tags);
1064         $tags->add_child($tag);
1066         $user->add_child($preferences);
1067         $preferences->add_child($preference);
1069         $user->add_child($roles);
1071         $roles->add_child($overrides);
1072         $roles->add_child($assignments);
1074         $overrides->add_child($override);
1075         $assignments->add_child($assignment);
1077         // Define sources
1079         $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
1080                                  FROM {user} u
1081                                  JOIN {backup_ids_temp} bi ON bi.itemid = u.id
1082                                  JOIN {context} c ON c.instanceid = u.id
1083                             LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
1084                                 WHERE bi.backupid = ?
1085                                   AND bi.itemname = ?
1086                                   AND c.contextlevel = ?', array(
1087                                       backup_helper::is_sqlparam($this->get_backupid()),
1088                                       backup_helper::is_sqlparam('userfinal'),
1089                                       backup_helper::is_sqlparam(CONTEXT_USER)));
1091         // All the rest on information is only added if we arent
1092         // in an anonymized backup
1093         if (!$anonymize) {
1094             $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data
1095                                             FROM {user_info_field} f
1096                                             JOIN {user_info_data} d ON d.fieldid = f.id
1097                                            WHERE d.userid = ?', array(backup::VAR_PARENTID));
1099             $customfield->set_source_alias('shortname', 'field_name');
1100             $customfield->set_source_alias('datatype',  'field_type');
1101             $customfield->set_source_alias('data',      'field_data');
1103             $tag->set_source_sql('SELECT t.id, t.name, t.rawname
1104                                     FROM {tag} t
1105                                     JOIN {tag_instance} ti ON ti.tagid = t.id
1106                                    WHERE ti.itemtype = ?
1107                                      AND ti.itemid = ?', array(
1108                                          backup_helper::is_sqlparam('user'),
1109                                          backup::VAR_PARENTID));
1111             $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1113             $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1115             // Assignments only added if specified
1116             if ($roleassignments) {
1117                 $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1118             }
1120             // Define id annotations (as final)
1121             $override->annotate_ids('rolefinal', 'roleid');
1122         }
1124         // Return root element (users)
1125         return $users;
1126     }
1129 /**
1130  * structure step in charge of constructing the block.xml file for one
1131  * given block (instance and positions). If the block has custom DB structure
1132  * that will go to a separate file (different step defined in block class)
1133  */
1134 class backup_block_instance_structure_step extends backup_structure_step {
1136     protected function define_structure() {
1137         global $DB;
1139         // Define each element separated
1141         $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
1142             'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1143             'subpagepattern', 'defaultregion', 'defaultweight', 'configdata'));
1145         $positions = new backup_nested_element('block_positions');
1147         $position = new backup_nested_element('block_position', array('id'), array(
1148             'contextid', 'pagetype', 'subpage', 'visible',
1149             'region', 'weight'));
1151         // Build the tree
1153         $block->add_child($positions);
1154         $positions->add_child($position);
1156         // Transform configdata information if needed (process links and friends)
1157         $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
1158         if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
1159             $configdata = (array)unserialize(base64_decode($blockrec->configdata));
1160             foreach ($configdata as $attribute => $value) {
1161                 if (in_array($attribute, $attrstotransform)) {
1162                     $configdata[$attribute] = $this->contenttransformer->process($value);
1163                 }
1164             }
1165             $blockrec->configdata = base64_encode(serialize((object)$configdata));
1166         }
1167         $blockrec->contextid = $this->task->get_contextid();
1168         // Get the version of the block
1169         $blockrec->version = $DB->get_field('block', 'version', array('name' => $this->task->get_blockname()));
1171         // Define sources
1173         $block->set_source_array(array($blockrec));
1175         $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
1177         // File anotations (for fileareas specified on each block)
1178         foreach ($this->task->get_fileareas() as $filearea) {
1179             $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);
1180         }
1182         // Return the root element (block)
1183         return $block;
1184     }
1187 /**
1188  * structure step in charge of constructing the logs.xml file for all the log records found
1189  * in activity
1190  */
1191 class backup_activity_logs_structure_step extends backup_structure_step {
1193     protected function define_structure() {
1195         // Define each element separated
1197         $logs = new backup_nested_element('logs');
1199         $log = new backup_nested_element('log', array('id'), array(
1200             'time', 'userid', 'ip', 'module',
1201             'action', 'url', 'info'));
1203         // Build the tree
1205         $logs->add_child($log);
1207         // Define sources
1209         $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1211         // Annotations
1212         // NOTE: We don't annotate users from logs as far as they MUST be
1213         //       always annotated by the activity.
1215         // Return the root element (logs)
1217         return $logs;
1218     }
1221 /**
1222  * structure in charge of constructing the inforef.xml file for all the items we want
1223  * to have referenced there (users, roles, files...)
1224  */
1225 class backup_inforef_structure_step extends backup_structure_step {
1227     protected function define_structure() {
1229         // Items we want to include in the inforef file.
1230         $items = backup_helper::get_inforef_itemnames();
1232         // Build the tree
1234         $inforef = new backup_nested_element('inforef');
1236         // For each item, conditionally, if there are already records, build element
1237         foreach ($items as $itemname) {
1238             if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {
1239                 $elementroot = new backup_nested_element($itemname . 'ref');
1240                 $element = new backup_nested_element($itemname, array(), array('id'));
1241                 $inforef->add_child($elementroot);
1242                 $elementroot->add_child($element);
1243                 $element->set_source_sql("
1244                     SELECT itemid AS id
1245                      FROM {backup_ids_temp}
1246                     WHERE backupid = ?
1247                       AND itemname = ?",
1248                    array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
1249             }
1250         }
1252         // We don't annotate anything there, but rely in the next step
1253         // (move_inforef_annotations_to_final) that will change all the
1254         // already saved 'inforref' entries to their 'final' annotations.
1255         return $inforef;
1256     }
1259 /**
1260  * This step will get all the annotations already processed to inforef.xml file and
1261  * transform them into 'final' annotations.
1262  */
1263 class move_inforef_annotations_to_final extends backup_execution_step {
1265     protected function define_execution() {
1267         // Items we want to include in the inforef file
1268         $items = backup_helper::get_inforef_itemnames();
1269         foreach ($items as $itemname) {
1270             // Delegate to dbops
1271             backup_structure_dbops::move_annotations_to_final($this->get_backupid(), $itemname);
1272         }
1273     }
1276 /**
1277  * structure in charge of constructing the files.xml file with all the
1278  * annotated (final) files along the process. At, the same time, and
1279  * using one specialised nested_element, will copy them form moodle storage
1280  * to backup storage
1281  */
1282 class backup_final_files_structure_step extends backup_structure_step {
1284     protected function define_structure() {
1286         // Define elements
1288         $files = new backup_nested_element('files');
1290         $file = new file_nested_element('file', array('id'), array(
1291             'contenthash', 'contextid', 'component', 'filearea', 'itemid',
1292             'filepath', 'filename', 'userid', 'filesize',
1293             'mimetype', 'status', 'timecreated', 'timemodified',
1294             'source', 'author', 'license', 'sortorder'));
1296         // Build the tree
1298         $files->add_child($file);
1300         // Define sources
1302         $file->set_source_sql("SELECT f.*
1303                                  FROM {files} f
1304                                  JOIN {backup_ids_temp} bi ON f.id = bi.itemid
1305                                 WHERE bi.backupid = ?
1306                                   AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
1308         return $files;
1309     }
1312 /**
1313  * Structure step in charge of creating the main moodle_backup.xml file
1314  * where all the information related to the backup, settings, license and
1315  * other information needed on restore is added*/
1316 class backup_main_structure_step extends backup_structure_step {
1318     protected function define_structure() {
1320         global $CFG;
1322         $info = array();
1324         $info['name'] = $this->get_setting_value('filename');
1325         $info['moodle_version'] = $CFG->version;
1326         $info['moodle_release'] = $CFG->release;
1327         $info['backup_version'] = $CFG->backup_version;
1328         $info['backup_release'] = $CFG->backup_release;
1329         $info['backup_date']    = time();
1330         $info['backup_uniqueid']= $this->get_backupid();
1331         $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
1332         $info['original_wwwroot']=$CFG->wwwroot;
1333         $info['original_site_identifier_hash'] = md5(get_site_identifier());
1334         $info['original_course_id'] = $this->get_courseid();
1335         $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
1336         $info['original_course_fullname']  = $originalcourseinfo->fullname;
1337         $info['original_course_shortname'] = $originalcourseinfo->shortname;
1338         $info['original_course_startdate'] = $originalcourseinfo->startdate;
1339         $info['original_course_contextid'] = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
1340         $info['original_system_contextid'] = get_context_instance(CONTEXT_SYSTEM)->id;
1342         // Get more information from controller
1343         list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($this->get_backupid());
1345         // Define elements
1347         $moodle_backup = new backup_nested_element('moodle_backup');
1349         $information = new backup_nested_element('information', null, array(
1350             'name', 'moodle_version', 'moodle_release', 'backup_version',
1351             'backup_release', 'backup_date', 'mnet_remoteusers', 'original_wwwroot',
1352             'original_site_identifier_hash', 'original_course_id',
1353             'original_course_fullname', 'original_course_shortname', 'original_course_startdate',
1354             'original_course_contextid', 'original_system_contextid'));
1356         $details = new backup_nested_element('details');
1358         $detail = new backup_nested_element('detail', array('backup_id'), array(
1359             'type', 'format', 'interactive', 'mode',
1360             'execution', 'executiontime'));
1362         $contents = new backup_nested_element('contents');
1364         $activities = new backup_nested_element('activities');
1366         $activity = new backup_nested_element('activity', null, array(
1367             'moduleid', 'sectionid', 'modulename', 'title',
1368             'directory'));
1370         $sections = new backup_nested_element('sections');
1372         $section = new backup_nested_element('section', null, array(
1373             'sectionid', 'title', 'directory'));
1375         $course = new backup_nested_element('course', null, array(
1376             'courseid', 'title', 'directory'));
1378         $settings = new backup_nested_element('settings');
1380         $setting = new backup_nested_element('setting', null, array(
1381             'level', 'section', 'activity', 'name', 'value'));
1383         // Build the tree
1385         $moodle_backup->add_child($information);
1387         $information->add_child($details);
1388         $details->add_child($detail);
1390         $information->add_child($contents);
1391         if (!empty($cinfo['activities'])) {
1392             $contents->add_child($activities);
1393             $activities->add_child($activity);
1394         }
1395         if (!empty($cinfo['sections'])) {
1396             $contents->add_child($sections);
1397             $sections->add_child($section);
1398         }
1399         if (!empty($cinfo['course'])) {
1400             $contents->add_child($course);
1401         }
1403         $information->add_child($settings);
1404         $settings->add_child($setting);
1407         // Set the sources
1409         $information->set_source_array(array((object)$info));
1411         $detail->set_source_array($dinfo);
1413         $activity->set_source_array($cinfo['activities']);
1415         $section->set_source_array($cinfo['sections']);
1417         $course->set_source_array($cinfo['course']);
1419         $setting->set_source_array($sinfo);
1421         // Prepare some information to be sent to main moodle_backup.xml file
1422         return $moodle_backup;
1423     }
1427 /**
1428  * Execution step that will generate the final zip (.mbz) file with all the contents
1429  */
1430 class backup_zip_contents extends backup_execution_step {
1432     protected function define_execution() {
1434         // Get basepath
1435         $basepath = $this->get_basepath();
1437         // Get the list of files in directory
1438         $filestemp = get_directory_list($basepath, '', false, true, true);
1439         $files = array();
1440         foreach ($filestemp as $file) { // Add zip paths and fs paths to all them
1441             $files[$file] = $basepath . '/' . $file;
1442         }
1444         // Add the log file if exists
1445         $logfilepath = $basepath . '.log';
1446         if (file_exists($logfilepath)) {
1447              $files['moodle_backup.log'] = $logfilepath;
1448         }
1450         // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1451         $zipfile = $basepath . '/backup.mbz';
1453         // Get the zip packer
1454         $zippacker = get_file_packer('application/zip');
1456         // Zip files
1457         $zippacker->archive_to_pathname($files, $zipfile);
1458     }
1461 /**
1462  * This step will send the generated backup file to its final destination
1463  */
1464 class backup_store_backup_file extends backup_execution_step {
1466     protected function define_execution() {
1468         // Get basepath
1469         $basepath = $this->get_basepath();
1471         // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1472         $zipfile = $basepath . '/backup.mbz';
1474         // Perform storage and return it (TODO: shouldn't be array but proper result object)
1475         return array('backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile));
1476     }
1480 /**
1481  * This step will search for all the activity (not calculations, categories nor aggregations) grade items
1482  * and put them to the backup_ids tables, to be used later as base to backup them
1483  */
1484 class backup_activity_grade_items_to_ids extends backup_execution_step {
1486     protected function define_execution() {
1488         // Fetch all activity grade items
1489         if ($items = grade_item::fetch_all(array(
1490                          'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),
1491                          'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {
1492             // Annotate them in backup_ids
1493             foreach ($items as $item) {
1494                 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);
1495             }
1496         }
1497     }
1500 /**
1501  * This step will annotate all the groups belonging to already annotated groupings
1502  */
1503 class backup_annotate_groups_from_groupings extends backup_execution_step {
1505     protected function define_execution() {
1506         global $DB;
1508         // Fetch all the annotated groupings
1509         if ($groupings = $DB->get_records('backup_ids_temp', array(
1510                 'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
1511             foreach ($groupings as $grouping) {
1512                 if ($groups = $DB->get_records('groupings_groups', array(
1513                         'groupingid' => $grouping->itemid))) {
1514                     foreach ($groups as $group) {
1515                         backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
1516                     }
1517                 }
1518             }
1519         }
1520     }
1523 /**
1524  * This step will annotate all the scales belonging to already annotated outcomes
1525  */
1526 class backup_annotate_scales_from_outcomes extends backup_execution_step {
1528     protected function define_execution() {
1529         global $DB;
1531         // Fetch all the annotated outcomes
1532         if ($outcomes = $DB->get_records('backup_ids_temp', array(
1533                 'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
1534             foreach ($outcomes as $outcome) {
1535                 if ($scale = $DB->get_record('grade_outcomes', array(
1536                         'id' => $outcome->itemid))) {
1537                     // Annotate as scalefinal because it's > 0
1538                     backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
1539                 }
1540             }
1541         }
1542     }
1545 /**
1546  * This step will generate all the file annotations for the already
1547  * annotated (final) question_categories. It calculates the different
1548  * contexts that are being backup and, annotates all the files
1549  * on every context belonging to the "question" component. As far as
1550  * we are always including *complete* question banks it is safe and
1551  * optimal to do that in this (one pass) way
1552  */
1553 class backup_annotate_all_question_files extends backup_execution_step {
1555     protected function define_execution() {
1556         global $DB;
1558         // Get all the different contexts for the final question_categories
1559         // annotated along the whole backup
1560         $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
1561                                         FROM {question_categories} qc
1562                                         JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
1563                                        WHERE bi.backupid = ?
1564                                          AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
1565         // To know about qtype specific components/fileareas
1566         $components = backup_qtype_plugin::get_components_and_fileareas();
1567         // Let's loop
1568         foreach($rs as $record) {
1569             // We don't need to specify filearea nor itemid as far as by
1570             // component and context it's enough to annotate the whole bank files
1571             // This backups "questiontext", "generalfeedback" and "answerfeedback" fileareas (all them
1572             // belonging to the "question" component
1573             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', null, null);
1574             // Again, it is enough to pick files only by context and component
1575             // Do it for qtype specific components
1576             foreach ($components as $component => $fileareas) {
1577                 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null);
1578             }
1579         }
1580         $rs->close();
1581     }
1584 /**
1585  * structure step in charge of constructing the questions.xml file for all the
1586  * question categories and questions required by the backup
1587  * and letters related to one activity
1588  */
1589 class backup_questions_structure_step extends backup_structure_step {
1591     protected function define_structure() {
1593         // Define each element separated
1595         $qcategories = new backup_nested_element('question_categories');
1597         $qcategory = new backup_nested_element('question_category', array('id'), array(
1598             'name', 'contextid', 'contextlevel', 'contextinstanceid',
1599             'info', 'infoformat', 'stamp', 'parent',
1600             'sortorder'));
1602         $questions = new backup_nested_element('questions');
1604         $question = new backup_nested_element('question', array('id'), array(
1605             'parent', 'name', 'questiontext', 'questiontextformat',
1606             'generalfeedback', 'generalfeedbackformat', 'defaultgrade', 'penalty',
1607             'qtype', 'length', 'stamp', 'version',
1608             'hidden', 'timecreated', 'timemodified', 'createdby', 'modifiedby'));
1610         // attach qtype plugin structure to $question element, only one allowed
1611         $this->add_plugin_structure('qtype', $question, false);
1613         // Build the tree
1615         $qcategories->add_child($qcategory);
1616         $qcategory->add_child($questions);
1618         $questions->add_child($question);
1620         // Define the sources
1622         $qcategory->set_source_sql("
1623             SELECT gc.*, contextlevel, instanceid AS contextinstanceid
1624               FROM {question_categories} gc
1625               JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
1626               JOIN {context} co ON co.id = gc.contextid
1627              WHERE bi.backupid = ?
1628                AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID));
1630         $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
1632         // don't need to annotate ids nor files
1633         // (already done by {@link backup_annotate_all_question_files}
1635         return $qcategories;
1636     }
1641 /**
1642  * This step will generate all the file  annotations for the already
1643  * annotated (final) users. Need to do this here because each user
1644  * has its own context and structure tasks only are able to handle
1645  * one context. Also, this step will guarantee that every user has
1646  * its context created (req for other steps)
1647  */
1648 class backup_annotate_all_user_files extends backup_execution_step {
1650     protected function define_execution() {
1651         global $DB;
1653         // List of fileareas we are going to annotate
1654         $fileareas = array('profile', 'icon');
1656         if ($this->get_setting_value('user_files')) { // private files only if enabled in settings
1657             $fileareas[] = 'private';
1658         }
1660         // Fetch all annotated (final) users
1661         $rs = $DB->get_recordset('backup_ids_temp', array(
1662             'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
1663         foreach ($rs as $record) {
1664             $userid = $record->itemid;
1665             $userctxid = get_context_instance(CONTEXT_USER, $userid)->id;
1666             // Proceed with every user filearea
1667             foreach ($fileareas as $filearea) {
1668                 // We don't need to specify itemid ($userid - 5th param) as far as by
1669                 // context we can get all the associated files. See MDL-22092
1670                 backup_structure_dbops::annotate_files($this->get_backupid(), $userctxid, 'user', $filearea, null);
1671             }
1672         }
1673         $rs->close();
1674     }
1677 /**
1678  * structure step in charge of constructing the grades.xml file for all the grade items
1679  * and letters related to one activity
1680  */
1681 class backup_activity_grades_structure_step extends backup_structure_step {
1683     protected function define_structure() {
1685         // To know if we are including userinfo
1686         $userinfo = $this->get_setting_value('userinfo');
1688         // Define each element separated
1690         $book = new backup_nested_element('activity_gradebook');
1692         $items = new backup_nested_element('grade_items');
1694         $item = new backup_nested_element('grade_item', array('id'), array(
1695             'categoryid', 'itemname', 'itemtype', 'itemmodule',
1696             'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
1697             'calculation', 'gradetype', 'grademax', 'grademin',
1698             'scaleid', 'outcomeid', 'gradepass', 'multfactor',
1699             'plusfactor', 'aggregationcoef', 'sortorder', 'display',
1700             'decimals', 'hidden', 'locked', 'locktime',
1701             'needsupdate', 'timecreated', 'timemodified'));
1703         $grades = new backup_nested_element('grade_grades');
1705         $grade = new backup_nested_element('grade_grade', array('id'), array(
1706             'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
1707             'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
1708             'locked', 'locktime', 'exported', 'overridden',
1709             'excluded', 'feedback', 'feedbackformat', 'information',
1710             'informationformat', 'timecreated', 'timemodified'));
1712         $letters = new backup_nested_element('grade_letters');
1714         $letter = new backup_nested_element('grade_letter', 'id', array(
1715             'lowerboundary', 'letter'));
1717         // Build the tree
1719         $book->add_child($items);
1720         $items->add_child($item);
1722         $item->add_child($grades);
1723         $grades->add_child($grade);
1725         $book->add_child($letters);
1726         $letters->add_child($letter);
1728         // Define sources
1730         $item->set_source_sql("SELECT gi.*
1731                                FROM {grade_items} gi
1732                                JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
1733                                WHERE bi.backupid = ?
1734                                AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
1736         // This only happens if we are including user info
1737         if ($userinfo) {
1738             $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
1739         }
1741         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
1743         // Annotations
1745         $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
1746         $item->annotate_ids('outcome', 'outcomeid');
1748         $grade->annotate_ids('user', 'userid');
1749         $grade->annotate_ids('user', 'usermodified');
1751         // Return the root element (book)
1753         return $book;
1754     }
1757 /**
1758  * Backups up the course completion information for the course.
1759  */
1760 class backup_course_completion_structure_step extends backup_structure_step {
1762     protected function execute_condition() {
1763         // Check that all activities have been included
1764         if ($this->task->is_excluding_activities()) {
1765             return false;
1766         }
1767         return true;
1768     }
1770     /**
1771      * The structure of the course completion backup
1772      *
1773      * @return backup_nested_element
1774      */
1775     protected function define_structure() {
1777         // To know if we are including user completion info
1778         $userinfo = $this->get_setting_value('userscompletion');
1780         $cc = new backup_nested_element('course_completion');
1782         $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
1783             'course','criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod', 'timeend', 'gradepass', 'role'
1784         ));
1786         $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
1788         $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
1789             'criteriaid', 'userid','gradefinal','unenrolled','deleted','timecompleted'
1790         ));
1792         $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
1793             'userid', 'course', 'deleted', 'timenotified', 'timeenrolled','timestarted','timecompleted','reaggregate'
1794         ));
1796         $notify = new backup_nested_element('course_completion_notify', array('id'), array(
1797             'course','role','message','timesent'
1798         ));
1800         $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
1801             'course','criteriatype','method','value'
1802         ));
1804         $cc->add_child($criteria);
1805             $criteria->add_child($criteriacompletions);
1806                 $criteriacompletions->add_child($criteriacomplete);
1807         $cc->add_child($coursecompletions);
1808         $cc->add_child($notify);
1809         $cc->add_child($aggregatemethod);
1811         // We need to get the courseinstances shortname rather than an ID for restore
1812         $criteria->set_source_sql("SELECT ccc.*, c.shortname AS courseinstanceshortname
1813                                    FROM {course_completion_criteria} ccc
1814                                    LEFT JOIN {course} c ON c.id = ccc.courseinstance
1815                                    WHERE ccc.course = ?", array(backup::VAR_COURSEID));
1818         $notify->set_source_table('course_completion_notify', array('course' => backup::VAR_COURSEID));
1819         $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
1821         if ($userinfo) {
1822             $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
1823             $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
1824         }
1826         $criteria->annotate_ids('role', 'role');
1827         $criteriacomplete->annotate_ids('user', 'userid');
1828         $coursecompletions->annotate_ids('user', 'userid');
1829         $notify->annotate_ids('role', 'role');
1831         return $cc;
1833     }