MDL-27438 backup - on course backups add course gropus and groupings
[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);
4ab111b9 128 $backupsubplugin = new $classname($subplugintype, $name, $optigroup, $this);
f2e34ee5
EL
129 // Add subplugin returned structure to optigroup
130 $backupsubplugin->define_subplugin_structure($element->get_name());
060df4c8
EL
131 }
132 }
060df4c8
EL
133 }
134
4ab111b9
EL
135 /**
136 * As far as activity backup steps are implementing backup_subplugin stuff, they need to
137 * have the parent task available for wrapping purposes (get course/context....)
138 */
139 public function get_task() {
140 return $this->task;
141 }
142
4abf04ea
EL
143 /**
144 * Wraps any activity backup structure within the common 'activity' element
145 * that will include common to all activities information like id, context...
146 */
77547b46
EL
147 protected function prepare_activity_structure($activitystructure) {
148
149 // Create the wrap element
150 $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);
151
152 // Build the tree
153 $activity->add_child($activitystructure);
154
155 // Set the source
156 $activityarr = array((object)array(
157 'id' => $this->task->get_activityid(),
158 'moduleid' => $this->task->get_moduleid(),
159 'modulename' => $this->task->get_modulename(),
160 'contextid' => $this->task->get_contextid()));
161
162 $activity->set_source_array($activityarr);
163
164 // Return the root element (activity)
165 return $activity;
166 }
167}
168
767cb7f0
EL
169/**
170 * Abstract structure step, to be used by all the activities using core questions stuff
41941110 171 * (namely quiz module), supporting question plugins, states and sessions
767cb7f0
EL
172 */
173abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
174
175 /**
176 * Attach to $element (usually attempts) the needed backup structures
bea1a6a7 177 * for question_usages and all the associated data.
767cb7f0 178 */
bea1a6a7 179 protected function add_question_usages($element, $usageidname) {
d6522c33
TH
180 global $CFG;
181 require_once($CFG->dirroot . '/question/engine/lib.php');
182
767cb7f0
EL
183 // Check $element is one nested_backup_element
184 if (! $element instanceof backup_nested_element) {
185 throw new backup_step_exception('question_states_bad_parent_element', $element);
186 }
d6522c33
TH
187 if (! $element->get_final_element($usageidname)) {
188 throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname);
767cb7f0
EL
189 }
190
bea1a6a7 191 $quba = new backup_nested_element('question_usage', array('id'),
c749527b 192 array('component', 'preferredbehaviour'));
767cb7f0 193
bea1a6a7
TH
194 $qas = new backup_nested_element('question_attempts');
195 $qa = new backup_nested_element('question_attempt', array('id'), array(
196 'slot', 'behaviour', 'questionid', 'maxmark', 'minfraction',
197 'flagged', 'questionsummary', 'rightanswer', 'responsesummary',
198 'timemodified'));
767cb7f0 199
bea1a6a7
TH
200 $steps = new backup_nested_element('steps');
201 $step = new backup_nested_element('step', array('id'), array(
202 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid'));
767cb7f0 203
c749527b
TH
204 $response = new backup_nested_element('response');
205 $variable = new backup_nested_element('variable', null, array('name', 'value'));
767cb7f0
EL
206
207 // Build the tree
bea1a6a7
TH
208 $element->add_child($quba);
209 $quba->add_child($qas);
210 $qas->add_child($qa);
211 $qa->add_child($steps);
212 $steps->add_child($step);
c749527b
TH
213 $step->add_child($response);
214 $response->add_child($variable);
767cb7f0
EL
215
216 // Set the sources
bea1a6a7 217 $quba->set_source_table('question_usages',
d6522c33 218 array('id' => '../' . $usageidname));
c749527b
TH
219 $qa->set_source_sql('
220 SELECT *
221 FROM {question_attempts}
222 WHERE questionusageid = :questionusageid
223 ORDER BY slot',
bea1a6a7 224 array('questionusageid' => backup::VAR_PARENTID));
c749527b
TH
225 $step->set_source_sql('
226 SELECT *
227 FROM {question_attempt_steps}
228 WHERE questionattemptid = :questionattemptid
229 ORDER BY sequencenumber',
bea1a6a7 230 array('questionattemptid' => backup::VAR_PARENTID));
c749527b 231 $variable->set_source_table('question_attempt_step_data',
bea1a6a7 232 array('attemptstepid' => backup::VAR_PARENTID));
767cb7f0
EL
233
234 // Annotate ids
bea1a6a7 235 $qa->annotate_ids('question', 'questionid');
c749527b 236 $step->annotate_ids('user', 'userid');
767cb7f0
EL
237
238 // Annotate files
c749527b 239 $fileareas = question_engine::get_all_response_file_areas();
bea1a6a7
TH
240 foreach ($fileareas as $filearea) {
241 $step->annotate_files('question', $filearea, 'id');
242 }
767cb7f0
EL
243 }
244}
245
bea1a6a7 246
767cb7f0
EL
247/**
248 * backup structure step in charge of calculating the categories to be
249 * included in backup, based in the context being backuped (module/course)
250 * and the already annotated questions present in backup_ids_temp
251 */
252class backup_calculate_question_categories extends backup_execution_step {
253
254 protected function define_execution() {
255 backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());
256 }
257}
258
259/**
260 * backup structure step in charge of deleting all the questions annotated
261 * in the backup_ids_temp table
262 */
263class backup_delete_temp_questions extends backup_execution_step {
264
265 protected function define_execution() {
266 backup_question_dbops::delete_temp_questions($this->get_backupid());
267 }
268}
269
77547b46 270/**
39b5371c 271 * Abstract structure step, parent of all the block structure steps. Used to wrap the
77547b46
EL
272 * block structure definition within the main <block ...> tag
273 */
274abstract class backup_block_structure_step extends backup_structure_step {
275
276 protected function prepare_block_structure($blockstructure) {
277
278 // Create the wrap element
279 $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);
280
281 // Build the tree
282 $block->add_child($blockstructure);
283
284 // Set the source
285 $blockarr = array((object)array(
286 'id' => $this->task->get_blockid(),
287 'blockname' => $this->task->get_blockname(),
288 'contextid' => $this->task->get_contextid()));
289
290 $block->set_source_array($blockarr);
291
292 // Return the root element (block)
293 return $block;
294 }
295}
296
297/**
298 * structure step that will generate the module.xml file for the activity,
39b5371c 299 * accumulating various information about the activity, annotating groupings
77547b46
EL
300 * and completion/avail conf
301 */
302class backup_module_structure_step extends backup_structure_step {
303
304 protected function define_structure() {
305
306 // Define each element separated
307
308 $module = new backup_nested_element('module', array('id', 'version'), array(
309 'modulename', 'sectionid', 'sectionnumber', 'idnumber',
310 'added', 'score', 'indent', 'visible',
311 'visibleold', 'groupmode', 'groupingid', 'groupmembersonly',
312 'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
313 'availablefrom', 'availableuntil', 'showavailability'));
314
315 $availinfo = new backup_nested_element('availability_info');
316 $availability = new backup_nested_element('availability', array('id'), array(
317 'sourcecmid', 'requiredcompletion', 'gradeitemid', 'grademin', 'grademax'));
318
a90659d6
EL
319 // attach format plugin structure to $module element, only one allowed
320 $this->add_plugin_structure('format', $module, false);
321
571ae252
DM
322 // attach plagiarism plugin structure to $module element, only one allowed
323 $this->add_plugin_structure('plagiarism', $module, false);
324
77547b46
EL
325 // Define the tree
326 $module->add_child($availinfo);
327 $availinfo->add_child($availability);
328
329 // Set the sources
330
331 $module->set_source_sql('
332 SELECT cm.*, m.version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
333 FROM {course_modules} cm
334 JOIN {modules} m ON m.id = cm.module
335 JOIN {course_sections} s ON s.id = cm.section
336 WHERE cm.id = ?', array(backup::VAR_MODID));
337
338 $availability->set_source_table('course_modules_availability', array('coursemoduleid' => backup::VAR_MODID));
339
340 // Define annotations
341 $module->annotate_ids('grouping', 'groupingid');
342
343 // Return the root element ($module)
344 return $module;
345 }
346}
347
348/**
39b5371c 349 * structure step that will generate the section.xml file for the section
77547b46
EL
350 * annotating files
351 */
352class backup_section_structure_step extends backup_structure_step {
353
354 protected function define_structure() {
355
356 // Define each element separated
357
358 $section = new backup_nested_element('section', array('id'), array(
e34a326f 359 'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible'));
77547b46 360
a90659d6
EL
361 // attach format plugin structure to $section element, only one allowed
362 $this->add_plugin_structure('format', $section, false);
363
77547b46
EL
364 // Define sources
365
366 $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
367
cd00f9b7
EL
368 // Aliases
369 $section->set_source_alias('section', 'number');
370
77547b46 371 // Set annotations
64f93798 372 $section->annotate_files('course', 'section', 'id');
77547b46
EL
373
374 return $section;
375 }
376}
377
378/**
379 * structure step that will generate the course.xml file for the course, including
df997f84 380 * course category reference, tags, modules restriction information
77547b46
EL
381 * and some annotations (files & groupings)
382 */
383class backup_course_structure_step extends backup_structure_step {
384
385 protected function define_structure() {
386 global $DB;
387
388 // Define each element separated
389
390 $course = new backup_nested_element('course', array('id', 'contextid'), array(
df997f84 391 'shortname', 'fullname', 'idnumber',
77547b46 392 'summary', 'summaryformat', 'format', 'showgrades',
58f8ca39
EL
393 'newsitems', 'startdate', 'numsections',
394 'marker', 'maxbytes', 'legacyfiles', 'showreports',
77547b46 395 'visible', 'hiddensections', 'groupmode', 'groupmodeforce',
df997f84
PS
396 'defaultgroupingid', 'lang', 'theme',
397 'timecreated', 'timemodified',
398 'requested', 'restrictmodules',
395dae30 399 'enablecompletion', 'completionstartonenrol', 'completionnotify'));
77547b46
EL
400
401 $category = new backup_nested_element('category', array('id'), array(
402 'name', 'description'));
403
404 $tags = new backup_nested_element('tags');
405
406 $tag = new backup_nested_element('tag', array('id'), array(
407 'name', 'rawname'));
408
409 $allowedmodules = new backup_nested_element('allowed_modules');
410
ce28d999 411 $module = new backup_nested_element('module', array(), array('modulename'));
77547b46 412
4fda584f
EL
413 // attach format plugin structure to $course element, only one allowed
414 $this->add_plugin_structure('format', $course, false);
415
89213d00 416 // attach theme plugin structure to $course element; multiple themes can
417 // save course data (in case of user theme, legacy theme, etc)
418 $this->add_plugin_structure('theme', $course, true);
419
5e0dae12 420 // attach course report plugin structure to $course element; multiple
421 // course reports can save course data if required
422 $this->add_plugin_structure('coursereport', $course, true);
423
571ae252
DM
424 // attach plagiarism plugin structure to $course element, only one allowed
425 $this->add_plugin_structure('plagiarism', $course, false);
426
77547b46
EL
427 // Build the tree
428
429 $course->add_child($category);
430
431 $course->add_child($tags);
432 $tags->add_child($tag);
433
434 $course->add_child($allowedmodules);
435 $allowedmodules->add_child($module);
436
437 // Set the sources
438
439 $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
440 $courserec->contextid = $this->task->get_contextid();
441
442 $course->set_source_array(array($courserec));
443
444 $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));
445
446 $category->set_source_array(array($categoryrec));
447
448 $tag->set_source_sql('SELECT t.id, t.name, t.rawname
449 FROM {tag} t
450 JOIN {tag_instance} ti ON ti.tagid = t.id
451 WHERE ti.itemtype = ?
452 AND ti.itemid = ?', array(
c0bd6249 453 backup_helper::is_sqlparam('course'),
77547b46
EL
454 backup::VAR_PARENTID));
455
456 $module->set_source_sql('SELECT m.name AS modulename
457 FROM {modules} m
458 JOIN {course_allowed_modules} cam ON m.id = cam.module
459 WHERE course = ?', array(backup::VAR_COURSEID));
460
461 // Some annotations
462
77547b46
EL
463 $course->annotate_ids('grouping', 'defaultgroupingid');
464
64f93798
PS
465 $course->annotate_files('course', 'summary', null);
466 $course->annotate_files('course', 'legacy', null);
77547b46
EL
467
468 // Return root element ($course)
469
470 return $course;
471 }
472}
473
cb34c4cd
PS
474/**
475 * structure step that will generate the enrolments.xml file for the given course
476 */
477class backup_enrolments_structure_step extends backup_structure_step {
478
479 protected function define_structure() {
480
481 // To know if we are including users
482 $users = $this->get_setting_value('users');
483
484 // Define each element separated
485
486 $enrolments = new backup_nested_element('enrolments');
487
488 $enrols = new backup_nested_element('enrols');
489
490 $enrol = new backup_nested_element('enrol', array('id'), array(
101bd9c9 491 'enrol', 'status', 'sortorder', 'name', 'enrolperiod', 'enrolstartdate',
cb34c4cd
PS
492 'enrolenddate', 'expirynotify', 'expirytreshold', 'notifyall',
493 'password', 'cost', 'currency', 'roleid', 'customint1', 'customint2', 'customint3',
494 'customint4', 'customchar1', 'customchar2', 'customdec1', 'customdec2',
495 'customtext1', 'customtext2', 'timecreated', 'timemodified'));
496
497 $userenrolments = new backup_nested_element('user_enrolments');
498
499 $enrolment = new backup_nested_element('enrolment', array('id'), array(
101bd9c9 500 'status', 'userid', 'timestart', 'timeend', 'modifierid',
cb34c4cd
PS
501 'timemodified'));
502
503 // Build the tree
504 $enrolments->add_child($enrols);
505 $enrols->add_child($enrol);
506 $enrol->add_child($userenrolments);
507 $userenrolments->add_child($enrolment);
508
509 // Define sources
510
511 $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID));
512
101bd9c9 513 // User enrolments only added only if users included
cb34c4cd
PS
514 if ($users) {
515 $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));
02eca29c 516 $enrolment->annotate_ids('user', 'userid');
cb34c4cd
PS
517 }
518
902944a8
PS
519 $enrol->annotate_ids('role', 'roleid');
520
cb34c4cd
PS
521 //TODO: let plugins annotate custom fields too and add more children
522
523 return $enrolments;
524 }
525}
526
77547b46
EL
527/**
528 * structure step that will generate the roles.xml file for the given context, observing
529 * the role_assignments setting to know if that part needs to be included
530 */
531class backup_roles_structure_step extends backup_structure_step {
532
533 protected function define_structure() {
534
535 // To know if we are including role assignments
536 $roleassignments = $this->get_setting_value('role_assignments');
537
538 // Define each element separated
539
540 $roles = new backup_nested_element('roles');
541
542 $overrides = new backup_nested_element('role_overrides');
543
544 $override = new backup_nested_element('override', array('id'), array(
545 'roleid', 'capability', 'permission', 'timemodified',
546 'modifierid'));
547
548 $assignments = new backup_nested_element('role_assignments');
549
550 $assignment = new backup_nested_element('assignment', array('id'), array(
cb34c4cd 551 'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid',
77547b46
EL
552 'sortorder'));
553
554 // Build the tree
555 $roles->add_child($overrides);
556 $roles->add_child($assignments);
557
558 $overrides->add_child($override);
559 $assignments->add_child($assignment);
560
561 // Define sources
562
563 $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));
564
565 // Assignments only added if specified
566 if ($roleassignments) {
567 $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));
568 }
569
570 // Define id annotations
571 $override->annotate_ids('role', 'roleid');
572
573 $assignment->annotate_ids('role', 'roleid');
574
575 $assignment->annotate_ids('user', 'userid');
576
cb34c4cd
PS
577 //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)
578
77547b46
EL
579 return $roles;
580 }
581}
582
583/**
584 * structure step that will generate the roles.xml containing the
585 * list of roles used along the whole backup process. Just raw
586 * list of used roles from role table
587 */
588class backup_final_roles_structure_step extends backup_structure_step {
589
590 protected function define_structure() {
591
592 // Define elements
593
594 $rolesdef = new backup_nested_element('roles_definition');
595
596 $role = new backup_nested_element('role', array('id'), array(
597 'name', 'shortname', 'nameincourse', 'description',
598 'sortorder', 'archetype'));
599
600 // Build the tree
601
602 $rolesdef->add_child($role);
603
604 // Define sources
605
606 $role->set_source_sql("SELECT r.*, rn.name AS nameincourse
607 FROM {role} r
608 JOIN {backup_ids_temp} bi ON r.id = bi.itemid
609 LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?
610 WHERE bi.backupid = ?
611 AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));
612
613 // Return main element (rolesdef)
614 return $rolesdef;
615 }
616}
617
618/**
619 * structure step that will generate the scales.xml containing the
620 * list of scales used along the whole backup process.
621 */
622class backup_final_scales_structure_step extends backup_structure_step {
623
624 protected function define_structure() {
625
626 // Define elements
627
628 $scalesdef = new backup_nested_element('scales_definition');
629
630 $scale = new backup_nested_element('scale', array('id'), array(
631 'courseid', 'userid', 'name', 'scale',
632 'description', 'descriptionformat', 'timemodified'));
633
634 // Build the tree
635
636 $scalesdef->add_child($scale);
637
638 // Define sources
639
640 $scale->set_source_sql("SELECT s.*
641 FROM {scale} s
642 JOIN {backup_ids_temp} bi ON s.id = bi.itemid
643 WHERE bi.backupid = ?
644 AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));
645
3a1cccc6
EL
646 // Annotate scale files (they store files in system context, so pass it instead of default one)
647 $scale->annotate_files('grade', 'scale', 'id', get_context_instance(CONTEXT_SYSTEM)->id);
648
77547b46
EL
649 // Return main element (scalesdef)
650 return $scalesdef;
651 }
652}
653
654/**
655 * structure step that will generate the outcomes.xml containing the
656 * list of outcomes used along the whole backup process.
657 */
658class backup_final_outcomes_structure_step extends backup_structure_step {
659
660 protected function define_structure() {
661
662 // Define elements
663
664 $outcomesdef = new backup_nested_element('outcomes_definition');
665
666 $outcome = new backup_nested_element('outcome', array('id'), array(
667 'courseid', 'userid', 'shortname', 'fullname',
668 'scaleid', 'description', 'descriptionformat', 'timecreated',
669 'timemodified','usermodified'));
670
671 // Build the tree
672
673 $outcomesdef->add_child($outcome);
674
675 // Define sources
676
677 $outcome->set_source_sql("SELECT o.*
678 FROM {grade_outcomes} o
679 JOIN {backup_ids_temp} bi ON o.id = bi.itemid
680 WHERE bi.backupid = ?
681 AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));
682
c8730ff0
EL
683 // Annotate outcome files (they store files in system context, so pass it instead of default one)
684 $outcome->annotate_files('grade', 'outcome', 'id', get_context_instance(CONTEXT_SYSTEM)->id);
685
77547b46
EL
686 // Return main element (outcomesdef)
687 return $outcomesdef;
688 }
689}
690
691/**
692 * structure step in charge of constructing the filters.xml file for all the filters found
693 * in activity
694 */
695class backup_filters_structure_step extends backup_structure_step {
696
697 protected function define_structure() {
698
699 // Define each element separated
700
701 $filters = new backup_nested_element('filters');
702
703 $actives = new backup_nested_element('filter_actives');
704
705 $active = new backup_nested_element('filter_active', null, array('filter', 'active'));
706
707 $configs = new backup_nested_element('filter_configs');
708
709 $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));
710
711 // Build the tree
712
713 $filters->add_child($actives);
714 $filters->add_child($configs);
715
716 $actives->add_child($active);
717 $configs->add_child($config);
718
719 // Define sources
720
721 list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());
722
723 $active->set_source_array($activearr);
724 $config->set_source_array($configarr);
725
726 // Return the root element (filters)
727 return $filters;
728 }
729}
730
731/**
732 * structure step in charge of constructing the comments.xml file for all the comments found
733 * in a given context
734 */
735class backup_comments_structure_step extends backup_structure_step {
736
737 protected function define_structure() {
738
739 // Define each element separated
740
741 $comments = new backup_nested_element('comments');
742
743 $comment = new backup_nested_element('comment', array('id'), array(
744 'commentarea', 'itemid', 'content', 'format',
745 'userid', 'timecreated'));
746
747 // Build the tree
748
749 $comments->add_child($comment);
750
751 // Define sources
752
753 $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));
754
755 // Define id annotations
756
757 $comment->annotate_ids('user', 'userid');
758
759 // Return the root element (comments)
760 return $comments;
761 }
762}
763
315f6d8e
AD
764/**
765 * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course
766 * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step
767 */
768class backup_gradebook_structure_step extends backup_structure_step {
769
d39595cc
EL
770 /**
771 * We need to decide conditionally, based on dynamic information
772 * about the execution of this step. Only will be executed if all
773 * the module gradeitems have been already included in backup
774 */
775 protected function execute_condition() {
776 return backup_plan_dbops::require_gradebook_backup($this->get_courseid(), $this->get_backupid());
777 }
778
315f6d8e
AD
779 protected function define_structure() {
780
781 // are we including user info?
782 $userinfo = $this->get_setting_value('users');
783
784 $gradebook = new backup_nested_element('gradebook');
785
786 //grade_letters are done in backup_activity_grades_structure_step()
787
788 //calculated grade items
789 $grade_items = new backup_nested_element('grade_items');
790 $grade_item = new backup_nested_element('grade_item', array('id'), array(
791 'categoryid', 'itemname', 'itemtype', 'itemmodule',
792 'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
793 'calculation', 'gradetype', 'grademax', 'grademin',
794 'scaleid', 'outcomeid', 'gradepass', 'multfactor',
795 'plusfactor', 'aggregationcoef', 'sortorder', 'display',
796 'decimals', 'hidden', 'locked', 'locktime',
797 'needsupdate', 'timecreated', 'timemodified'));
798
799 $grade_grades = new backup_nested_element('grade_grades');
800 $grade_grade = new backup_nested_element('grade_grade', array('id'), array(
801 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
802 'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
803 'locked', 'locktime', 'exported', 'overridden',
804 'excluded', 'feedback', 'feedbackformat', 'information',
805 'informationformat', 'timecreated', 'timemodified'));
806
807 //grade_categories
808 $grade_categories = new backup_nested_element('grade_categories');
0067d939 809 $grade_category = new backup_nested_element('grade_category', array('id'), array(
9a20df96
AD
810 //'courseid',
811 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
315f6d8e 812 'dropload', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
90c29857 813 'timecreated', 'timemodified', 'hidden'));
315f6d8e
AD
814
815 $letters = new backup_nested_element('grade_letters');
816 $letter = new backup_nested_element('grade_letter', 'id', array(
e101180d 817 'lowerboundary', 'letter'));
315f6d8e 818
b8040c83
AD
819 $grade_settings = new backup_nested_element('grade_settings');
820 $grade_setting = new backup_nested_element('grade_setting', 'id', array(
821 'name', 'value'));
822
315f6d8e
AD
823
824 // Build the tree
0067d939
AD
825 $gradebook->add_child($grade_categories);
826 $grade_categories->add_child($grade_category);
315f6d8e
AD
827
828 $gradebook->add_child($grade_items);
829 $grade_items->add_child($grade_item);
830 $grade_item->add_child($grade_grades);
831 $grade_grades->add_child($grade_grade);
832
315f6d8e
AD
833 $gradebook->add_child($letters);
834 $letters->add_child($letter);
835
b8040c83
AD
836 $gradebook->add_child($grade_settings);
837 $grade_settings->add_child($grade_setting);
838
315f6d8e
AD
839 // Define sources
840
be739b71 841 //Include manual, category and the course grade item
58328ce8 842 $grade_items_sql ="SELECT * FROM {grade_items}
be739b71
AD
843 WHERE courseid = :courseid
844 AND (itemtype='manual' OR itemtype='course' OR itemtype='category')";
3f92c2fc 845 $grade_items_params = array('courseid'=>backup::VAR_COURSEID);
be739b71 846 $grade_item->set_source_sql($grade_items_sql, $grade_items_params);
315f6d8e
AD
847
848 if ($userinfo) {
849 $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
850 }
851
852 $grade_category_sql = "SELECT gc.*, gi.sortorder
853 FROM {grade_categories} gc
854 JOIN {grade_items} gi ON (gi.iteminstance = gc.id)
855 WHERE gc.courseid = :courseid
856 AND (gi.itemtype='course' OR gi.itemtype='category')
857 ORDER BY gc.parent ASC";//need parent categories before their children
858 $grade_category_params = array('courseid'=>backup::VAR_COURSEID);
859 $grade_category->set_source_sql($grade_category_sql, $grade_category_params);
860
861 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
862
b8040c83
AD
863 $grade_setting->set_source_table('grade_settings', array('courseid' => backup::VAR_COURSEID));
864
76cfb124 865 // Annotations (both as final as far as they are going to be exported in next steps)
315f6d8e 866 $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
76cfb124 867 $grade_item->annotate_ids('outcomefinal', 'outcomeid');
315f6d8e 868
9a20df96
AD
869 //just in case there are any users not already annotated by the activities
870 $grade_grade->annotate_ids('userfinal', 'userid');
871
315f6d8e
AD
872 // Return the root element
873 return $gradebook;
874 }
875}
876
77547b46
EL
877/**
878 * structure step in charge if constructing the completion.xml file for all the users completion
879 * information in a given activity
880 */
881class backup_userscompletion_structure_step extends backup_structure_step {
882
883 protected function define_structure() {
884
885 // Define each element separated
886
887 $completions = new backup_nested_element('completions');
888
889 $completion = new backup_nested_element('completion', array('id'), array(
890 'userid', 'completionstate', 'viewed', 'timemodified'));
891
892 // Build the tree
893
894 $completions->add_child($completion);
895
896 // Define sources
897
898 $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
899
900 // Define id annotations
901
902 $completion->annotate_ids('user', 'userid');
903
904 // Return the root element (completions)
905 return $completions;
906 }
907}
908
909/**
910 * structure step in charge of constructing the main groups.xml file for all the groups and
911 * groupings information already annotated
912 */
913class backup_groups_structure_step extends backup_structure_step {
914
915 protected function define_structure() {
916
917 // To know if we are including users
918 $users = $this->get_setting_value('users');
919
920 // Define each element separated
921
922 $groups = new backup_nested_element('groups');
923
924 $group = new backup_nested_element('group', array('id'), array(
925 'name', 'description', 'descriptionformat', 'enrolmentkey',
926 'picture', 'hidepicture', 'timecreated', 'timemodified'));
927
928 $members = new backup_nested_element('group_members');
929
930 $member = new backup_nested_element('group_member', array('id'), array(
931 'userid', 'timeadded'));
932
933 $groupings = new backup_nested_element('groupings');
934
935 $grouping = new backup_nested_element('grouping', 'id', array(
936 'name', 'description', 'descriptionformat', 'configdata',
937 'timecreated', 'timemodified'));
938
939 $groupinggroups = new backup_nested_element('grouping_groups');
940
941 $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
942 'groupid', 'timeadded'));
943
944 // Build the tree
945
946 $groups->add_child($group);
947 $groups->add_child($groupings);
948
949 $group->add_child($members);
950 $members->add_child($member);
951
952 $groupings->add_child($grouping);
953 $grouping->add_child($groupinggroups);
954 $groupinggroups->add_child($groupinggroup);
955
956 // Define sources
957
958 $group->set_source_sql("
959 SELECT g.*
960 FROM {groups} g
961 JOIN {backup_ids_temp} bi ON g.id = bi.itemid
962 WHERE bi.backupid = ?
963 AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
964
965 // This only happens if we are including users
966 if ($users) {
967 $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
968 }
969
970 $grouping->set_source_sql("
971 SELECT g.*
972 FROM {groupings} g
973 JOIN {backup_ids_temp} bi ON g.id = bi.itemid
974 WHERE bi.backupid = ?
975 AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
976
977 $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
978
979 // Define id annotations (as final)
980
981 $member->annotate_ids('userfinal', 'userid');
982
983 // Define file annotations
984
64f93798 985 $group->annotate_files('group', 'description', 'id');
78d47b30 986 $group->annotate_files('group', 'icon', 'id');
5cc70f32 987 $grouping->annotate_files('grouping', 'description', 'id');
77547b46
EL
988
989 // Return the root element (groups)
990 return $groups;
991 }
992}
993
994/**
995 * structure step in charge of constructing the main users.xml file for all the users already
996 * annotated (final). Includes custom profile fields, preferences, tags, role assignments and
997 * overrides.
998 */
999class backup_users_structure_step extends backup_structure_step {
1000
1001 protected function define_structure() {
1002 global $CFG;
1003
1004 // To know if we are anonymizing users
1005 $anonymize = $this->get_setting_value('anonymize');
1006 // To know if we are including role assignments
1007 $roleassignments = $this->get_setting_value('role_assignments');
1008
1009 // Define each element separated
1010
1011 $users = new backup_nested_element('users');
1012
1013 // Create the array of user fields by hand, as far as we have various bits to control
1014 // anonymize option, password backup, mnethostid...
1015
1016 // First, the fields not needing anonymization nor special handling
1017 $normalfields = array(
1018 'confirmed', 'policyagreed', 'deleted',
1019 'lang', 'theme', 'timezone', 'firstaccess',
482aac65 1020 'lastaccess', 'lastlogin', 'currentlogin',
77547b46
EL
1021 'mailformat', 'maildigest', 'maildisplay', 'htmleditor',
1022 'ajax', 'autosubscribe', 'trackforums', 'timecreated',
1023 'timemodified', 'trustbitmask', 'screenreader');
1024
1025 // Then, the fields potentially needing anonymization
1026 $anonfields = array(
1027 'username', 'idnumber', 'firstname', 'lastname',
46505ee7 1028 'email', 'icq', 'skype',
482aac65
EL
1029 'yahoo', 'aim', 'msn', 'phone1',
1030 'phone2', 'institution', 'department', 'address',
1031 'city', 'country', 'lastip', 'picture',
c44d4aee 1032 'url', 'description', 'descriptionformat', 'imagealt', 'auth');
77547b46
EL
1033
1034 // Add anonymized fields to $userfields with custom final element
1035 foreach ($anonfields as $field) {
1036 if ($anonymize) {
1037 $userfields[] = new anonymizer_final_element($field);
1038 } else {
1039 $userfields[] = $field; // No anonymization, normally added
1040 }
1041 }
1042
1043 // mnethosturl requires special handling (custom final element)
1044 $userfields[] = new mnethosturl_final_element('mnethosturl');
1045
1046 // password added conditionally
1047 if (!empty($CFG->includeuserpasswordsinbackup)) {
1048 $userfields[] = 'password';
1049 }
1050
1051 // Merge all the fields
1052 $userfields = array_merge($userfields, $normalfields);
1053
1054 $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1055
1056 $customfields = new backup_nested_element('custom_fields');
1057
1058 $customfield = new backup_nested_element('custom_field', array('id'), array(
1059 'field_name', 'field_type', 'field_data'));
1060
1061 $tags = new backup_nested_element('tags');
1062
1063 $tag = new backup_nested_element('tag', array('id'), array(
1064 'name', 'rawname'));
1065
1066 $preferences = new backup_nested_element('preferences');
1067
1068 $preference = new backup_nested_element('preference', array('id'), array(
1069 'name', 'value'));
1070
1071 $roles = new backup_nested_element('roles');
1072
1073 $overrides = new backup_nested_element('role_overrides');
1074
1075 $override = new backup_nested_element('override', array('id'), array(
1076 'roleid', 'capability', 'permission', 'timemodified',
1077 'modifierid'));
1078
1079 $assignments = new backup_nested_element('role_assignments');
1080
1081 $assignment = new backup_nested_element('assignment', array('id'), array(
df997f84 1082 'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
77547b46
EL
1083 'sortorder'));
1084
1085 // Build the tree
1086
1087 $users->add_child($user);
1088
1089 $user->add_child($customfields);
1090 $customfields->add_child($customfield);
1091
1092 $user->add_child($tags);
1093 $tags->add_child($tag);
1094
1095 $user->add_child($preferences);
1096 $preferences->add_child($preference);
1097
1098 $user->add_child($roles);
1099
1100 $roles->add_child($overrides);
1101 $roles->add_child($assignments);
1102
1103 $overrides->add_child($override);
1104 $assignments->add_child($assignment);
1105
1106 // Define sources
1107
1108 $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
1109 FROM {user} u
1110 JOIN {backup_ids_temp} bi ON bi.itemid = u.id
1111 JOIN {context} c ON c.instanceid = u.id
1112 LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
1113 WHERE bi.backupid = ?
1114 AND bi.itemname = ?
1115 AND c.contextlevel = ?', array(
c0bd6249
EL
1116 backup_helper::is_sqlparam($this->get_backupid()),
1117 backup_helper::is_sqlparam('userfinal'),
1118 backup_helper::is_sqlparam(CONTEXT_USER)));
77547b46
EL
1119
1120 // All the rest on information is only added if we arent
1121 // in an anonymized backup
1122 if (!$anonymize) {
1123 $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data
1124 FROM {user_info_field} f
1125 JOIN {user_info_data} d ON d.fieldid = f.id
1126 WHERE d.userid = ?', array(backup::VAR_PARENTID));
1127
1128 $customfield->set_source_alias('shortname', 'field_name');
1129 $customfield->set_source_alias('datatype', 'field_type');
1130 $customfield->set_source_alias('data', 'field_data');
1131
1132 $tag->set_source_sql('SELECT t.id, t.name, t.rawname
1133 FROM {tag} t
1134 JOIN {tag_instance} ti ON ti.tagid = t.id
1135 WHERE ti.itemtype = ?
1136 AND ti.itemid = ?', array(
c0bd6249 1137 backup_helper::is_sqlparam('user'),
77547b46
EL
1138 backup::VAR_PARENTID));
1139
1140 $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1141
1142 $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1143
1144 // Assignments only added if specified
1145 if ($roleassignments) {
1146 $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1147 }
1148
1149 // Define id annotations (as final)
1150 $override->annotate_ids('rolefinal', 'roleid');
1151 }
1152
1153 // Return root element (users)
1154 return $users;
1155 }
1156}
1157
1158/**
1159 * structure step in charge of constructing the block.xml file for one
39b5371c 1160 * given block (instance and positions). If the block has custom DB structure
77547b46
EL
1161 * that will go to a separate file (different step defined in block class)
1162 */
1163class backup_block_instance_structure_step extends backup_structure_step {
1164
1165 protected function define_structure() {
1166 global $DB;
1167
1168 // Define each element separated
1169
5f8354eb 1170 $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
61243f3a
EL
1171 'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1172 'subpagepattern', 'defaultregion', 'defaultweight', 'configdata'));
77547b46 1173
2d7cd798
EL
1174 $positions = new backup_nested_element('block_positions');
1175
1176 $position = new backup_nested_element('block_position', array('id'), array(
77547b46
EL
1177 'contextid', 'pagetype', 'subpage', 'visible',
1178 'region', 'weight'));
1179
1180 // Build the tree
1181
1182 $block->add_child($positions);
2d7cd798 1183 $positions->add_child($position);
77547b46
EL
1184
1185 // Transform configdata information if needed (process links and friends)
1186 $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
1187 if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
1188 $configdata = (array)unserialize(base64_decode($blockrec->configdata));
1189 foreach ($configdata as $attribute => $value) {
1190 if (in_array($attribute, $attrstotransform)) {
1191 $configdata[$attribute] = $this->contenttransformer->process($value);
1192 }
1193 }
1194 $blockrec->configdata = base64_encode(serialize((object)$configdata));
1195 }
5f8354eb 1196 $blockrec->contextid = $this->task->get_contextid();
77547b46
EL
1197 // Get the version of the block
1198 $blockrec->version = $DB->get_field('block', 'version', array('name' => $this->task->get_blockname()));
1199
1200 // Define sources
1201
1202 $block->set_source_array(array($blockrec));
1203
2d7cd798 1204 $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
77547b46 1205
4a15bb76
EL
1206 // File anotations (for fileareas specified on each block)
1207 foreach ($this->task->get_fileareas() as $filearea) {
1208 $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);
1209 }
1210
77547b46
EL
1211 // Return the root element (block)
1212 return $block;
1213 }
1214}
1215
0f66aced
EL
1216/**
1217 * structure step in charge of constructing the logs.xml file for all the log records found
1218 * in course. Note that we are sending to backup ALL the log records having cmid = 0. That
1219 * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do
1220 * that just in case they become restored some day in the future
1221 */
1222class backup_course_logs_structure_step extends backup_structure_step {
1223
1224 protected function define_structure() {
1225
1226 // Define each element separated
1227
1228 $logs = new backup_nested_element('logs');
1229
1230 $log = new backup_nested_element('log', array('id'), array(
1231 'time', 'userid', 'ip', 'module',
1232 'action', 'url', 'info'));
1233
1234 // Build the tree
1235
1236 $logs->add_child($log);
1237
1238 // Define sources (all the records belonging to the course, having cmid = 0)
1239
1240 $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0)));
1241
1242 // Annotations
1243 // NOTE: We don't annotate users from logs as far as they MUST be
1244 // always annotated by the course (enrol, ras... whatever)
1245
1246 // Return the root element (logs)
1247
1248 return $logs;
1249 }
1250}
1251
77547b46
EL
1252/**
1253 * structure step in charge of constructing the logs.xml file for all the log records found
1254 * in activity
1255 */
1256class backup_activity_logs_structure_step extends backup_structure_step {
1257
1258 protected function define_structure() {
1259
1260 // Define each element separated
1261
1262 $logs = new backup_nested_element('logs');
1263
1264 $log = new backup_nested_element('log', array('id'), array(
1265 'time', 'userid', 'ip', 'module',
1266 'action', 'url', 'info'));
1267
1268 // Build the tree
1269
1270 $logs->add_child($log);
1271
1272 // Define sources
1273
1274 $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1275
1276 // Annotations
1277 // NOTE: We don't annotate users from logs as far as they MUST be
0f66aced 1278 // always annotated by the activity (true participants).
77547b46
EL
1279
1280 // Return the root element (logs)
1281
1282 return $logs;
1283 }
1284}
1285
1286/**
1287 * structure in charge of constructing the inforef.xml file for all the items we want
1288 * to have referenced there (users, roles, files...)
1289 */
1290class backup_inforef_structure_step extends backup_structure_step {
1291
1292 protected function define_structure() {
1293
482aac65
EL
1294 // Items we want to include in the inforef file.
1295 $items = backup_helper::get_inforef_itemnames();
77547b46
EL
1296
1297 // Build the tree
1298
1299 $inforef = new backup_nested_element('inforef');
1300
1301 // For each item, conditionally, if there are already records, build element
1302 foreach ($items as $itemname) {
1303 if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {
1304 $elementroot = new backup_nested_element($itemname . 'ref');
482aac65 1305 $element = new backup_nested_element($itemname, array(), array('id'));
77547b46
EL
1306 $inforef->add_child($elementroot);
1307 $elementroot->add_child($element);
1308 $element->set_source_sql("
1309 SELECT itemid AS id
1310 FROM {backup_ids_temp}
1311 WHERE backupid = ?
1312 AND itemname = ?",
c0bd6249 1313 array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
77547b46
EL
1314 }
1315 }
1316
1317 // We don't annotate anything there, but rely in the next step
1318 // (move_inforef_annotations_to_final) that will change all the
1319 // already saved 'inforref' entries to their 'final' annotations.
1320 return $inforef;
1321 }
1322}
1323
1324/**
1325 * This step will get all the annotations already processed to inforef.xml file and
1326 * transform them into 'final' annotations.
1327 */
1328class move_inforef_annotations_to_final extends backup_execution_step {
1329
1330 protected function define_execution() {
1331
482aac65
EL
1332 // Items we want to include in the inforef file
1333 $items = backup_helper::get_inforef_itemnames();
77547b46
EL
1334 foreach ($items as $itemname) {
1335 // Delegate to dbops
1336 backup_structure_dbops::move_annotations_to_final($this->get_backupid(), $itemname);
1337 }
1338 }
1339}
1340
1341/**
1342 * structure in charge of constructing the files.xml file with all the
1343 * annotated (final) files along the process. At, the same time, and
1344 * using one specialised nested_element, will copy them form moodle storage
1345 * to backup storage
1346 */
1347class backup_final_files_structure_step extends backup_structure_step {
1348
1349 protected function define_structure() {
1350
1351 // Define elements
1352
1353 $files = new backup_nested_element('files');
1354
1355 $file = new file_nested_element('file', array('id'), array(
64f93798 1356 'contenthash', 'contextid', 'component', 'filearea', 'itemid',
77547b46
EL
1357 'filepath', 'filename', 'userid', 'filesize',
1358 'mimetype', 'status', 'timecreated', 'timemodified',
1fd3ea43 1359 'source', 'author', 'license', 'sortorder'));
77547b46
EL
1360
1361 // Build the tree
1362
1363 $files->add_child($file);
1364
1365 // Define sources
1366
1367 $file->set_source_sql("SELECT f.*
1368 FROM {files} f
1369 JOIN {backup_ids_temp} bi ON f.id = bi.itemid
1370 WHERE bi.backupid = ?
1371 AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
1372
1373 return $files;
1374 }
1375}
1376
1377/**
1378 * Structure step in charge of creating the main moodle_backup.xml file
1379 * where all the information related to the backup, settings, license and
1380 * other information needed on restore is added*/
1381class backup_main_structure_step extends backup_structure_step {
1382
1383 protected function define_structure() {
1384
1385 global $CFG;
1386
1387 $info = array();
1388
1389 $info['name'] = $this->get_setting_value('filename');
1390 $info['moodle_version'] = $CFG->version;
1391 $info['moodle_release'] = $CFG->release;
1392 $info['backup_version'] = $CFG->backup_version;
1393 $info['backup_release'] = $CFG->backup_release;
1394 $info['backup_date'] = time();
1395 $info['backup_uniqueid']= $this->get_backupid();
c3ea499d 1396 $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
77547b46 1397 $info['original_wwwroot']=$CFG->wwwroot;
482aac65 1398 $info['original_site_identifier_hash'] = md5(get_site_identifier());
77547b46 1399 $info['original_course_id'] = $this->get_courseid();
560811a9
EL
1400 $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
1401 $info['original_course_fullname'] = $originalcourseinfo->fullname;
1402 $info['original_course_shortname'] = $originalcourseinfo->shortname;
1403 $info['original_course_startdate'] = $originalcourseinfo->startdate;
76cfb124 1404 $info['original_course_contextid'] = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
3a1cccc6 1405 $info['original_system_contextid'] = get_context_instance(CONTEXT_SYSTEM)->id;
77547b46
EL
1406
1407 // Get more information from controller
1408 list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($this->get_backupid());
1409
1410 // Define elements
1411
1412 $moodle_backup = new backup_nested_element('moodle_backup');
1413
1414 $information = new backup_nested_element('information', null, array(
1415 'name', 'moodle_version', 'moodle_release', 'backup_version',
c3ea499d 1416 'backup_release', 'backup_date', 'mnet_remoteusers', 'original_wwwroot',
560811a9
EL
1417 'original_site_identifier_hash', 'original_course_id',
1418 'original_course_fullname', 'original_course_shortname', 'original_course_startdate',
1419 'original_course_contextid', 'original_system_contextid'));
77547b46
EL
1420
1421 $details = new backup_nested_element('details');
1422
1423 $detail = new backup_nested_element('detail', array('backup_id'), array(
1424 'type', 'format', 'interactive', 'mode',
1425 'execution', 'executiontime'));
1426
1427 $contents = new backup_nested_element('contents');
1428
1429 $activities = new backup_nested_element('activities');
1430
1431 $activity = new backup_nested_element('activity', null, array(
1432 'moduleid', 'sectionid', 'modulename', 'title',
1433 'directory'));
1434
1435 $sections = new backup_nested_element('sections');
1436
1437 $section = new backup_nested_element('section', null, array(
1438 'sectionid', 'title', 'directory'));
1439
1440 $course = new backup_nested_element('course', null, array(
1441 'courseid', 'title', 'directory'));
1442
1443 $settings = new backup_nested_element('settings');
1444
1445 $setting = new backup_nested_element('setting', null, array(
d12fd69b 1446 'level', 'section', 'activity', 'name', 'value'));
77547b46
EL
1447
1448 // Build the tree
1449
1450 $moodle_backup->add_child($information);
1451
1452 $information->add_child($details);
1453 $details->add_child($detail);
1454
1455 $information->add_child($contents);
1456 if (!empty($cinfo['activities'])) {
1457 $contents->add_child($activities);
1458 $activities->add_child($activity);
1459 }
1460 if (!empty($cinfo['sections'])) {
1461 $contents->add_child($sections);
1462 $sections->add_child($section);
1463 }
1464 if (!empty($cinfo['course'])) {
1465 $contents->add_child($course);
1466 }
1467
1468 $information->add_child($settings);
1469 $settings->add_child($setting);
1470
1471
1472 // Set the sources
1473
1474 $information->set_source_array(array((object)$info));
1475
1476 $detail->set_source_array($dinfo);
1477
1478 $activity->set_source_array($cinfo['activities']);
1479
1480 $section->set_source_array($cinfo['sections']);
1481
1482 $course->set_source_array($cinfo['course']);
1483
1484 $setting->set_source_array($sinfo);
1485
1486 // Prepare some information to be sent to main moodle_backup.xml file
1487 return $moodle_backup;
1488 }
1489
1490}
1491
ce937f99 1492/**
12c80f79 1493 * Execution step that will generate the final zip (.mbz) file with all the contents
ce937f99
EL
1494 */
1495class backup_zip_contents extends backup_execution_step {
1496
1497 protected function define_execution() {
1498
1499 // Get basepath
1500 $basepath = $this->get_basepath();
1501
1502 // Get the list of files in directory
1503 $filestemp = get_directory_list($basepath, '', false, true, true);
1504 $files = array();
1505 foreach ($filestemp as $file) { // Add zip paths and fs paths to all them
1506 $files[$file] = $basepath . '/' . $file;
1507 }
1508
1509 // Add the log file if exists
1510 $logfilepath = $basepath . '.log';
1511 if (file_exists($logfilepath)) {
1512 $files['moodle_backup.log'] = $logfilepath;
1513 }
1514
12c80f79
EL
1515 // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1516 $zipfile = $basepath . '/backup.mbz';
ce937f99
EL
1517
1518 // Get the zip packer
1519 $zippacker = get_file_packer('application/zip');
1520
1521 // Zip files
1522 $zippacker->archive_to_pathname($files, $zipfile);
1523 }
1524}
1525
1526/**
1527 * This step will send the generated backup file to its final destination
1528 */
1529class backup_store_backup_file extends backup_execution_step {
1530
1531 protected function define_execution() {
1532
1533 // Get basepath
1534 $basepath = $this->get_basepath();
1535
12c80f79
EL
1536 // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
1537 $zipfile = $basepath . '/backup.mbz';
ce937f99
EL
1538
1539 // Perform storage and return it (TODO: shouldn't be array but proper result object)
1540 return array('backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile));
1541 }
1542}
1543
1544
77547b46
EL
1545/**
1546 * This step will search for all the activity (not calculations, categories nor aggregations) grade items
1547 * and put them to the backup_ids tables, to be used later as base to backup them
1548 */
1549class backup_activity_grade_items_to_ids extends backup_execution_step {
1550
1551 protected function define_execution() {
1552
1553 // Fetch all activity grade items
1554 if ($items = grade_item::fetch_all(array(
1555 'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),
1556 'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {
1557 // Annotate them in backup_ids
1558 foreach ($items as $item) {
1559 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);
1560 }
1561 }
1562 }
1563}
1564
f70676a7
EL
1565/**
1566 * This step will annotate all the groups and groupings belonging to the course
1567 */
1568class backup_annotate_course_groups_and_groupings extends backup_execution_step {
1569
1570 protected function define_execution() {
1571 global $DB;
1572
1573 // Get all the course groups
1574 if ($groups = $DB->get_records('groups', array(
1575 'courseid' => $this->task->get_courseid()))) {
1576 foreach ($groups as $group) {
1577 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id);
1578 }
1579 }
1580
1581 // Get all the course groupings
1582 if ($groupings = $DB->get_records('groupings', array(
1583 'courseid' => $this->task->get_courseid()))) {
1584 foreach ($groupings as $grouping) {
1585 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id);
1586 }
1587 }
1588 }
1589}
1590
77547b46
EL
1591/**
1592 * This step will annotate all the groups belonging to already annotated groupings
1593 */
1594class backup_annotate_groups_from_groupings extends backup_execution_step {
1595
1596 protected function define_execution() {
1597 global $DB;
1598
1599 // Fetch all the annotated groupings
1600 if ($groupings = $DB->get_records('backup_ids_temp', array(
1601 'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
1602 foreach ($groupings as $grouping) {
1603 if ($groups = $DB->get_records('groupings_groups', array(
1604 'groupingid' => $grouping->itemid))) {
1605 foreach ($groups as $group) {
1606 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
1607 }
1608 }
1609 }
1610 }
1611 }
1612}
1613
1614/**
1615 * This step will annotate all the scales belonging to already annotated outcomes
1616 */
1617class backup_annotate_scales_from_outcomes extends backup_execution_step {
1618
1619 protected function define_execution() {
1620 global $DB;
1621
1622 // Fetch all the annotated outcomes
1623 if ($outcomes = $DB->get_records('backup_ids_temp', array(
1624 'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
1625 foreach ($outcomes as $outcome) {
1626 if ($scale = $DB->get_record('grade_outcomes', array(
1627 'id' => $outcome->itemid))) {
1628 // Annotate as scalefinal because it's > 0
1629 backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
1630 }
1631 }
1632 }
1633 }
1634}
1635
767cb7f0
EL
1636/**
1637 * This step will generate all the file annotations for the already
1638 * annotated (final) question_categories. It calculates the different
1639 * contexts that are being backup and, annotates all the files
1640 * on every context belonging to the "question" component. As far as
1641 * we are always including *complete* question banks it is safe and
1642 * optimal to do that in this (one pass) way
1643 */
1644class backup_annotate_all_question_files extends backup_execution_step {
1645
1646 protected function define_execution() {
1647 global $DB;
1648
1649 // Get all the different contexts for the final question_categories
1650 // annotated along the whole backup
1651 $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
1652 FROM {question_categories} qc
1653 JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
1654 WHERE bi.backupid = ?
1655 AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
41941110
EL
1656 // To know about qtype specific components/fileareas
1657 $components = backup_qtype_plugin::get_components_and_fileareas();
1658 // Let's loop
767cb7f0
EL
1659 foreach($rs as $record) {
1660 // We don't need to specify filearea nor itemid as far as by
1661 // component and context it's enough to annotate the whole bank files
41941110
EL
1662 // This backups "questiontext", "generalfeedback" and "answerfeedback" fileareas (all them
1663 // belonging to the "question" component
767cb7f0 1664 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', null, null);
41941110
EL
1665 // Again, it is enough to pick files only by context and component
1666 // Do it for qtype specific components
1667 foreach ($components as $component => $fileareas) {
1668 backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null);
1669 }
767cb7f0
EL
1670 }
1671 $rs->close();
1672 }
1673}
1674
1675/**
1676 * structure step in charge of constructing the questions.xml file for all the
1677 * question categories and questions required by the backup
1678 * and letters related to one activity
1679 */
1680class backup_questions_structure_step extends backup_structure_step {
1681
1682 protected function define_structure() {
1683
1684 // Define each element separated
1685
1686 $qcategories = new backup_nested_element('question_categories');
1687
1688 $qcategory = new backup_nested_element('question_category', array('id'), array(
1689 'name', 'contextid', 'contextlevel', 'contextinstanceid',
1690 'info', 'infoformat', 'stamp', 'parent',
1691 'sortorder'));
1692
1693 $questions = new backup_nested_element('questions');
1694
1695 $question = new backup_nested_element('question', array('id'), array(
1696 'parent', 'name', 'questiontext', 'questiontextformat',
f3ca24e4 1697 'generalfeedback', 'generalfeedbackformat', 'defaultmark', 'penalty',
767cb7f0
EL
1698 'qtype', 'length', 'stamp', 'version',
1699 'hidden', 'timecreated', 'timemodified', 'createdby', 'modifiedby'));
1700
1701 // attach qtype plugin structure to $question element, only one allowed
1702 $this->add_plugin_structure('qtype', $question, false);
1703
f3ca24e4
TH
1704 $qhints = new backup_nested_element('question_hints');
1705
1706 $qhint = new backup_nested_element('question_hint', array('id'), array(
1707 'hint', 'hintformat', 'shownumcorrect', 'clearwrong', 'options'));
1708
767cb7f0
EL
1709 // Build the tree
1710
1711 $qcategories->add_child($qcategory);
1712 $qcategory->add_child($questions);
767cb7f0 1713 $questions->add_child($question);
f3ca24e4
TH
1714 $question->add_child($qhints);
1715 $qhints->add_child($qhint);
767cb7f0
EL
1716
1717 // Define the sources
1718
1719 $qcategory->set_source_sql("
1720 SELECT gc.*, contextlevel, instanceid AS contextinstanceid
1721 FROM {question_categories} gc
1722 JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
1723 JOIN {context} co ON co.id = gc.contextid
1724 WHERE bi.backupid = ?
1725 AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID));
1726
1727 $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
1728
c749527b
TH
1729 $qhint->set_source_sql('
1730 SELECT *
1731 FROM {question_hints}
1732 WHERE questionid = :questionid
1733 ORDER BY id',
1734 array('questionid' => backup::VAR_PARENTID));
f3ca24e4 1735
767cb7f0 1736 // don't need to annotate ids nor files
41941110 1737 // (already done by {@link backup_annotate_all_question_files}
767cb7f0
EL
1738
1739 return $qcategories;
1740 }
1741}
1742
1743
1744
77547b46 1745/**
76cfb124 1746 * This step will generate all the file annotations for the already
39b5371c 1747 * annotated (final) users. Need to do this here because each user
77547b46
EL
1748 * has its own context and structure tasks only are able to handle
1749 * one context. Also, this step will guarantee that every user has
1750 * its context created (req for other steps)
1751 */
1752class backup_annotate_all_user_files extends backup_execution_step {
1753
1754 protected function define_execution() {
1755 global $DB;
1756
1757 // List of fileareas we are going to annotate
76cfb124
EL
1758 $fileareas = array('profile', 'icon');
1759
1760 if ($this->get_setting_value('user_files')) { // private files only if enabled in settings
1761 $fileareas[] = 'private';
1762 }
77547b46
EL
1763
1764 // Fetch all annotated (final) users
1765 $rs = $DB->get_recordset('backup_ids_temp', array(
1766 'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
1767 foreach ($rs as $record) {
1768 $userid = $record->itemid;
1769 $userctxid = get_context_instance(CONTEXT_USER, $userid)->id;
1770 // Proceed with every user filearea
1771 foreach ($fileareas as $filearea) {
78d47b30 1772 // We don't need to specify itemid ($userid - 5th param) as far as by
77547b46 1773 // context we can get all the associated files. See MDL-22092
64f93798 1774 backup_structure_dbops::annotate_files($this->get_backupid(), $userctxid, 'user', $filearea, null);
77547b46
EL
1775 }
1776 }
1777 $rs->close();
1778 }
1779}
1780
1781/**
1782 * structure step in charge of constructing the grades.xml file for all the grade items
1783 * and letters related to one activity
1784 */
1785class backup_activity_grades_structure_step extends backup_structure_step {
1786
1787 protected function define_structure() {
1788
1789 // To know if we are including userinfo
1790 $userinfo = $this->get_setting_value('userinfo');
1791
1792 // Define each element separated
1793
1794 $book = new backup_nested_element('activity_gradebook');
1795
1796 $items = new backup_nested_element('grade_items');
1797
1798 $item = new backup_nested_element('grade_item', array('id'), array(
1799 'categoryid', 'itemname', 'itemtype', 'itemmodule',
1800 'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
1801 'calculation', 'gradetype', 'grademax', 'grademin',
1802 'scaleid', 'outcomeid', 'gradepass', 'multfactor',
1803 'plusfactor', 'aggregationcoef', 'sortorder', 'display',
1804 'decimals', 'hidden', 'locked', 'locktime',
1805 'needsupdate', 'timecreated', 'timemodified'));
1806
1807 $grades = new backup_nested_element('grade_grades');
1808
1809 $grade = new backup_nested_element('grade_grade', array('id'), array(
1810 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
1811 'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
1812 'locked', 'locktime', 'exported', 'overridden',
1813 'excluded', 'feedback', 'feedbackformat', 'information',
1814 'informationformat', 'timecreated', 'timemodified'));
1815
1816 $letters = new backup_nested_element('grade_letters');
1817
1818 $letter = new backup_nested_element('grade_letter', 'id', array(
1819 'lowerboundary', 'letter'));
1820
1821 // Build the tree
1822
1823 $book->add_child($items);
1824 $items->add_child($item);
1825
1826 $item->add_child($grades);
1827 $grades->add_child($grade);
1828
1829 $book->add_child($letters);
1830 $letters->add_child($letter);
1831
1832 // Define sources
1833
315f6d8e
AD
1834 $item->set_source_sql("SELECT gi.*
1835 FROM {grade_items} gi
1836 JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
1837 WHERE bi.backupid = ?
1838 AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
77547b46
EL
1839
1840 // This only happens if we are including user info
1841 if ($userinfo) {
1842 $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
1843 }
1844
1845 $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
1846
1847 // Annotations
1848
1849 $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
1850 $item->annotate_ids('outcome', 'outcomeid');
1851
1852 $grade->annotate_ids('user', 'userid');
1853 $grade->annotate_ids('user', 'usermodified');
1854
1855 // Return the root element (book)
1856
1857 return $book;
1858 }
1859}
bd39b6f2
SH
1860
1861/**
1862 * Backups up the course completion information for the course.
1863 */
1864class backup_course_completion_structure_step extends backup_structure_step {
1865
1866 protected function execute_condition() {
1867 // Check that all activities have been included
1868 if ($this->task->is_excluding_activities()) {
1869 return false;
1870 }
1871 return true;
1872 }
1873
1874 /**
1875 * The structure of the course completion backup
1876 *
1877 * @return backup_nested_element
1878 */
1879 protected function define_structure() {
1880
1881 // To know if we are including user completion info
1882 $userinfo = $this->get_setting_value('userscompletion');
1883
1884 $cc = new backup_nested_element('course_completion');
1885
1886 $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
1887 'course','criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod', 'timeend', 'gradepass', 'role'
1888 ));
1889
1890 $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
1891
1892 $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
1893 'criteriaid', 'userid','gradefinal','unenrolled','deleted','timecompleted'
1894 ));
1895
1896 $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
1897 'userid', 'course', 'deleted', 'timenotified', 'timeenrolled','timestarted','timecompleted','reaggregate'
1898 ));
1899
1900 $notify = new backup_nested_element('course_completion_notify', array('id'), array(
1901 'course','role','message','timesent'
1902 ));
1903
1904 $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
1905 'course','criteriatype','method','value'
1906 ));
1907
1908 $cc->add_child($criteria);
1909 $criteria->add_child($criteriacompletions);
1910 $criteriacompletions->add_child($criteriacomplete);
1911 $cc->add_child($coursecompletions);
1912 $cc->add_child($notify);
1913 $cc->add_child($aggregatemethod);
1914
1915 // We need to get the courseinstances shortname rather than an ID for restore
9404c7db 1916 $criteria->set_source_sql("SELECT ccc.*, c.shortname AS courseinstanceshortname
bd39b6f2
SH
1917 FROM {course_completion_criteria} ccc
1918 LEFT JOIN {course} c ON c.id = ccc.courseinstance
1919 WHERE ccc.course = ?", array(backup::VAR_COURSEID));
1920
1921
1922 $notify->set_source_table('course_completion_notify', array('course' => backup::VAR_COURSEID));
1923 $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
1924
1925 if ($userinfo) {
1926 $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
1927 $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
1928 }
1929
1930 $criteria->annotate_ids('role', 'role');
1931 $criteriacomplete->annotate_ids('user', 'userid');
1932 $coursecompletions->annotate_ids('user', 'userid');
1933 $notify->annotate_ids('role', 'role');
1934
1935 return $cc;
1936
1937 }
767cb7f0 1938}