MDL-23752 backup - export/import mode. Fixed various details.
[moodle.git] / backup / moodle2 / backup_stepslib.php
CommitLineData
77547b46
EL
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * @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 */
24
25/**
26 * Define all the backup steps that will be used by common tasks in backup
27 */
2de3539b
EL
28
29/**
30 * create the temp dir where backup/restore will happen,
31 * delete old directories and create temp ids table
32 */
77547b46
EL
33class create_and_clean_temp_stuff extends backup_execution_step {
34
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}
42
2de3539b
EL
43/**
44 * delete the temp dir used by backup/restore (conditionally),
45 * delete old directories and drop tem ids table. Note we delete
39b5371c 46 * the directory but not the corresponding log file that will be
2de3539b
EL
47 * there for, at least, 4 hours - only delete_old_backup_dirs()
48 * deletes log files (for easier access to them)
49 */
50class drop_and_clean_temp_stuff extends backup_execution_step {
51
dc1e4cce
EL
52 protected $skipcleaningtempdir = false;
53
2de3539b
EL
54 protected function define_execution() {
55 global $CFG;
dc1e4cce 56
2de3539b
EL
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
dc1e4cce
EL
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) {
2de3539b
EL
63 backup_helper::delete_backup_dir($this->get_backupid()); // Empty backup dir
64 }
65 }
dc1e4cce
EL
66
67 public function skip_cleaning_temp_dir($skip) {
68 $this->skipcleaningtempdir = $skip;
69 }
2de3539b
EL
70}
71
77547b46
EL
72/**
73 * Create the directory where all the task (activity/block...) information will be stored
74 */
75class create_taskbasepath_directory extends backup_execution_step {
76
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}
85
86/**
39b5371c 87 * Abstract structure step, parent of all the activity structure steps. Used to wrap the
060df4c8
EL
88 * activity structure definition within the main <activity ...> tag. Also provides
89 * subplugin support for activities (that must be properly declared)
77547b46
EL
90 */
91abstract class backup_activity_structure_step extends backup_structure_step {
92
4abf04ea
EL
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) {
060df4c8
EL
103
104 global $CFG;
105
4abf04ea 106 // Check the requested subplugintype is a valid one
060df4c8
EL
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);
4abf04ea
EL
112 if (!array_key_exists($subplugintype, $subplugins)) {
113 throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
060df4c8
EL
114 }
115
116 // Arrived here, subplugin is correct, let's create the optigroup
4abf04ea 117 $optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
060df4c8 118 $optigroup = new backup_optigroup($optigroupname, null, $multiple);
f2e34ee5 119 $element->add_child($optigroup); // Add optigroup to stay connected since beginning
060df4c8 120
39b5371c 121 // Get all the optigroup_elements, looking across all the subplugin dirs
4abf04ea 122 $subpluginsdirs = get_plugin_list($subplugintype);
060df4c8 123 foreach ($subpluginsdirs as $name => $subpluginsdir) {
4abf04ea 124 $classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
060df4c8
EL
125 $backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
126 if (file_exists($backupfile)) {
127 require_once($backupfile);
f2e34ee5
EL
128 $backupsubplugin = new $classname($subplugintype, $name, $optigroup);
129 // Add subplugin returned structure to optigroup
130 $backupsubplugin->define_subplugin_structure($element->get_name());
060df4c8
EL
131 }
132 }
060df4c8
EL
133 }
134
4abf04ea
EL
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 */
77547b46
EL
139 protected function prepare_activity_structure($activitystructure) {
140
141 // Create the wrap element
142 $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);
143
144 // Build the tree
145 $activity->add_child($activitystructure);
146
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()));
153
154 $activity->set_source_array($activityarr);
155
156 // Return the root element (activity)
157 return $activity;
158 }
159}
160
767cb7f0
EL
161/**
162 * Abstract structure step, to be used by all the activities using core questions stuff
163 * (namelu quiz module), supporting question plugins, states and sessions
164 */
165abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
166
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 }
180
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
183
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 :-(
187
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'));
193
194 // Build the tree
195 $element->add_child($states);
196 $states->add_child($state);
197
198 // Set the sources
199 $state->set_source_table('question_states', array('attempt' => '../../' . $questionattemptname));
200
201 // Annotate ids
202 $state->annotate_ids('question', 'question');
203 }
204
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 }
218
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'));
224
225 // Build the tree
226 $element->add_child($sessions);
227 $sessions->add_child($session);
228
229 // Set the sources
230 $session->set_source_table('question_sessions', array('attemptid' => '../../' . $questionattemptname));
231
232 // Annotate ids
233 $session->annotate_ids('question', 'questionid');
234
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 }
239}
240
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 */
246class backup_calculate_question_categories extends backup_execution_step {
247
248 protected function define_execution() {
249 backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());
250 }
251}
252
253/**
254 * backup structure step in charge of deleting all the questions annotated
255 * in the backup_ids_temp table
256 */
257class backup_delete_temp_questions extends backup_execution_step {
258
259 protected function define_execution() {
260 backup_question_dbops::delete_temp_questions($this->get_backupid());
261 }
262}
263
77547b46 264/**
39b5371c 265 * Abstract structure step, parent of all the block structure steps. Used to wrap the
77547b46
EL
266 * block structure definition within the main <block ...> tag
267 */
268abstract class backup_block_structure_step extends backup_structure_step {
269
270 protected function prepare_block_structure($blockstructure) {
271
272 // Create the wrap element
273 $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);
274
275 // Build the tree
276 $block->add_child($blockstructure);
277
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()));
283
284 $block->set_source_array($blockarr);
285
286 // Return the root element (block)
287 return $block;
288 }
289}
290
291/**
292 * structure step that will generate the module.xml file for the activity,
39b5371c 293 * accumulating various information about the activity, annotating groupings
77547b46
EL
294 * and completion/avail conf
295 */
296class backup_module_structure_step extends backup_structure_step {
297
298 protected function define_structure() {
299
300 // Define each element separated
301
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'));
308
309 $availinfo = new backup_nested_element('availability_info');
310 $availability = new backup_nested_element('availability', array('id'), array(
311 'sourcecmid', 'requiredcompletion', 'gradeitemid', 'grademin', 'grademax'));
312
313 // Define the tree
314 $module->add_child($availinfo);
315 $availinfo->add_child($availability);
316
317 // Set the sources
318
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));
325
326 $availability->set_source_table('course_modules_availability', array('coursemoduleid' => backup::VAR_MODID));
327
328 // Define annotations
329 $module->annotate_ids('grouping', 'groupingid');
330
331 // Return the root element ($module)
332 return $module;
333 }
334}
335
336/**
39b5371c 337 * structure step that will generate the section.xml file for the section
77547b46
EL
338 * annotating files
339 */
340class backup_section_structure_step extends backup_structure_step {
341
342 protected function define_structure() {
343
344 // Define each element separated
345
346 $section = new backup_nested_element('section', array('id'), array(
e34a326f 347 'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible'));
77547b46
EL
348
349 // Define sources
350
351 $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
352
cd00f9b7
EL
353 // Aliases
354 $section->set_source_alias('section', 'number');
355
77547b46 356 // Set annotations
64f93798 357 $section->annotate_files('course', 'section', 'id');
77547b46
EL
358
359 return $section;
360 }
361}
362
363/**
364 * structure step that will generate the course.xml file for the course, including
df997f84 365 * course category reference, tags, modules restriction information
77547b46
EL
366 * and some annotations (files & groupings)
367 */
368class backup_course_structure_step extends backup_structure_step {
369
370 protected function define_structure() {
371 global $DB;
372
373 // Define each element separated
374
375 $course = new backup_nested_element('course', array('id', 'contextid'), array(
df997f84 376 'shortname', 'fullname', 'idnumber',
77547b46 377 'summary', 'summaryformat', 'format', 'showgrades',
58f8ca39
EL
378 'newsitems', 'startdate', 'numsections',
379 'marker', 'maxbytes', 'legacyfiles', 'showreports',
77547b46 380 'visible', 'hiddensections', 'groupmode', 'groupmodeforce',
df997f84
PS
381 'defaultgroupingid', 'lang', 'theme',
382 'timecreated', 'timemodified',
383 'requested', 'restrictmodules',
395dae30 384 'enablecompletion', 'completionstartonenrol', 'completionnotify'));
77547b46
EL
385
386 $category = new backup_nested_element('category', array('id'), array(
387 'name', 'description'));
388
389 $tags = new backup_nested_element('tags');
390
391 $tag = new backup_nested_element('tag', array('id'), array(
392 'name', 'rawname'));
393
394 $allowedmodules = new backup_nested_element('allowed_modules');
395
ce28d999 396 $module = new backup_nested_element('module', array(), array('modulename'));
77547b46
EL
397
398 // Build the tree
399
400 $course->add_child($category);
401
402 $course->add_child($tags);
403 $tags->add_child($tag);
404
405 $course->add_child($allowedmodules);
406 $allowedmodules->add_child($module);
407
408 // Set the sources
409
410 $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
411 $courserec->contextid = $this->task->get_contextid();
412
413 $course->set_source_array(array($courserec));
414
415 $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));
416
417 $category->set_source_array(array($categoryrec));
418
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(
c0bd6249 424 backup_helper::is_sqlparam('course'),
77547b46
EL
425 backup::VAR_PARENTID));
426
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));
431
432 // Some annotations
433
77547b46
EL
434 $course->annotate_ids('grouping', 'defaultgroupingid');
435
64f93798
PS
436 $course->annotate_files('course', 'summary', null);
437 $course->annotate_files('course', 'legacy', null);
77547b46
EL
438
439 // Return root element ($course)
440
441 return $course;
442 }
443}
444
cb34c4cd
PS
445/**
446 * structure step that will generate the enrolments.xml file for the given course
447 */
448class backup_enrolments_structure_step extends backup_structure_step {
449
450 protected function define_structure() {
451
452 // To know if we are including users
453 $users = $this->get_setting_value('users');
454
455 // Define each element separated
456
457 $enrolments = new backup_nested_element('enrolments');
458
459 $enrols = new backup_nested_element('enrols');
460
461 $enrol = new backup_nested_element('enrol', array('id'), array(
101bd9c9 462 'enrol', 'status', 'sortorder', 'name', 'enrolperiod', 'enrolstartdate',
cb34c4cd
PS
463 'enrolenddate', 'expirynotify', 'expirytreshold', 'notifyall',
464 'password', 'cost', 'currency', 'roleid', 'customint1', 'customint2', 'customint3',
465 'customint4', 'customchar1', 'customchar2', 'customdec1', 'customdec2',
466 'customtext1', 'customtext2', 'timecreated', 'timemodified'));
467
468 $userenrolments = new backup_nested_element('user_enrolments');
469
470 $enrolment = new backup_nested_element('enrolment', array('id'), array(
101bd9c9 471 'status', 'userid', 'timestart', 'timeend', 'modifierid',
cb34c4cd
PS
472 'timemodified'));
473
474 // Build the tree
475 $enrolments->add_child($enrols);
476 $enrols->add_child($enrol);
477 $enrol->add_child($userenrolments);
478 $userenrolments->add_child($enrolment);
479
480 // Define sources
481
482 $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID));
483
101bd9c9 484 // User enrolments only added only if users included
cb34c4cd
PS
485 if ($users) {
486 $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));
02eca29c 487 $enrolment->annotate_ids('user', 'userid');
cb34c4cd
PS
488 }
489
902944a8
PS
490 $enrol->annotate_ids('role', 'roleid');
491
cb34c4cd
PS
492 //TODO: let plugins annotate custom fields too and add more children
493
494 return $enrolments;
495 }
496}
497
77547b46
EL
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 */
502class backup_roles_structure_step extends backup_structure_step {
503
504 protected function define_structure() {
505
506 // To know if we are including role assignments
507 $roleassignments = $this->get_setting_value('role_assignments');
508
509 // Define each element separated
510
511 $roles = new backup_nested_element('roles');
512
513 $overrides = new backup_nested_element('role_overrides');
514
515 $override = new backup_nested_element('override', array('id'), array(
516 'roleid', 'capability', 'permission', 'timemodified',
517 'modifierid'));
518
519 $assignments = new backup_nested_element('role_assignments');
520
521 $assignment = new backup_nested_element('assignment', array('id'), array(
cb34c4cd 522 'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid',
77547b46
EL
523 'sortorder'));
524
525 // Build the tree
526 $roles->add_child($overrides);
527 $roles->add_child($assignments);
528
529 $overrides->add_child($override);
530 $assignments->add_child($assignment);
531
532 // Define sources
533
534 $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));
535
536 // Assignments only added if specified
537 if ($roleassignments) {
538 $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));
539 }
540
541 // Define id annotations
542 $override->annotate_ids('role', 'roleid');
543
544 $assignment->annotate_ids('role', 'roleid');
545
546 $assignment->annotate_ids('user', 'userid');
547
cb34c4cd
PS
548 //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)
549
77547b46
EL
550 return $roles;
551 }
552}
553
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 */
559class backup_final_roles_structure_step extends backup_structure_step {
560
561 protected function define_structure() {
562
563 // Define elements
564
565 $rolesdef = new backup_nested_element('roles_definition');
566
567 $role = new backup_nested_element('role', array('id'), array(
568 'name', 'shortname', 'nameincourse', 'description',
569 'sortorder', 'archetype'));
570
571 // Build the tree
572
573 $rolesdef->add_child($role);
574
575 // Define sources
576
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));
583
584 // Return main element (rolesdef)
585 return $rolesdef;
586 }
587}
588
589/**
590 * structure step that will generate the scales.xml containing the
591 * list of scales used along the whole backup process.
592 */
593class backup_final_scales_structure_step extends backup_structure_step {
594
595 protected function define_structure() {
596
597 // Define elements
598
599 $scalesdef = new backup_nested_element('scales_definition');
600
601 $scale = new backup_nested_element('scale', array('id'), array(
602 'courseid', 'userid', 'name', 'scale',
603 'description', 'descriptionformat', 'timemodified'));
604
605 // Build the tree
606
607 $scalesdef->add_child($scale);
608
609 // Define sources
610
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));
616
3a1cccc6
EL
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);
619
77547b46
EL
620 // Return main element (scalesdef)
621 return $scalesdef;
622 }
623}
624
625/**
626 * structure step that will generate the outcomes.xml containing the
627 * list of outcomes used along the whole backup process.
628 */
629class backup_final_outcomes_structure_step extends backup_structure_step {
630
631 protected function define_structure() {
632
633 // Define elements
634
635 $outcomesdef = new backup_nested_element('outcomes_definition');
636
637 $outcome = new backup_nested_element('outcome', array('id'), array(
638 'courseid', 'userid', 'shortname', 'fullname',
639 'scaleid', 'description', 'descriptionformat', 'timecreated',
640 'timemodified','usermodified'));
641
642 // Build the tree
643
644 $outcomesdef->add_child($outcome);
645
646 // Define sources
647
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));
653
c8730ff0
EL
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);
656
77547b46
EL
657 // Return main element (outcomesdef)
658 return $outcomesdef;
659 }
660}
661
662/**
663 * structure step in charge of constructing the filters.xml file for all the filters found
664 * in activity
665 */
666class backup_filters_structure_step extends backup_structure_step {
667
668 protected function define_structure() {
669
670 // Define each element separated
671
672 $filters = new backup_nested_element('filters');
673
674 $actives = new backup_nested_element('filter_actives');
675
676 $active = new backup_nested_element('filter_active', null, array('filter', 'active'));
677
678 $configs = new backup_nested_element('filter_configs');
679
680 $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));
681
682 // Build the tree
683
684 $filters->add_child($actives);
685 $filters->add_child($configs);
686
687 $actives->add_child($active);
688 $configs->add_child($config);
689
690 // Define sources
691
692 list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());
693
694 $active->set_source_array($activearr);
695 $config->set_source_array($configarr);
696
697 // Return the root element (filters)
698 return $filters;
699 }
700}
701
702/**
703 * structure step in charge of constructing the comments.xml file for all the comments found
704 * in a given context
705 */
706class backup_comments_structure_step extends backup_structure_step {
707
708 protected function define_structure() {
709
710 // Define each element separated
711
712 $comments = new backup_nested_element('comments');
713
714 $comment = new backup_nested_element('comment', array('id'), array(
715 'commentarea', 'itemid', 'content', 'format',
716 'userid', 'timecreated'));
717
718 // Build the tree
719
720 $comments->add_child($comment);
721
722 // Define sources
723
724 $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));
725
726 // Define id annotations
727
728 $comment->annotate_ids('user', 'userid');
729
730 // Return the root element (comments)
731 return $comments;
732 }
733}
734
315f6d8e
AD
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 */
739class backup_gradebook_structure_step extends backup_structure_step {
740
d39595cc
EL
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 }
749
315f6d8e
AD
750 protected function define_structure() {
751
752 // are we including user info?
753 $userinfo = $this->get_setting_value('users');
754
755 $gradebook = new backup_nested_element('gradebook');
756
757 //grade_letters are done in backup_activity_grades_structure_step()
758
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'));
769
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'));
777
778 //grade_categories
779 $grade_categories = new backup_nested_element('grade_categories');
0067d939 780 $grade_category = new backup_nested_element('grade_category', array('id'), array(
9a20df96
AD
781 //'courseid',
782 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
315f6d8e
AD
783 'dropload', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
784 'timecreated', 'timemodified'));
785
786 $letters = new backup_nested_element('grade_letters');
787 $letter = new backup_nested_element('grade_letter', 'id', array(
e101180d 788 'lowerboundary', 'letter'));
315f6d8e 789
b8040c83
AD
790 $grade_settings = new backup_nested_element('grade_settings');
791 $grade_setting = new backup_nested_element('grade_setting', 'id', array(
792 'name', 'value'));
793
315f6d8e
AD
794
795 // Build the tree
0067d939
AD
796 $gradebook->add_child($grade_categories);
797 $grade_categories->add_child($grade_category);
315f6d8e
AD
798
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);
803
315f6d8e
AD
804 $gradebook->add_child($letters);
805 $letters->add_child($letter);
806
b8040c83
AD
807 $gradebook->add_child($grade_settings);
808 $grade_settings->add_child($grade_setting);
809
315f6d8e
AD
810 // Define sources
811
be739b71 812 //Include manual, category and the course grade item
58328ce8 813 $grade_items_sql ="SELECT * FROM {grade_items}
be739b71
AD
814 WHERE courseid = :courseid
815 AND (itemtype='manual' OR itemtype='course' OR itemtype='category')";
3f92c2fc 816 $grade_items_params = array('courseid'=>backup::VAR_COURSEID);
be739b71 817 $grade_item->set_source_sql($grade_items_sql, $grade_items_params);
315f6d8e
AD
818
819 if ($userinfo) {
820 $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
821 }
822
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);
831
832 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
833
b8040c83
AD
834 $grade_setting->set_source_table('grade_settings', array('courseid' => backup::VAR_COURSEID));
835
76cfb124 836 // Annotations (both as final as far as they are going to be exported in next steps)
315f6d8e 837 $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
76cfb124 838 $grade_item->annotate_ids('outcomefinal', 'outcomeid');
315f6d8e 839
9a20df96
AD
840 //just in case there are any users not already annotated by the activities
841 $grade_grade->annotate_ids('userfinal', 'userid');
842
315f6d8e
AD
843 // Return the root element
844 return $gradebook;
845 }
846}
847
77547b46
EL
848/**
849 * structure step in charge if constructing the completion.xml file for all the users completion
850 * information in a given activity
851 */
852class backup_userscompletion_structure_step extends backup_structure_step {
853
854 protected function define_structure() {
855
856 // Define each element separated
857
858 $completions = new backup_nested_element('completions');
859
860 $completion = new backup_nested_element('completion', array('id'), array(
861 'userid', 'completionstate', 'viewed', 'timemodified'));
862
863 // Build the tree
864
865 $completions->add_child($completion);
866
867 // Define sources
868
869 $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
870
871 // Define id annotations
872
873 $completion->annotate_ids('user', 'userid');
874
875 // Return the root element (completions)
876 return $completions;
877 }
878}
879
880/**
881 * structure step in charge of constructing the main groups.xml file for all the groups and
882 * groupings information already annotated
883 */
884class backup_groups_structure_step extends backup_structure_step {
885
886 protected function define_structure() {
887
888 // To know if we are including users
889 $users = $this->get_setting_value('users');
890
891 // Define each element separated
892
893 $groups = new backup_nested_element('groups');
894
895 $group = new backup_nested_element('group', array('id'), array(
896 'name', 'description', 'descriptionformat', 'enrolmentkey',
897 'picture', 'hidepicture', 'timecreated', 'timemodified'));
898
899 $members = new backup_nested_element('group_members');
900
901 $member = new backup_nested_element('group_member', array('id'), array(
902 'userid', 'timeadded'));
903
904 $groupings = new backup_nested_element('groupings');
905
906 $grouping = new backup_nested_element('grouping', 'id', array(
907 'name', 'description', 'descriptionformat', 'configdata',
908 'timecreated', 'timemodified'));
909
910 $groupinggroups = new backup_nested_element('grouping_groups');
911
912 $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
913 'groupid', 'timeadded'));
914
915 // Build the tree
916
917 $groups->add_child($group);
918 $groups->add_child($groupings);
919
920 $group->add_child($members);
921 $members->add_child($member);
922
923 $groupings->add_child($grouping);
924 $grouping->add_child($groupinggroups);
925 $groupinggroups->add_child($groupinggroup);
926
927 // Define sources
928
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));
935
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 }
940
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));
947
948 $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
949
950 // Define id annotations (as final)
951
952 $member->annotate_ids('userfinal', 'userid');
953
954 // Define file annotations
955
64f93798 956 $group->annotate_files('group', 'description', 'id');
78d47b30 957 $group->annotate_files('group', 'icon', 'id');
5cc70f32 958 $grouping->annotate_files('grouping', 'description', 'id');
77547b46
EL
959
960 // Return the root element (groups)
961 return $groups;
962 }
963}
964
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 */
970class backup_users_structure_step extends backup_structure_step {
971
972 protected function define_structure() {
973 global $CFG;
974
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');
979
980 // Define each element separated
981
982 $users = new backup_nested_element('users');
983
984 // Create the array of user fields by hand, as far as we have various bits to control
985 // anonymize option, password backup, mnethostid...
986
987 // First, the fields not needing anonymization nor special handling
988 $normalfields = array(
989 'confirmed', 'policyagreed', 'deleted',
990 'lang', 'theme', 'timezone', 'firstaccess',
482aac65 991 'lastaccess', 'lastlogin', 'currentlogin',
77547b46
EL
992 'mailformat', 'maildigest', 'maildisplay', 'htmleditor',
993 'ajax', 'autosubscribe', 'trackforums', 'timecreated',
994 'timemodified', 'trustbitmask', 'screenreader');
995
996 // Then, the fields potentially needing anonymization
997 $anonfields = array(
998 'username', 'idnumber', 'firstname', 'lastname',
482aac65
EL
999 'email', 'emailstop', 'icq', 'skype',
1000 'yahoo', 'aim', 'msn', 'phone1',
1001 'phone2', 'institution', 'department', 'address',
1002 'city', 'country', 'lastip', 'picture',
c44d4aee 1003 'url', 'description', 'descriptionformat', 'imagealt', 'auth');
77547b46
EL
1004
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 }
1013
1014 // mnethosturl requires special handling (custom final element)
1015 $userfields[] = new mnethosturl_final_element('mnethosturl');
1016
1017 // password added conditionally
1018 if (!empty($CFG->includeuserpasswordsinbackup)) {
1019 $userfields[] = 'password';
1020 }
1021
1022 // Merge all the fields
1023 $userfields = array_merge($userfields, $normalfields);
1024
1025 $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1026
1027 $customfields = new backup_nested_element('custom_fields');
1028
1029 $customfield = new backup_nested_element('custom_field', array('id'), array(
1030 'field_name', 'field_type', 'field_data'));
1031
1032 $tags = new backup_nested_element('tags');
1033
1034 $tag = new backup_nested_element('tag', array('id'), array(
1035 'name', 'rawname'));
1036
1037 $preferences = new backup_nested_element('preferences');
1038
1039 $preference = new backup_nested_element('preference', array('id'), array(
1040 'name', 'value'));
1041
1042 $roles = new backup_nested_element('roles');
1043
1044 $overrides = new backup_nested_element('role_overrides');
1045
1046 $override = new backup_nested_element('override', array('id'), array(
1047 'roleid', 'capability', 'permission', 'timemodified',
1048 'modifierid'));
1049
1050 $assignments = new backup_nested_element('role_assignments');
1051
1052 $assignment = new backup_nested_element('assignment', array('id'), array(
df997f84 1053 'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
77547b46
EL
1054 'sortorder'));
1055
1056 // Build the tree
1057
1058 $users->add_child($user);
1059
1060 $user->add_child($customfields);
1061 $customfields->add_child($customfield);
1062
1063 $user->add_child($tags);
1064 $tags->add_child($tag);
1065
1066 $user->add_child($preferences);
1067 $preferences->add_child($preference);
1068
1069 $user->add_child($roles);
1070
1071 $roles->add_child($overrides);
1072 $roles->add_child($assignments);
1073
1074 $overrides->add_child($override);
1075 $assignments->add_child($assignment);
1076
1077 // Define sources
1078
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(
c0bd6249
EL
1087 backup_helper::is_sqlparam($this->get_backupid()),
1088 backup_helper::is_sqlparam('userfinal'),
1089 backup_helper::is_sqlparam(CONTEXT_USER)));
77547b46
EL
1090
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));
1098
1099 $customfield->set_source_alias('shortname', 'field_name');
1100 $customfield->set_source_alias('datatype', 'field_type');
1101 $customfield->set_source_alias('data', 'field_data');
1102
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(
c0bd6249 1108 backup_helper::is_sqlparam('user'),
77547b46
EL
1109 backup::VAR_PARENTID));
1110
1111 $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1112
1113 $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1114
1115 // Assignments only added if specified
1116 if ($roleassignments) {
1117 $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1118 }
1119
1120 // Define id annotations (as final)
1121 $override->annotate_ids('rolefinal', 'roleid');
1122 }
1123
1124 // Return root element (users)
1125 return $users;
1126 }
1127}
1128
1129/**
1130 * structure step in charge of constructing the block.xml file for one
39b5371c 1131 * given block (instance and positions). If the block has custom DB structure
77547b46
EL
1132 * that will go to a separate file (different step defined in block class)
1133 */
1134class backup_block_instance_structure_step extends backup_structure_step {
1135
1136 protected function define_structure() {
1137 global $DB;
1138
1139 // Define each element separated
1140
5f8354eb 1141 $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
61243f3a
EL
1142 'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1143 'subpagepattern', 'defaultregion', 'defaultweight', 'configdata'));
77547b46 1144
2d7cd798
EL
1145 $positions = new backup_nested_element('block_positions');
1146
1147 $position = new backup_nested_element('block_position', array('id'), array(
77547b46
EL
1148 'contextid', 'pagetype', 'subpage', 'visible',
1149 'region', 'weight'));
1150
1151 // Build the tree
1152
1153 $block->add_child($positions);
2d7cd798 1154 $positions->add_child($position);
77547b46
EL
1155
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 }
5f8354eb 1167 $blockrec->contextid = $this->task->get_contextid();
77547b46
EL
1168 // Get the version of the block
1169 $blockrec->version = $DB->get_field('block', 'version', array('name' => $this->task->get_blockname()));
1170
1171 // Define sources
1172
1173 $block->set_source_array(array($blockrec));
1174
2d7cd798 1175 $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
77547b46 1176
4a15bb76
EL
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 }
1181
77547b46
EL
1182 // Return the root element (block)
1183 return $block;
1184 }
1185}
1186
1187/**
1188 * structure step in charge of constructing the logs.xml file for all the log records found
1189 * in activity
1190 */
1191class backup_activity_logs_structure_step extends backup_structure_step {
1192
1193 protected function define_structure() {
1194
1195 // Define each element separated
1196
1197 $logs = new backup_nested_element('logs');
1198
1199 $log = new backup_nested_element('log', array('id'), array(
1200 'time', 'userid', 'ip', 'module',
1201 'action', 'url', 'info'));
1202
1203 // Build the tree
1204
1205 $logs->add_child($log);
1206
1207 // Define sources
1208
1209 $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1210
1211 // Annotations
1212 // NOTE: We don't annotate users from logs as far as they MUST be
1213 // always annotated by the activity.
1214
1215 // Return the root element (logs)
1216
1217 return $logs;
1218 }
1219}
1220
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 */
1225class backup_inforef_structure_step extends backup_structure_step {
1226
1227 protected function define_structure() {
1228
482aac65
EL
1229 // Items we want to include in the inforef file.
1230 $items = backup_helper::get_inforef_itemnames();
77547b46
EL
1231
1232 // Build the tree
1233
1234 $inforef = new backup_nested_element('inforef');
1235
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');
482aac65 1240 $element = new backup_nested_element($itemname, array(), array('id'));
77547b46
EL
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 = ?",
c0bd6249 1248 array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
77547b46
EL
1249 }
1250 }
1251
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 }
1257}
1258
1259/**
1260 * This step will get all the annotations already processed to inforef.xml file and
1261 * transform them into 'final' annotations.
1262 */
1263class move_inforef_annotations_to_final extends backup_execution_step {
1264
1265 protected function define_execution() {
1266
482aac65
EL
1267 // Items we want to include in the inforef file
1268 $items = backup_helper::get_inforef_itemnames();
77547b46
EL
1269 foreach ($items as $itemname) {
1270 // Delegate to dbops
1271 backup_structure_dbops::move_annotations_to_final($this->get_backupid(), $itemname);
1272 }
1273 }
1274}
1275
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 */
1282class backup_final_files_structure_step extends backup_structure_step {
1283
1284 protected function define_structure() {
1285
1286 // Define elements
1287
1288 $files = new backup_nested_element('files');
1289
1290 $file = new file_nested_element('file', array('id'), array(
64f93798 1291 'contenthash', 'contextid', 'component', 'filearea', 'itemid',
77547b46
EL
1292 'filepath', 'filename', 'userid', 'filesize',
1293 'mimetype', 'status', 'timecreated', 'timemodified',
1fd3ea43 1294 'source', 'author', 'license', 'sortorder'));
77547b46
EL
1295
1296 // Build the tree
1297
1298 $files->add_child($file);
1299
1300 // Define sources
1301
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));
1307
1308 return $files;
1309 }
1310}
1311
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*/
1316class backup_main_structure_step extends backup_structure_step {
1317
1318 protected function define_structure() {
1319
1320 global $CFG;
1321
1322 $info = array();
1323
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();
c3ea499d 1331 $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
77547b46 1332 $info['original_wwwroot']=$CFG->wwwroot;
482aac65 1333 $info['original_site_identifier_hash'] = md5(get_site_identifier());
77547b46 1334 $info['original_course_id'] = $this->get_courseid();
560811a9
EL
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;
76cfb124 1339 $info['original_course_contextid'] = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
3a1cccc6 1340 $info['original_system_contextid'] = get_context_instance(CONTEXT_SYSTEM)->id;
77547b46
EL
1341
1342 // Get more information from controller
1343 list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($this->get_backupid());
1344
1345 // Define elements
1346
1347 $moodle_backup = new backup_nested_element('moodle_backup');
1348
1349 $information = new backup_nested_element('information', null, array(
1350 'name', 'moodle_version', 'moodle_release', 'backup_version',
c3ea499d 1351 'backup_release', 'backup_date', 'mnet_remoteusers', 'original_wwwroot',
560811a9
EL
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'));
77547b46
EL
1355
1356 $details = new backup_nested_element('details');
1357
1358 $detail = new backup_nested_element('detail', array('backup_id'), array(
1359 'type', 'format', 'interactive', 'mode',
1360 'execution', 'executiontime'));
1361
1362 $contents = new backup_nested_element('contents');
1363
1364 $activities = new backup_nested_element('activities');
1365
1366 $activity = new backup_nested_element('activity', null, array(
1367 'moduleid', 'sectionid', 'modulename', 'title',
1368 'directory'));
1369
1370 $sections = new backup_nested_element('sections');
1371
1372 $section = new backup_nested_element('section', null, array(
1373 'sectionid', 'title', 'directory'));
1374
1375 $course = new backup_nested_element('course', null, array(
1376 'courseid', 'title', 'directory'));
1377
1378 $settings = new backup_nested_element('settings');
1379
1380 $setting = new backup_nested_element('setting', null, array(
d12fd69b 1381 'level', 'section', 'activity', 'name', 'value'));
77547b46
EL
1382
1383 // Build the tree
1384
1385 $moodle_backup->add_child($information);
1386
1387 $information->add_child($details);
1388 $details->add_child($detail);
1389
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 }
1402
1403 $information->add_child($settings);
1404 $settings->add_child($setting);
1405
1406
1407 // Set the sources
1408
1409 $information->set_source_array(array((object)$info));
1410
1411 $detail->set_source_array($dinfo);
1412
1413 $activity->set_source_array($cinfo['activities']);
1414
1415 $section->set_source_array($cinfo['sections']);
1416
1417 $course->set_source_array($cinfo['course']);
1418
1419 $setting->set_source_array($sinfo);
1420
1421 // Prepare some information to be sent to main moodle_backup.xml file
1422 return $moodle_backup;
1423 }
1424
1425}
1426
ce937f99 1427/**
12c80f79 1428 * Execution step that will generate the final zip (.mbz) file with all the contents
ce937f99
EL
1429 */
1430class backup_zip_contents extends backup_execution_step {
1431
1432 protected function define_execution() {
1433
1434 // Get basepath
1435 $basepath = $this->get_basepath();
1436
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 }
1443
1444 // Add the log file if exists
1445 $logfilepath = $basepath . '.log';
1446 if (file_exists($logfilepath)) {
1447 $files['moodle_backup.log'] = $logfilepath;
1448 }
1449
12c80f79
EL
1450 // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1451 $zipfile = $basepath . '/backup.mbz';
ce937f99
EL
1452
1453 // Get the zip packer
1454 $zippacker = get_file_packer('application/zip');
1455
1456 // Zip files
1457 $zippacker->archive_to_pathname($files, $zipfile);
1458 }
1459}
1460
1461/**
1462 * This step will send the generated backup file to its final destination
1463 */
1464class backup_store_backup_file extends backup_execution_step {
1465
1466 protected function define_execution() {
1467
1468 // Get basepath
1469 $basepath = $this->get_basepath();
1470
12c80f79
EL
1471 // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1472 $zipfile = $basepath . '/backup.mbz';
ce937f99
EL
1473
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 }
1477}
1478
1479
77547b46
EL
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 */
1484class backup_activity_grade_items_to_ids extends backup_execution_step {
1485
1486 protected function define_execution() {
1487
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 }
1498}
1499
1500/**
1501 * This step will annotate all the groups belonging to already annotated groupings
1502 */
1503class backup_annotate_groups_from_groupings extends backup_execution_step {
1504
1505 protected function define_execution() {
1506 global $DB;
1507
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 }
1521}
1522
1523/**
1524 * This step will annotate all the scales belonging to already annotated outcomes
1525 */
1526class backup_annotate_scales_from_outcomes extends backup_execution_step {
1527
1528 protected function define_execution() {
1529 global $DB;
1530
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 }
1543}
1544
767cb7f0
EL
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 */
1553class backup_annotate_all_question_files extends backup_execution_step {
1554
1555 protected function define_execution() {
1556 global $DB;
1557
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 foreach($rs as $record) {
1566 // We don't need to specify filearea nor itemid as far as by
1567 // component and context it's enough to annotate the whole bank files
1568 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', null, null);
1569 }
1570 $rs->close();
1571 }
1572}
1573
1574/**
1575 * structure step in charge of constructing the questions.xml file for all the
1576 * question categories and questions required by the backup
1577 * and letters related to one activity
1578 */
1579class backup_questions_structure_step extends backup_structure_step {
1580
1581 protected function define_structure() {
1582
1583 // Define each element separated
1584
1585 $qcategories = new backup_nested_element('question_categories');
1586
1587 $qcategory = new backup_nested_element('question_category', array('id'), array(
1588 'name', 'contextid', 'contextlevel', 'contextinstanceid',
1589 'info', 'infoformat', 'stamp', 'parent',
1590 'sortorder'));
1591
1592 $questions = new backup_nested_element('questions');
1593
1594 $question = new backup_nested_element('question', array('id'), array(
1595 'parent', 'name', 'questiontext', 'questiontextformat',
1596 'generalfeedback', 'generalfeedbackformat', 'defaultgrade', 'penalty',
1597 'qtype', 'length', 'stamp', 'version',
1598 'hidden', 'timecreated', 'timemodified', 'createdby', 'modifiedby'));
1599
1600 // attach qtype plugin structure to $question element, only one allowed
1601 $this->add_plugin_structure('qtype', $question, false);
1602
1603 // Build the tree
1604
1605 $qcategories->add_child($qcategory);
1606 $qcategory->add_child($questions);
1607
1608 $questions->add_child($question);
1609
1610 // Define the sources
1611
1612 $qcategory->set_source_sql("
1613 SELECT gc.*, contextlevel, instanceid AS contextinstanceid
1614 FROM {question_categories} gc
1615 JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
1616 JOIN {context} co ON co.id = gc.contextid
1617 WHERE bi.backupid = ?
1618 AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID));
1619
1620 $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
1621
1622 // don't need to annotate ids nor files
1623
1624 return $qcategories;
1625 }
1626}
1627
1628
1629
77547b46 1630/**
76cfb124 1631 * This step will generate all the file annotations for the already
39b5371c 1632 * annotated (final) users. Need to do this here because each user
77547b46
EL
1633 * has its own context and structure tasks only are able to handle
1634 * one context. Also, this step will guarantee that every user has
1635 * its context created (req for other steps)
1636 */
1637class backup_annotate_all_user_files extends backup_execution_step {
1638
1639 protected function define_execution() {
1640 global $DB;
1641
1642 // List of fileareas we are going to annotate
76cfb124
EL
1643 $fileareas = array('profile', 'icon');
1644
1645 if ($this->get_setting_value('user_files')) { // private files only if enabled in settings
1646 $fileareas[] = 'private';
1647 }
77547b46
EL
1648
1649 // Fetch all annotated (final) users
1650 $rs = $DB->get_recordset('backup_ids_temp', array(
1651 'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
1652 foreach ($rs as $record) {
1653 $userid = $record->itemid;
1654 $userctxid = get_context_instance(CONTEXT_USER, $userid)->id;
1655 // Proceed with every user filearea
1656 foreach ($fileareas as $filearea) {
78d47b30 1657 // We don't need to specify itemid ($userid - 5th param) as far as by
77547b46 1658 // context we can get all the associated files. See MDL-22092
64f93798 1659 backup_structure_dbops::annotate_files($this->get_backupid(), $userctxid, 'user', $filearea, null);
77547b46
EL
1660 }
1661 }
1662 $rs->close();
1663 }
1664}
1665
1666/**
1667 * structure step in charge of constructing the grades.xml file for all the grade items
1668 * and letters related to one activity
1669 */
1670class backup_activity_grades_structure_step extends backup_structure_step {
1671
1672 protected function define_structure() {
1673
1674 // To know if we are including userinfo
1675 $userinfo = $this->get_setting_value('userinfo');
1676
1677 // Define each element separated
1678
1679 $book = new backup_nested_element('activity_gradebook');
1680
1681 $items = new backup_nested_element('grade_items');
1682
1683 $item = new backup_nested_element('grade_item', array('id'), array(
1684 'categoryid', 'itemname', 'itemtype', 'itemmodule',
1685 'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
1686 'calculation', 'gradetype', 'grademax', 'grademin',
1687 'scaleid', 'outcomeid', 'gradepass', 'multfactor',
1688 'plusfactor', 'aggregationcoef', 'sortorder', 'display',
1689 'decimals', 'hidden', 'locked', 'locktime',
1690 'needsupdate', 'timecreated', 'timemodified'));
1691
1692 $grades = new backup_nested_element('grade_grades');
1693
1694 $grade = new backup_nested_element('grade_grade', array('id'), array(
1695 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
1696 'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
1697 'locked', 'locktime', 'exported', 'overridden',
1698 'excluded', 'feedback', 'feedbackformat', 'information',
1699 'informationformat', 'timecreated', 'timemodified'));
1700
1701 $letters = new backup_nested_element('grade_letters');
1702
1703 $letter = new backup_nested_element('grade_letter', 'id', array(
1704 'lowerboundary', 'letter'));
1705
1706 // Build the tree
1707
1708 $book->add_child($items);
1709 $items->add_child($item);
1710
1711 $item->add_child($grades);
1712 $grades->add_child($grade);
1713
1714 $book->add_child($letters);
1715 $letters->add_child($letter);
1716
1717 // Define sources
1718
315f6d8e
AD
1719 $item->set_source_sql("SELECT gi.*
1720 FROM {grade_items} gi
1721 JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
1722 WHERE bi.backupid = ?
1723 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
77547b46
EL
1724
1725 // This only happens if we are including user info
1726 if ($userinfo) {
1727 $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
1728 }
1729
1730 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
1731
1732 // Annotations
1733
1734 $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
1735 $item->annotate_ids('outcome', 'outcomeid');
1736
1737 $grade->annotate_ids('user', 'userid');
1738 $grade->annotate_ids('user', 'usermodified');
1739
1740 // Return the root element (book)
1741
1742 return $book;
1743 }
1744}
bd39b6f2
SH
1745
1746/**
1747 * Backups up the course completion information for the course.
1748 */
1749class backup_course_completion_structure_step extends backup_structure_step {
1750
1751 protected function execute_condition() {
1752 // Check that all activities have been included
1753 if ($this->task->is_excluding_activities()) {
1754 return false;
1755 }
1756 return true;
1757 }
1758
1759 /**
1760 * The structure of the course completion backup
1761 *
1762 * @return backup_nested_element
1763 */
1764 protected function define_structure() {
1765
1766 // To know if we are including user completion info
1767 $userinfo = $this->get_setting_value('userscompletion');
1768
1769 $cc = new backup_nested_element('course_completion');
1770
1771 $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
1772 'course','criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod', 'timeend', 'gradepass', 'role'
1773 ));
1774
1775 $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
1776
1777 $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
1778 'criteriaid', 'userid','gradefinal','unenrolled','deleted','timecompleted'
1779 ));
1780
1781 $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
1782 'userid', 'course', 'deleted', 'timenotified', 'timeenrolled','timestarted','timecompleted','reaggregate'
1783 ));
1784
1785 $notify = new backup_nested_element('course_completion_notify', array('id'), array(
1786 'course','role','message','timesent'
1787 ));
1788
1789 $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
1790 'course','criteriatype','method','value'
1791 ));
1792
1793 $cc->add_child($criteria);
1794 $criteria->add_child($criteriacompletions);
1795 $criteriacompletions->add_child($criteriacomplete);
1796 $cc->add_child($coursecompletions);
1797 $cc->add_child($notify);
1798 $cc->add_child($aggregatemethod);
1799
1800 // We need to get the courseinstances shortname rather than an ID for restore
9404c7db 1801 $criteria->set_source_sql("SELECT ccc.*, c.shortname AS courseinstanceshortname
bd39b6f2
SH
1802 FROM {course_completion_criteria} ccc
1803 LEFT JOIN {course} c ON c.id = ccc.courseinstance
1804 WHERE ccc.course = ?", array(backup::VAR_COURSEID));
1805
1806
1807 $notify->set_source_table('course_completion_notify', array('course' => backup::VAR_COURSEID));
1808 $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
1809
1810 if ($userinfo) {
1811 $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
1812 $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
1813 }
1814
1815 $criteria->annotate_ids('role', 'role');
1816 $criteriacomplete->annotate_ids('user', 'userid');
1817 $coursecompletions->annotate_ids('user', 'userid');
1818 $notify->annotate_ids('role', 'role');
1819
1820 return $cc;
1821
1822 }
767cb7f0 1823}