Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * Defines various backup steps that will be used by common tasks in backup
20
 *
21
 * @package     core_backup
22
 * @subpackage  moodle2
23
 * @category    backup
24
 * @copyright   2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
25
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 */
27
 
28
defined('MOODLE_INTERNAL') || die();
29
 
30
/**
31
 * Create the temp dir where backup/restore will happen and create temp ids table.
32
 */
33
class create_and_clean_temp_stuff extends backup_execution_step {
34
 
35
    protected function define_execution() {
36
        $progress = $this->task->get_progress();
37
        $progress->start_progress('Deleting backup directories');
38
        backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
39
        backup_helper::clear_backup_dir($this->get_backupid(), $progress);           // Empty temp dir, just in case
40
        backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
41
        backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
42
        $progress->end_progress();
43
    }
44
}
45
 
46
/**
47
 * Delete the temp dir used by backup/restore (conditionally) and drop temp ids table.
48
 * Note we delete the directory but not the corresponding log file that will be
49
 * there until cron cleans it up.
50
 */
51
class drop_and_clean_temp_stuff extends backup_execution_step {
52
 
53
    protected $skipcleaningtempdir = false;
54
 
55
    protected function define_execution() {
56
        global $CFG;
57
 
58
        backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
59
        // Delete temp dir conditionally:
60
        // 1) If $CFG->keeptempdirectoriesonbackup is not enabled
61
        // 2) If backup temp dir deletion has been marked to be avoided
62
        if (empty($CFG->keeptempdirectoriesonbackup) && !$this->skipcleaningtempdir) {
63
            $progress = $this->task->get_progress();
64
            $progress->start_progress('Deleting backup dir');
65
            backup_helper::delete_backup_dir($this->get_backupid(), $progress); // Empty backup dir
66
            $progress->end_progress();
67
        }
68
    }
69
 
70
    public function skip_cleaning_temp_dir($skip) {
71
        $this->skipcleaningtempdir = $skip;
72
    }
73
}
74
 
75
/**
76
 * Create the directory where all the task (activity/block...) information will be stored
77
 */
78
class create_taskbasepath_directory extends backup_execution_step {
79
 
80
    protected function define_execution() {
81
        global $CFG;
82
        $basepath = $this->task->get_taskbasepath();
83
        if (!check_dir_exists($basepath, true, true)) {
84
            throw new backup_step_exception('cannot_create_taskbasepath_directory', $basepath);
85
        }
86
    }
87
}
88
 
89
/**
90
 * Abstract structure step, parent of all the activity structure steps. Used to wrap the
91
 * activity structure definition within the main <activity ...> tag.
92
 */
93
abstract class backup_activity_structure_step extends backup_structure_step {
94
 
95
    /**
96
     * Wraps any activity backup structure within the common 'activity' element
97
     * that will include common to all activities information like id, context...
98
     *
99
     * @param backup_nested_element $activitystructure the element to wrap
100
     * @return backup_nested_element the $activitystructure wrapped by the common 'activity' element
101
     */
102
    protected function prepare_activity_structure($activitystructure) {
103
 
104
        // Create the wrap element
105
        $activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);
106
 
107
        // Build the tree
108
        $activity->add_child($activitystructure);
109
 
110
        // Set the source
111
        $activityarr = array((object)array(
112
            'id'         => $this->task->get_activityid(),
113
            'moduleid'   => $this->task->get_moduleid(),
114
            'modulename' => $this->task->get_modulename(),
115
            'contextid'  => $this->task->get_contextid()));
116
 
117
        $activity->set_source_array($activityarr);
118
 
119
        // Return the root element (activity)
120
        return $activity;
121
    }
122
}
123
 
124
/**
125
 * Helper code for use by any plugin that stores question attempt data that it needs to back up.
126
 */
127
trait backup_questions_attempt_data_trait {
128
 
129
    /**
130
     * Attach to $element (usually attempts) the needed backup structures
131
     * for question_usages and all the associated data.
132
     *
133
     * @param backup_nested_element $element the element that will contain all the question_usages data.
134
     * @param string $usageidname the name of the element that holds the usageid.
135
     *      This must be child of $element, and must be a final element.
136
     * @param string $nameprefix this prefix is added to all the element names we create.
137
     *      Element names in the XML must be unique, so if you are using usages in
138
     *      two different ways, you must give a prefix to at least one of them. If
139
     *      you only use one sort of usage, then you can just use the default empty prefix.
140
     *      This should include a trailing underscore. For example "myprefix_"
141
     */
142
    protected function add_question_usages($element, $usageidname, $nameprefix = '') {
143
        global $CFG;
144
        require_once($CFG->dirroot . '/question/engine/lib.php');
145
 
146
        // Check $element is one nested_backup_element
147
        if (! $element instanceof backup_nested_element) {
148
            throw new backup_step_exception('question_states_bad_parent_element', $element);
149
        }
150
        if (! $element->get_final_element($usageidname)) {
151
            throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname);
152
        }
153
 
154
        $quba = new backup_nested_element($nameprefix . 'question_usage', array('id'),
155
                array('component', 'preferredbehaviour'));
156
 
157
        $qas = new backup_nested_element($nameprefix . 'question_attempts');
158
        $qa = new backup_nested_element($nameprefix . 'question_attempt', array('id'), array(
159
                'slot', 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction',
160
                'flagged', 'questionsummary', 'rightanswer', 'responsesummary',
161
                'timemodified'));
162
 
163
        $steps = new backup_nested_element($nameprefix . 'steps');
164
        $step = new backup_nested_element($nameprefix . 'step', array('id'), array(
165
                'sequencenumber', 'state', 'fraction', 'timecreated', 'userid'));
166
 
167
        $response = new backup_nested_element($nameprefix . 'response');
168
        $variable = new backup_nested_element($nameprefix . 'variable', null,  array('name', 'value'));
169
 
170
        // Build the tree
171
        $element->add_child($quba);
172
        $quba->add_child($qas);
173
        $qas->add_child($qa);
174
        $qa->add_child($steps);
175
        $steps->add_child($step);
176
        $step->add_child($response);
177
        $response->add_child($variable);
178
 
179
        // Set the sources
180
        $quba->set_source_table('question_usages',
181
                array('id'                => '../' . $usageidname));
182
        $qa->set_source_table('question_attempts', array('questionusageid' => backup::VAR_PARENTID), 'slot ASC');
183
        $step->set_source_table('question_attempt_steps', array('questionattemptid' => backup::VAR_PARENTID), 'sequencenumber ASC');
184
        $variable->set_source_table('question_attempt_step_data', array('attemptstepid' => backup::VAR_PARENTID));
185
 
186
        // Annotate ids
187
        $qa->annotate_ids('question', 'questionid');
188
        $step->annotate_ids('user', 'userid');
189
 
190
        // Annotate files
191
        $fileareas = question_engine::get_all_response_file_areas();
192
        foreach ($fileareas as $filearea) {
193
            $step->annotate_files('question', $filearea, 'id');
194
        }
195
    }
196
}
197
 
198
/**
199
 * Helper to backup question reference data for an instance.
200
 */
201
trait backup_question_reference_data_trait {
202
 
203
    /**
204
     * Backup the related data from reference table for the instance.
205
     *
206
     * @param backup_nested_element $element
207
     * @param string $component
208
     * @param string $questionarea
209
     */
210
    protected function add_question_references($element, $component, $questionarea) {
211
        // Check $element is one nested_backup_element.
212
        if (! $element instanceof backup_nested_element) {
213
            throw new backup_step_exception('question_states_bad_parent_element', $element);
214
        }
215
 
216
        $reference = new backup_nested_element('question_reference', ['id'],
217
            ['usingcontextid', 'component', 'questionarea', 'questionbankentryid', 'version']);
218
 
219
        $element->add_child($reference);
220
 
221
        $reference->set_source_table('question_references', [
222
            'usingcontextid' => backup::VAR_CONTEXTID,
223
            'component' => backup_helper::is_sqlparam($component),
224
            'questionarea' => backup_helper::is_sqlparam($questionarea),
225
            'itemid' => backup::VAR_PARENTID
226
        ]);
227
    }
228
}
229
 
230
/**
231
 * Helper to backup question set reference data for an instance.
232
 */
233
trait backup_question_set_reference_trait {
234
 
235
    /**
236
     * Backup the related data from set_reference table for the instance.
237
     *
238
     * @param backup_nested_element $element
239
     * @param string $component
240
     * @param string $questionarea
241
     */
242
    protected function add_question_set_references($element, $component, $questionarea) {
243
        // Check $element is one nested_backup_element.
244
        if (! $element instanceof backup_nested_element) {
245
            throw new backup_step_exception('question_states_bad_parent_element', $element);
246
        }
247
 
248
        $setreference = new backup_nested_element('question_set_reference', ['id'],
249
            ['usingcontextid', 'component', 'questionarea', 'questionscontextid', 'filtercondition']);
250
 
251
        $element->add_child($setreference);
252
 
253
        $setreference->set_source_table('question_set_references', [
254
            'usingcontextid' => backup::VAR_CONTEXTID,
255
            'component' => backup_helper::is_sqlparam($component),
256
            'questionarea' => backup_helper::is_sqlparam($questionarea),
257
            'itemid' => backup::VAR_PARENTID
258
        ]);
259
    }
260
}
261
 
262
 
263
/**
264
 * Abstract structure step to help activities that store question attempt data, reference data and set reference data.
265
 *
266
 * @copyright 2011 The Open University
267
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
268
 */
269
abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
270
    use backup_questions_attempt_data_trait;
271
    use backup_question_reference_data_trait;
272
    use backup_question_set_reference_trait;
273
}
274
 
275
 
276
/**
277
 * backup structure step in charge of calculating the categories to be
278
 * included in backup, based in the context being backuped (module/course)
279
 * and the already annotated questions present in backup_ids_temp
280
 */
281
class backup_calculate_question_categories extends backup_execution_step {
282
 
283
    protected function define_execution() {
284
        backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());
285
    }
286
}
287
 
288
/**
289
 * backup structure step in charge of deleting all the questions annotated
290
 * in the backup_ids_temp table
291
 */
292
class backup_delete_temp_questions extends backup_execution_step {
293
 
294
    protected function define_execution() {
295
        backup_question_dbops::delete_temp_questions($this->get_backupid());
296
    }
297
}
298
 
299
/**
300
 * Abstract structure step, parent of all the block structure steps. Used to wrap the
301
 * block structure definition within the main <block ...> tag
302
 */
303
abstract class backup_block_structure_step extends backup_structure_step {
304
 
305
    protected function prepare_block_structure($blockstructure) {
306
 
307
        // Create the wrap element
308
        $block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);
309
 
310
        // Build the tree
311
        $block->add_child($blockstructure);
312
 
313
        // Set the source
314
        $blockarr = array((object)array(
315
            'id'         => $this->task->get_blockid(),
316
            'blockname'  => $this->task->get_blockname(),
317
            'contextid'  => $this->task->get_contextid()));
318
 
319
        $block->set_source_array($blockarr);
320
 
321
        // Return the root element (block)
322
        return $block;
323
    }
324
}
325
 
326
/**
327
 * structure step that will generate the module.xml file for the activity,
328
 * accumulating various information about the activity, annotating groupings
329
 * and completion/avail conf
330
 */
331
class backup_module_structure_step extends backup_structure_step {
332
 
333
    protected function define_structure() {
334
        global $DB;
335
 
336
        // Define each element separated
337
 
338
        $module = new backup_nested_element('module', array('id', 'version'), array(
339
            'modulename', 'sectionid', 'sectionnumber', 'idnumber',
340
            'added', 'score', 'indent', 'visible', 'visibleoncoursepage',
341
            'visibleold', 'groupmode', 'groupingid',
342
            'completion', 'completiongradeitemnumber', 'completionpassgrade',
343
            'completionview', 'completionexpected',
344
            'availability', 'showdescription', 'downloadcontent', 'lang'));
345
 
346
        $tags = new backup_nested_element('tags');
347
        $tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));
348
 
349
        // attach format plugin structure to $module element, only one allowed
350
        $this->add_plugin_structure('format', $module, false);
351
 
352
        // Attach report plugin structure to $module element, multiple allowed.
353
        $this->add_plugin_structure('report', $module, true);
354
 
355
        // attach plagiarism plugin structure to $module element, there can be potentially
356
        // many plagiarism plugins storing information about this course
357
        $this->add_plugin_structure('plagiarism', $module, true);
358
 
359
        // attach local plugin structure to $module, multiple allowed
360
        $this->add_plugin_structure('local', $module, true);
361
 
362
        // Attach admin tools plugin structure to $module.
363
        $this->add_plugin_structure('tool', $module, true);
364
 
365
        $module->add_child($tags);
366
        $tags->add_child($tag);
367
 
368
        // Set the sources
369
        $concat = $DB->sql_concat("'mod_'", 'm.name');
370
        $module->set_source_sql("
371
            SELECT cm.*, cp.value AS version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumber
372
              FROM {course_modules} cm
373
              JOIN {modules} m ON m.id = cm.module
374
              JOIN {config_plugins} cp ON cp.plugin = $concat AND cp.name = 'version'
375
              JOIN {course_sections} s ON s.id = cm.section
376
             WHERE cm.id = ?", array(backup::VAR_MODID));
377
 
378
        $tag->set_source_sql("SELECT t.id, t.name, t.rawname
379
                                FROM {tag} t
380
                                JOIN {tag_instance} ti ON ti.tagid = t.id
381
                               WHERE ti.itemtype = 'course_modules'
382
                                 AND ti.component = 'core'
383
                                 AND ti.itemid = ?", array(backup::VAR_MODID));
384
 
385
        // Define annotations
386
        $module->annotate_ids('grouping', 'groupingid');
387
 
388
        // Return the root element ($module)
389
        return $module;
390
    }
391
}
392
 
393
/**
394
 * structure step that will generate the section.xml file for the section
395
 * annotating files
396
 */
397
class backup_section_structure_step extends backup_structure_step {
398
 
399
    protected function define_structure() {
400
 
401
        // Define each element separated
402
 
403
        $section = new backup_nested_element(
404
            'section',
405
            ['id'],
406
            [
407
                'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible',
408
                'availabilityjson', 'component', 'itemid', 'timemodified',
409
            ]
410
        );
411
 
412
        // attach format plugin structure to $section element, only one allowed
413
        $this->add_plugin_structure('format', $section, false);
414
 
415
        // attach local plugin structure to $section element, multiple allowed
416
        $this->add_plugin_structure('local', $section, true);
417
 
418
        // Add nested elements for course_format_options table
419
        $formatoptions = new backup_nested_element('course_format_options', array('id'), array(
420
            'format', 'name', 'value'));
421
        $section->add_child($formatoptions);
422
 
423
        // Define sources.
424
        $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
425
        $formatoptions->set_source_sql('SELECT cfo.id, cfo.format, cfo.name, cfo.value
426
              FROM {course} c
427
              JOIN {course_format_options} cfo
428
              ON cfo.courseid = c.id AND cfo.format = c.format
429
              WHERE c.id = ? AND cfo.sectionid = ?',
430
                array(backup::VAR_COURSEID, backup::VAR_SECTIONID));
431
 
432
        // Aliases
433
        $section->set_source_alias('section', 'number');
434
        // The 'availability' field needs to be renamed because it clashes with
435
        // the old nested element structure for availability data.
436
        $section->set_source_alias('availability', 'availabilityjson');
437
 
438
        // Set annotations
439
        $section->annotate_files('course', 'section', 'id');
440
 
441
        return $section;
442
    }
443
}
444
 
445
/**
446
 * structure step that will generate the course.xml file for the course, including
447
 * course category reference, tags, modules restriction information
448
 * and some annotations (files & groupings)
449
 */
450
class backup_course_structure_step extends backup_structure_step {
451
 
452
    protected function define_structure() {
453
        global $DB;
454
 
455
        // Define each element separated
456
 
457
        $course = new backup_nested_element('course', array('id', 'contextid'), array(
458
            'shortname', 'fullname', 'idnumber',
459
            'summary', 'summaryformat', 'format', 'showgrades',
460
            'newsitems', 'startdate', 'enddate',
461
            'marker', 'maxbytes', 'legacyfiles', 'showreports',
462
            'visible', 'groupmode', 'groupmodeforce',
463
            'defaultgroupingid', 'lang', 'theme',
464
            'timecreated', 'timemodified',
465
            'requested',
466
            'showactivitydates',
467
            'showcompletionconditions', 'pdfexportfont',
468
            'enablecompletion', 'completionstartonenrol', 'completionnotify'));
469
 
470
        $category = new backup_nested_element('category', array('id'), array(
471
            'name', 'description'));
472
 
473
        $tags = new backup_nested_element('tags');
474
 
475
        $tag = new backup_nested_element('tag', array('id'), array(
476
            'name', 'rawname'));
477
 
478
        $customfields = new backup_nested_element('customfields');
479
        $customfield = new backup_nested_element('customfield', array('id'), array(
480
            'shortname', 'type', 'value', 'valueformat', 'valuetrust',
481
        ));
482
 
483
        $courseformatoptions = new backup_nested_element('courseformatoptions');
484
        $courseformatoption = new backup_nested_element('courseformatoption', [], [
485
            'courseid', 'format', 'sectionid', 'name', 'value'
486
        ]);
487
 
488
        // attach format plugin structure to $course element, only one allowed
489
        $this->add_plugin_structure('format', $course, false);
490
 
491
        // attach theme plugin structure to $course element; multiple themes can
492
        // save course data (in case of user theme, legacy theme, etc)
493
        $this->add_plugin_structure('theme', $course, true);
494
 
495
        // attach general report plugin structure to $course element; multiple
496
        // reports can save course data if required
497
        $this->add_plugin_structure('report', $course, true);
498
 
499
        // attach course report plugin structure to $course element; multiple
500
        // course reports can save course data if required
501
        $this->add_plugin_structure('coursereport', $course, true);
502
 
503
        // attach plagiarism plugin structure to $course element, there can be potentially
504
        // many plagiarism plugins storing information about this course
505
        $this->add_plugin_structure('plagiarism', $course, true);
506
 
507
        // attach local plugin structure to $course element; multiple local plugins
508
        // can save course data if required
509
        $this->add_plugin_structure('local', $course, true);
510
 
511
        // Attach admin tools plugin structure to $course element; multiple plugins
512
        // can save course data if required.
513
        $this->add_plugin_structure('tool', $course, true);
514
 
515
        // Build the tree
516
 
517
        $course->add_child($category);
518
 
519
        $course->add_child($tags);
520
        $tags->add_child($tag);
521
 
522
        $course->add_child($customfields);
523
        $customfields->add_child($customfield);
524
 
525
        $course->add_child($courseformatoptions);
526
        $courseformatoptions->add_child($courseformatoption);
527
 
528
        // Set the sources
529
 
530
        $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
531
        $courserec->contextid = $this->task->get_contextid();
532
 
533
        // Add 'numsections' in order to be able to restore in previous versions of Moodle.
534
        // Even though Moodle does not officially support restore into older verions of Moodle from the
535
        // version where backup was made, without 'numsections' restoring will go very wrong.
536
        if (!property_exists($courserec, 'numsections') && course_get_format($courserec)->uses_sections()) {
537
            $courserec->numsections = course_get_format($courserec)->get_last_section_number();
538
        }
539
 
540
        $course->set_source_array(array($courserec));
541
 
542
        $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));
543
 
544
        $category->set_source_array(array($categoryrec));
545
 
546
        $tag->set_source_sql('SELECT t.id, t.name, t.rawname
547
                                FROM {tag} t
548
                                JOIN {tag_instance} ti ON ti.tagid = t.id
549
                               WHERE ti.itemtype = ?
550
                                 AND ti.itemid = ?', array(
551
                                     backup_helper::is_sqlparam('course'),
552
                                     backup::VAR_PARENTID));
553
 
554
        // Section level settings are dealt with in backup_section_structure_step.
555
        // We only need to deal with course level (sectionid = 0) here.
556
        $courseformatoption->set_source_sql('SELECT id, format, sectionid, name, value
557
                                 FROM {course_format_options}
558
                                 WHERE courseid = ? AND sectionid = 0', [ backup::VAR_PARENTID ]);
559
 
560
        $handler = core_course\customfield\course_handler::create();
561
        $fieldsforbackup = $handler->get_instance_data_for_backup($this->task->get_courseid());
562
        $handler->backup_define_structure($this->task->get_courseid(), $customfield);
563
        $customfield->set_source_array($fieldsforbackup);
564
 
565
        // Some annotations
566
 
567
        $course->annotate_ids('grouping', 'defaultgroupingid');
568
 
569
        $course->annotate_files('course', 'summary', null);
570
        $course->annotate_files('course', 'overviewfiles', null);
571
 
572
        if ($this->get_setting_value('legacyfiles')) {
573
            $course->annotate_files('course', 'legacy', null);
574
        }
575
 
576
        // Return root element ($course)
577
 
578
        return $course;
579
    }
580
}
581
 
582
/**
583
 * structure step that will generate the enrolments.xml file for the given course
584
 */
585
class backup_enrolments_structure_step extends backup_structure_step {
586
 
587
    /**
588
     * Skip enrolments on the front page.
589
     * @return bool
590
     */
591
    protected function execute_condition() {
592
        return ($this->get_courseid() != SITEID);
593
    }
594
 
595
    protected function define_structure() {
596
        global $DB;
597
 
598
        // To know if we are including users
599
        $users = $this->get_setting_value('users');
600
        $keptroles = $this->task->get_kept_roles();
601
 
602
        // Define each element separated
603
 
604
        $enrolments = new backup_nested_element('enrolments');
605
 
606
        $enrols = new backup_nested_element('enrols');
607
 
608
        $enrol = new backup_nested_element('enrol', array('id'), array(
609
            'enrol', 'status', 'name', 'enrolperiod', 'enrolstartdate',
610
            'enrolenddate', 'expirynotify', 'expirythreshold', 'notifyall',
611
            'password', 'cost', 'currency', 'roleid',
612
            'customint1', 'customint2', 'customint3', 'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
613
            'customchar1', 'customchar2', 'customchar3',
614
            'customdec1', 'customdec2',
615
            'customtext1', 'customtext2', 'customtext3', 'customtext4',
616
            'timecreated', 'timemodified'));
617
 
618
        $userenrolments = new backup_nested_element('user_enrolments');
619
 
620
        $enrolment = new backup_nested_element('enrolment', array('id'), array(
621
            'status', 'userid', 'timestart', 'timeend', 'modifierid',
622
            'timemodified'));
623
 
624
        // Build the tree
625
        $enrolments->add_child($enrols);
626
        $enrols->add_child($enrol);
627
        $enrol->add_child($userenrolments);
628
        $userenrolments->add_child($enrolment);
629
 
630
        // Define sources - the instances are restored using the same sortorder, we do not need to store it in xml and deal with it afterwards.
631
        $enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID), 'sortorder ASC');
632
 
633
        // User enrolments only added only if users included.
634
        if (empty($keptroles) && $users) {
635
            $enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));
636
            $enrolment->annotate_ids('user', 'userid');
637
        } else if (!empty($keptroles)) {
638
            list($insql, $inparams) = $DB->get_in_or_equal($keptroles);
639
            $params = array(
640
                backup::VAR_CONTEXTID,
641
                backup::VAR_PARENTID
642
            );
643
            foreach ($inparams as $inparam) {
644
                $params[] = backup_helper::is_sqlparam($inparam);
645
            }
646
            $enrolment->set_source_sql(
647
               "SELECT ue.*
648
                  FROM {user_enrolments} ue
649
            INNER JOIN {role_assignments} ra ON ue.userid = ra.userid
650
                 WHERE ra.contextid = ?
651
                       AND ue.enrolid = ?
652
                       AND ra.roleid $insql",
653
                $params);
654
            $enrolment->annotate_ids('user', 'userid');
655
        }
656
 
657
        $enrol->annotate_ids('role', 'roleid');
658
 
659
        // Add enrol plugin structure.
660
        $this->add_plugin_structure('enrol', $enrol, true);
661
 
662
        return $enrolments;
663
    }
664
}
665
 
666
/**
667
 * structure step that will generate the roles.xml file for the given context, observing
668
 * the role_assignments setting to know if that part needs to be included
669
 */
670
class backup_roles_structure_step extends backup_structure_step {
671
 
672
    protected function define_structure() {
673
 
674
        // To know if we are including role assignments
675
        $roleassignments = $this->get_setting_value('role_assignments');
676
 
677
        // Define each element separated
678
 
679
        $roles = new backup_nested_element('roles');
680
 
681
        $overrides = new backup_nested_element('role_overrides');
682
 
683
        $override = new backup_nested_element('override', array('id'), array(
684
            'roleid', 'capability', 'permission', 'timemodified',
685
            'modifierid'));
686
 
687
        $assignments = new backup_nested_element('role_assignments');
688
 
689
        $assignment = new backup_nested_element('assignment', array('id'), array(
690
            'roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid',
691
            'sortorder'));
692
 
693
        // Build the tree
694
        $roles->add_child($overrides);
695
        $roles->add_child($assignments);
696
 
697
        $overrides->add_child($override);
698
        $assignments->add_child($assignment);
699
 
700
        // Define sources
701
 
702
        $override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));
703
 
704
        // Assignments only added if specified
705
        if ($roleassignments) {
706
            $assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));
707
        }
708
 
709
        // Define id annotations
710
        $override->annotate_ids('role', 'roleid');
711
 
712
        $assignment->annotate_ids('role', 'roleid');
713
 
714
        $assignment->annotate_ids('user', 'userid');
715
 
716
        //TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)
717
 
718
        return $roles;
719
    }
720
}
721
 
722
/**
723
 * structure step that will generate the roles.xml containing the
724
 * list of roles used along the whole backup process. Just raw
725
 * list of used roles from role table
726
 */
727
class backup_final_roles_structure_step extends backup_structure_step {
728
 
729
    protected function define_structure() {
730
 
731
        // Define elements
732
 
733
        $rolesdef = new backup_nested_element('roles_definition');
734
 
735
        $role = new backup_nested_element('role', array('id'), array(
736
            'name', 'shortname', 'nameincourse', 'description',
737
            'sortorder', 'archetype'));
738
 
739
        // Build the tree
740
 
741
        $rolesdef->add_child($role);
742
 
743
        // Define sources
744
 
745
        $role->set_source_sql("SELECT r.*, rn.name AS nameincourse
746
                                 FROM {role} r
747
                                 JOIN {backup_ids_temp} bi ON r.id = bi.itemid
748
                            LEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?
749
                                WHERE bi.backupid = ?
750
                                  AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));
751
 
752
        // Return main element (rolesdef)
753
        return $rolesdef;
754
    }
755
}
756
 
757
/**
758
 * structure step that will generate the scales.xml containing the
759
 * list of scales used along the whole backup process.
760
 */
761
class backup_final_scales_structure_step extends backup_structure_step {
762
 
763
    protected function define_structure() {
764
 
765
        // Define elements
766
 
767
        $scalesdef = new backup_nested_element('scales_definition');
768
 
769
        $scale = new backup_nested_element('scale', array('id'), array(
770
            'courseid', 'userid', 'name', 'scale',
771
            'description', 'descriptionformat', 'timemodified'));
772
 
773
        // Build the tree
774
 
775
        $scalesdef->add_child($scale);
776
 
777
        // Define sources
778
 
779
        $scale->set_source_sql("SELECT s.*
780
                                  FROM {scale} s
781
                                  JOIN {backup_ids_temp} bi ON s.id = bi.itemid
782
                                 WHERE bi.backupid = ?
783
                                   AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));
784
 
785
        // Annotate scale files (they store files in system context, so pass it instead of default one)
786
        $scale->annotate_files('grade', 'scale', 'id', context_system::instance()->id);
787
 
788
        // Return main element (scalesdef)
789
        return $scalesdef;
790
    }
791
}
792
 
793
/**
794
 * structure step that will generate the outcomes.xml containing the
795
 * list of outcomes used along the whole backup process.
796
 */
797
class backup_final_outcomes_structure_step extends backup_structure_step {
798
 
799
    protected function define_structure() {
800
 
801
        // Define elements
802
 
803
        $outcomesdef = new backup_nested_element('outcomes_definition');
804
 
805
        $outcome = new backup_nested_element('outcome', array('id'), array(
806
            'courseid', 'userid', 'shortname', 'fullname',
807
            'scaleid', 'description', 'descriptionformat', 'timecreated',
808
            'timemodified','usermodified'));
809
 
810
        // Build the tree
811
 
812
        $outcomesdef->add_child($outcome);
813
 
814
        // Define sources
815
 
816
        $outcome->set_source_sql("SELECT o.*
817
                                    FROM {grade_outcomes} o
818
                                    JOIN {backup_ids_temp} bi ON o.id = bi.itemid
819
                                   WHERE bi.backupid = ?
820
                                     AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));
821
 
822
        // Annotate outcome files (they store files in system context, so pass it instead of default one)
823
        $outcome->annotate_files('grade', 'outcome', 'id', context_system::instance()->id);
824
 
825
        // Return main element (outcomesdef)
826
        return $outcomesdef;
827
    }
828
}
829
 
830
/**
831
 * structure step in charge of constructing the filters.xml file for all the filters found
832
 * in activity
833
 */
834
class backup_filters_structure_step extends backup_structure_step {
835
 
836
    protected function define_structure() {
837
 
838
        // Define each element separated
839
 
840
        $filters = new backup_nested_element('filters');
841
 
842
        $actives = new backup_nested_element('filter_actives');
843
 
844
        $active = new backup_nested_element('filter_active', null, array('filter', 'active'));
845
 
846
        $configs = new backup_nested_element('filter_configs');
847
 
848
        $config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));
849
 
850
        // Build the tree
851
 
852
        $filters->add_child($actives);
853
        $filters->add_child($configs);
854
 
855
        $actives->add_child($active);
856
        $configs->add_child($config);
857
 
858
        // Define sources
859
 
860
        list($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());
861
 
862
        $active->set_source_array($activearr);
863
        $config->set_source_array($configarr);
864
 
865
        // Return the root element (filters)
866
        return $filters;
867
    }
868
}
869
 
870
/**
871
 * Structure step in charge of constructing the comments.xml file for all the comments found in a given context.
872
 */
873
class backup_comments_structure_step extends backup_structure_step {
874
 
875
    protected function define_structure() {
876
        // Define each element separated.
877
        $comments = new backup_nested_element('comments');
878
 
879
        $comment = new backup_nested_element('comment', array('id'), array(
880
            'component', 'commentarea', 'itemid', 'content', 'format',
881
            'userid', 'timecreated'));
882
 
883
        // Build the tree.
884
        $comments->add_child($comment);
885
 
886
        // Define sources.
887
        $comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));
888
 
889
        // Define id annotations.
890
        $comment->annotate_ids('user', 'userid');
891
 
892
        // Return the root element (comments).
893
        return $comments;
894
    }
895
}
896
 
897
/**
898
 * structure step in charge of constructing the badges.xml file for all the badges found
899
 * in a given context
900
 */
901
class backup_badges_structure_step extends backup_structure_step {
902
 
903
    protected function execute_condition() {
904
        // Check that all activities have been included.
905
        if ($this->task->is_excluding_activities()) {
906
            return false;
907
        }
908
        return true;
909
    }
910
 
911
    protected function define_structure() {
912
        global $CFG;
913
 
914
        require_once($CFG->libdir . '/badgeslib.php');
915
        // Define each element separated.
916
 
917
        $badges = new backup_nested_element('badges');
918
        $badge = new backup_nested_element('badge', array('id'), array('name', 'description',
919
                'timecreated', 'timemodified', 'usercreated', 'usermodified', 'issuername',
920
                'issuerurl', 'issuercontact', 'expiredate', 'expireperiod', 'type', 'courseid',
921
                'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron',
922
                'version', 'language', 'imageauthorname', 'imageauthoremail', 'imageauthorurl',
923
                'imagecaption'));
924
 
925
        $criteria = new backup_nested_element('criteria');
926
        $criterion = new backup_nested_element('criterion', array('id'), array('badgeid',
927
                'criteriatype', 'method', 'description', 'descriptionformat'));
928
 
929
        $endorsement = new backup_nested_element('endorsement', array('id'), array('badgeid',
930
                'issuername', 'issuerurl', 'issueremail', 'claimid', 'claimcomment', 'dateissued'));
931
 
932
        $alignments = new backup_nested_element('alignments');
933
        $alignment = new backup_nested_element('alignment', array('id'), array('badgeid',
934
                'targetname', 'targeturl', 'targetdescription', 'targetframework', 'targetcode'));
935
 
936
        $relatedbadges = new backup_nested_element('relatedbadges');
937
        $relatedbadge = new backup_nested_element('relatedbadge', array('id'), array('badgeid',
938
                'relatedbadgeid'));
939
 
940
        $parameters = new backup_nested_element('parameters');
941
        $parameter = new backup_nested_element('parameter', array('id'), array('critid',
942
                'name', 'value', 'criteriatype'));
943
 
944
        $manual_awards = new backup_nested_element('manual_awards');
945
        $manual_award = new backup_nested_element('manual_award', array('id'), array('badgeid',
946
                'recipientid', 'issuerid', 'issuerrole', 'datemet'));
947
 
948
        $tags = new backup_nested_element('tags');
949
        $tag = new backup_nested_element('tag', ['id'], ['name', 'rawname']);
950
 
951
        // Build the tree.
952
 
953
        $badges->add_child($badge);
954
        $badge->add_child($criteria);
955
        $criteria->add_child($criterion);
956
        $criterion->add_child($parameters);
957
        $parameters->add_child($parameter);
958
        $badge->add_child($endorsement);
959
        $badge->add_child($alignments);
960
        $alignments->add_child($alignment);
961
        $badge->add_child($relatedbadges);
962
        $relatedbadges->add_child($relatedbadge);
963
        $badge->add_child($manual_awards);
964
        $manual_awards->add_child($manual_award);
965
        $badge->add_child($tags);
966
        $tags->add_child($tag);
967
 
968
        // Define sources.
969
 
970
        $parametersql = '
971
                SELECT *
972
                FROM {badge}
973
                WHERE courseid = :courseid
974
                AND status != ' . BADGE_STATUS_ARCHIVED;
975
        $parameterparams = [
976
            'courseid' => backup::VAR_COURSEID
977
        ];
978
        $badge->set_source_sql($parametersql, $parameterparams);
979
        $criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID));
980
        $endorsement->set_source_table('badge_endorsement', array('badgeid' => backup::VAR_PARENTID));
981
 
982
        $alignment->set_source_table('badge_alignment', array('badgeid' => backup::VAR_PARENTID));
983
        $relatedbadge->set_source_table('badge_related', array('badgeid' => backup::VAR_PARENTID));
984
 
985
        $parametersql = 'SELECT cp.*, c.criteriatype
986
                             FROM {badge_criteria_param} cp JOIN {badge_criteria} c
987
                                 ON cp.critid = c.id
988
                             WHERE critid = :critid';
989
        $parameterparams = array('critid' => backup::VAR_PARENTID);
990
        $parameter->set_source_sql($parametersql, $parameterparams);
991
 
992
        $manual_award->set_source_table('badge_manual_award', array('badgeid' => backup::VAR_PARENTID));
993
 
994
        $tag->set_source_sql('SELECT t.id, t.name, t.rawname
995
                                FROM {tag} t
996
                                JOIN {tag_instance} ti ON ti.tagid = t.id
997
                               WHERE ti.itemtype = ?
998
                                 AND ti.itemid = ?', [backup_helper::is_sqlparam('badge'), backup::VAR_PARENTID]);
999
 
1000
        // Define id annotations.
1001
 
1002
        $badge->annotate_ids('user', 'usercreated');
1003
        $badge->annotate_ids('user', 'usermodified');
1004
        $criterion->annotate_ids('badge', 'badgeid');
1005
        $parameter->annotate_ids('criterion', 'critid');
1006
        $endorsement->annotate_ids('badge', 'badgeid');
1007
        $alignment->annotate_ids('badge', 'badgeid');
1008
        $relatedbadge->annotate_ids('badge', 'badgeid');
1009
        $relatedbadge->annotate_ids('badge', 'relatedbadgeid');
1010
        $badge->annotate_files('badges', 'badgeimage', 'id');
1011
        $manual_award->annotate_ids('badge', 'badgeid');
1012
        $manual_award->annotate_ids('user', 'recipientid');
1013
        $manual_award->annotate_ids('user', 'issuerid');
1014
        $manual_award->annotate_ids('role', 'issuerrole');
1015
 
1016
        // Return the root element ($badges).
1017
        return $badges;
1018
    }
1019
}
1020
 
1021
/**
1022
 * structure step in charge of constructing the calender.xml file for all the events found
1023
 * in a given context
1024
 */
1025
class backup_calendarevents_structure_step extends backup_structure_step {
1026
 
1027
    protected function define_structure() {
1028
 
1029
        // Define each element separated
1030
 
1031
        $events = new backup_nested_element('events');
1032
 
1033
        $event = new backup_nested_element('event', array('id'), array(
1034
                'name', 'description', 'format', 'courseid', 'groupid', 'userid',
1035
                'repeatid', 'modulename', 'instance', 'type', 'eventtype', 'timestart',
1036
                'timeduration', 'timesort', 'visible', 'uuid', 'sequence', 'timemodified',
1037
                'priority', 'location'));
1038
 
1039
        // Build the tree
1040
        $events->add_child($event);
1041
 
1042
        // Define sources
1043
        if ($this->name == 'course_calendar') {
1044
            $calendar_items_sql ="SELECT * FROM {event}
1045
                        WHERE courseid = :courseid
1046
                        AND (eventtype = 'course' OR eventtype = 'group')";
1047
            $calendar_items_params = array('courseid'=>backup::VAR_COURSEID);
1048
            $event->set_source_sql($calendar_items_sql, $calendar_items_params);
1049
        } else if ($this->name == 'activity_calendar') {
1050
            // We don't backup action events.
1051
            $params = array('instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME,
1052
                'type' => array('sqlparam' => CALENDAR_EVENT_TYPE_ACTION));
1053
            // If we don't want to include the userinfo in the backup then setting the courseid
1054
            // will filter out all of the user override events (which have a course id of zero).
1055
            $coursewhere = "";
1056
            if (!$this->get_setting_value('userinfo')) {
1057
                $params['courseid'] = backup::VAR_COURSEID;
1058
                $coursewhere = " AND courseid = :courseid";
1059
            }
1060
            $calendarsql = "SELECT * FROM {event}
1061
                             WHERE instance = :instance
1062
                               AND type <> :type
1063
                               AND modulename = :modulename";
1064
            $calendarsql = $calendarsql . $coursewhere;
1065
            $event->set_source_sql($calendarsql, $params);
1066
        } else {
1067
            $event->set_source_table('event', array('courseid' => backup::VAR_COURSEID, 'instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME));
1068
        }
1069
 
1070
        // Define id annotations
1071
 
1072
        $event->annotate_ids('user', 'userid');
1073
        $event->annotate_ids('group', 'groupid');
1074
        $event->annotate_files('calendar', 'event_description', 'id');
1075
 
1076
        // Return the root element (events)
1077
        return $events;
1078
    }
1079
}
1080
 
1081
/**
1082
 * structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course
1083
 * NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step
1084
 */
1085
class backup_gradebook_structure_step extends backup_structure_step {
1086
 
1087
    /**
1088
     * We need to decide conditionally, based on dynamic information
1089
     * about the execution of this step. Only will be executed if all
1090
     * the module gradeitems have been already included in backup
1091
     */
1092
    protected function execute_condition() {
1093
        $courseid = $this->get_courseid();
1094
        if ($courseid == SITEID) {
1095
            return false;
1096
        }
1097
 
1098
        return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
1099
    }
1100
 
1101
    protected function define_structure() {
1102
        global $CFG, $DB;
1103
 
1104
        // are we including user info?
1105
        $userinfo = $this->get_setting_value('users');
1106
 
1107
        $gradebook = new backup_nested_element('gradebook');
1108
 
1109
        //grade_letters are done in backup_activity_grades_structure_step()
1110
 
1111
        //calculated grade items
1112
        $grade_items = new backup_nested_element('grade_items');
1113
        $grade_item = new backup_nested_element('grade_item', array('id'), array(
1114
            'categoryid', 'itemname', 'itemtype', 'itemmodule',
1115
            'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
1116
            'calculation', 'gradetype', 'grademax', 'grademin',
1117
            'scaleid', 'outcomeid', 'gradepass', 'multfactor',
1118
            'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
1119
            'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
1120
            'needsupdate', 'timecreated', 'timemodified'));
1121
 
1122
        $this->add_plugin_structure('local', $grade_item, true);
1123
 
1124
        $grade_grades = new backup_nested_element('grade_grades');
1125
        $grade_grade = new backup_nested_element('grade_grade', array('id'), array(
1126
            'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
1127
            'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
1128
            'locked', 'locktime', 'exported', 'overridden',
1129
            'excluded', 'feedback', 'feedbackformat', 'information',
1130
            'informationformat', 'timecreated', 'timemodified',
1131
            'aggregationstatus', 'aggregationweight'));
1132
 
1133
        //grade_categories
1134
        $grade_categories = new backup_nested_element('grade_categories');
1135
        $grade_category   = new backup_nested_element('grade_category', array('id'), array(
1136
                //'courseid',
1137
                'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
1138
                'droplow', 'aggregateonlygraded', 'aggregateoutcomes',
1139
                'timecreated', 'timemodified', 'hidden'));
1140
 
1141
        $letters = new backup_nested_element('grade_letters');
1142
        $letter = new backup_nested_element('grade_letter', 'id', array(
1143
            'lowerboundary', 'letter'));
1144
 
1145
        $grade_settings = new backup_nested_element('grade_settings');
1146
        $grade_setting = new backup_nested_element('grade_setting', 'id', array(
1147
            'name', 'value'));
1148
 
1149
        $gradebook_attributes = new backup_nested_element('attributes', null, array('calculations_freeze'));
1150
 
1151
        // Build the tree
1152
        $gradebook->add_child($gradebook_attributes);
1153
 
1154
        $gradebook->add_child($grade_categories);
1155
        $grade_categories->add_child($grade_category);
1156
 
1157
        $gradebook->add_child($grade_items);
1158
        $grade_items->add_child($grade_item);
1159
        $grade_item->add_child($grade_grades);
1160
        $grade_grades->add_child($grade_grade);
1161
 
1162
        $gradebook->add_child($letters);
1163
        $letters->add_child($letter);
1164
 
1165
        $gradebook->add_child($grade_settings);
1166
        $grade_settings->add_child($grade_setting);
1167
 
1168
        // Define sources
1169
 
1170
        // Add attribute with gradebook calculation freeze date if needed.
1171
        $attributes = new stdClass();
1172
        $gradebookcalculationfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid());
1173
        if ($gradebookcalculationfreeze) {
1174
            $attributes->calculations_freeze = $gradebookcalculationfreeze;
1175
        }
1176
        $gradebook_attributes->set_source_array([$attributes]);
1177
 
1178
        //Include manual, category and the course grade item
1179
        $grade_items_sql ="SELECT * FROM {grade_items}
1180
                           WHERE courseid = :courseid
1181
                           AND (itemtype='manual' OR itemtype='course' OR itemtype='category')";
1182
        $grade_items_params = array('courseid'=>backup::VAR_COURSEID);
1183
        $grade_item->set_source_sql($grade_items_sql, $grade_items_params);
1184
 
1185
        if ($userinfo) {
1186
            $grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
1187
        }
1188
 
1189
        $grade_category_sql = "SELECT gc.*, gi.sortorder
1190
                               FROM {grade_categories} gc
1191
                               JOIN {grade_items} gi ON (gi.iteminstance = gc.id)
1192
                               WHERE gc.courseid = :courseid
1193
                               AND (gi.itemtype='course' OR gi.itemtype='category')
1194
                               ORDER BY gc.parent ASC";//need parent categories before their children
1195
        $grade_category_params = array('courseid'=>backup::VAR_COURSEID);
1196
        $grade_category->set_source_sql($grade_category_sql, $grade_category_params);
1197
 
1198
        $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
1199
 
1200
        // Set the grade settings source, forcing the inclusion of minmaxtouse if not present.
1201
        $settings = array();
1202
        $rs = $DB->get_recordset('grade_settings', array('courseid' => $this->get_courseid()));
1203
        foreach ($rs as $record) {
1204
            $settings[$record->name] = $record;
1205
        }
1206
        $rs->close();
1207
        if (!isset($settings['minmaxtouse'])) {
1208
            $settings['minmaxtouse'] = (object) array('name' => 'minmaxtouse', 'value' => $CFG->grade_minmaxtouse);
1209
        }
1210
        $grade_setting->set_source_array($settings);
1211
 
1212
 
1213
        // Annotations (both as final as far as they are going to be exported in next steps)
1214
        $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
1215
        $grade_item->annotate_ids('outcomefinal', 'outcomeid');
1216
 
1217
        //just in case there are any users not already annotated by the activities
1218
        $grade_grade->annotate_ids('userfinal', 'userid');
1219
 
1220
        // Return the root element
1221
        return $gradebook;
1222
    }
1223
}
1224
 
1225
/**
1226
 * Step in charge of constructing the grade_history.xml file containing the grade histories.
1227
 */
1228
class backup_grade_history_structure_step extends backup_structure_step {
1229
 
1230
    /**
1231
     * Limit the execution.
1232
     *
1233
     * This applies the same logic than the one applied to {@link backup_gradebook_structure_step},
1234
     * because we do not want to save the history of items which are not backed up. At least for now.
1235
     */
1236
    protected function execute_condition() {
1237
        $courseid = $this->get_courseid();
1238
        if ($courseid == SITEID) {
1239
            return false;
1240
        }
1241
 
1242
        return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());
1243
    }
1244
 
1245
    protected function define_structure() {
1246
 
1247
        // Settings to use.
1248
        $userinfo = $this->get_setting_value('users');
1249
        $history = $this->get_setting_value('grade_histories');
1250
 
1251
        // Create the nested elements.
1252
        $bookhistory = new backup_nested_element('grade_history');
1253
        $grades = new backup_nested_element('grade_grades');
1254
        $grade = new backup_nested_element('grade_grade', array('id'), array(
1255
            'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
1256
            'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
1257
            'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
1258
            'excluded', 'feedback', 'feedbackformat', 'information',
1259
            'informationformat', 'timemodified'));
1260
 
1261
        // Build the tree.
1262
        $bookhistory->add_child($grades);
1263
        $grades->add_child($grade);
1264
 
1265
        // This only happens if we are including user info and history.
1266
        if ($userinfo && $history) {
1267
            // Only keep the history of grades related to items which have been backed up, The query is
1268
            // similar (but not identical) to the one used in backup_gradebook_structure_step::define_structure().
1269
            $gradesql = "SELECT ggh.*
1270
                           FROM {grade_grades_history} ggh
1271
                           JOIN {grade_items} gi ON ggh.itemid = gi.id
1272
                          WHERE gi.courseid = :courseid
1273
                            AND (gi.itemtype = 'manual' OR gi.itemtype = 'course' OR gi.itemtype = 'category')";
1274
            $grade->set_source_sql($gradesql, array('courseid' => backup::VAR_COURSEID));
1275
        }
1276
 
1277
        // Annotations. (Final annotations as this step is part of the final task).
1278
        $grade->annotate_ids('scalefinal', 'rawscaleid');
1279
        $grade->annotate_ids('userfinal', 'loggeduser');
1280
        $grade->annotate_ids('userfinal', 'userid');
1281
        $grade->annotate_ids('userfinal', 'usermodified');
1282
 
1283
        // Return the root element.
1284
        return $bookhistory;
1285
    }
1286
 
1287
}
1288
 
1289
/**
1290
 * structure step in charge if constructing the completion.xml file for all the users completion
1291
 * information in a given activity
1292
 */
1293
class backup_userscompletion_structure_step extends backup_structure_step {
1294
 
1295
    /**
1296
     * Skip completion on the front page.
1297
     * @return bool
1298
     */
1299
    protected function execute_condition() {
1300
        return ($this->get_courseid() != SITEID);
1301
    }
1302
 
1303
    protected function define_structure() {
1304
 
1305
        // Define each element separated
1306
        $completions = new backup_nested_element('completions');
1307
 
1308
        $completion = new backup_nested_element('completion', array('id'), array(
1309
            'userid', 'completionstate', 'viewed', 'timemodified'));
1310
 
1311
        // Build the tree
1312
 
1313
        $completions->add_child($completion);
1314
 
1315
        // Define sources
1316
 
1317
        $completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));
1318
 
1319
        // Define id annotations
1320
 
1321
        $completion->annotate_ids('user', 'userid');
1322
 
1323
        $completionviews = new backup_nested_element('completionviews');
1324
        $completionview = new backup_nested_element('completionview', ['id'], ['userid', 'timecreated']);
1325
 
1326
        // Build the tree.
1327
        $completionviews->add_child($completionview);
1328
 
1329
        // Define sources.
1330
        $completionview->set_source_table('course_modules_viewed', ['coursemoduleid' => backup::VAR_MODID]);
1331
 
1332
        // Define id annotations.
1333
        $completionview->annotate_ids('user', 'userid');
1334
 
1335
        $completions->add_child($completionviews);
1336
        // Return the root element (completions).
1337
        return $completions;
1338
 
1339
    }
1340
}
1341
 
1342
/**
1343
 * structure step in charge of constructing the main groups.xml file for all the groups and
1344
 * groupings information already annotated
1345
 */
1346
class backup_groups_structure_step extends backup_structure_step {
1347
 
1348
    protected function define_structure() {
1349
 
1350
        // To know if we are including users.
1351
        $userinfo = $this->get_setting_value('users');
1352
        // To know if we are including groups and groupings.
1353
        $groupinfo = $this->get_setting_value('groups');
1354
 
1355
        // Define each element separated
1356
 
1357
        $groups = new backup_nested_element('groups');
1358
 
1359
        $group = new backup_nested_element('group', array('id'), array(
1360
            'name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey',
1361
            'picture', 'visibility', 'participation', 'timecreated', 'timemodified'));
1362
 
1363
        $groupcustomfields = new backup_nested_element('groupcustomfields');
1364
        $groupcustomfield = new backup_nested_element('groupcustomfield', ['id'], [
1365
            'shortname', 'type', 'value', 'valueformat', 'valuetrust', 'groupid']);
1366
 
1367
        $members = new backup_nested_element('group_members');
1368
 
1369
        $member = new backup_nested_element('group_member', array('id'), array(
1370
            'userid', 'timeadded', 'component', 'itemid'));
1371
 
1372
        $groupings = new backup_nested_element('groupings');
1373
 
1374
        $grouping = new backup_nested_element('grouping', 'id', array(
1375
            'name', 'idnumber', 'description', 'descriptionformat', 'configdata',
1376
            'timecreated', 'timemodified'));
1377
 
1378
        $groupingcustomfields = new backup_nested_element('groupingcustomfields');
1379
        $groupingcustomfield = new backup_nested_element('groupingcustomfield', ['id'], [
1380
            'shortname', 'type', 'value', 'valueformat', 'valuetrust', 'groupingid']);
1381
 
1382
        $groupinggroups = new backup_nested_element('grouping_groups');
1383
 
1384
        $groupinggroup = new backup_nested_element('grouping_group', array('id'), array(
1385
            'groupid', 'timeadded'));
1386
 
1387
        // Build the tree
1388
 
1389
        $groups->add_child($group);
1390
        $groups->add_child($groupcustomfields);
1391
        $groupcustomfields->add_child($groupcustomfield);
1392
        $groups->add_child($groupings);
1393
 
1394
        $group->add_child($members);
1395
        $members->add_child($member);
1396
 
1397
        $groupings->add_child($grouping);
1398
        $groupings->add_child($groupingcustomfields);
1399
        $groupingcustomfields->add_child($groupingcustomfield);
1400
        $grouping->add_child($groupinggroups);
1401
        $groupinggroups->add_child($groupinggroup);
1402
 
1403
        // Define sources
1404
 
1405
        // This only happens if we are including groups/groupings.
1406
        if ($groupinfo) {
1407
            $group->set_source_sql("
1408
                SELECT g.*
1409
                  FROM {groups} g
1410
                  JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1411
                 WHERE bi.backupid = ?
1412
                   AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));
1413
 
1414
            $grouping->set_source_sql("
1415
                SELECT g.*
1416
                  FROM {groupings} g
1417
                  JOIN {backup_ids_temp} bi ON g.id = bi.itemid
1418
                 WHERE bi.backupid = ?
1419
                   AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));
1420
            $groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));
1421
 
1422
            // This only happens if we are including users.
1423
            if ($userinfo) {
1424
                $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
1425
            }
1426
 
1427
            $courseid = $this->task->get_courseid();
1428
            $groupcustomfield->set_source_array($this->get_group_custom_fields_for_backup($courseid));
1429
            $groupingcustomfield->set_source_array($this->get_grouping_custom_fields_for_backup($courseid));
1430
        }
1431
 
1432
        // Define id annotations (as final)
1433
 
1434
        $member->annotate_ids('userfinal', 'userid');
1435
 
1436
        // Define file annotations
1437
 
1438
        $group->annotate_files('group', 'description', 'id');
1439
        $group->annotate_files('group', 'icon', 'id');
1440
        $grouping->annotate_files('grouping', 'description', 'id');
1441
 
1442
        // Return the root element (groups)
1443
        return $groups;
1444
    }
1445
 
1446
    /**
1447
     * Get custom fields array for group
1448
     * @param int $courseid
1449
     * @return array
1450
     */
1451
    protected function get_group_custom_fields_for_backup(int $courseid): array {
1452
        global $DB;
1453
        $handler = \core_group\customfield\group_handler::create();
1454
        $fieldsforbackup = [];
1455
        if ($groups = $DB->get_records('groups', ['courseid' => $courseid], '', 'id')) {
1456
            foreach ($groups as $group) {
1457
                $fieldsforbackup = array_merge($fieldsforbackup, $handler->get_instance_data_for_backup($group->id));
1458
            }
1459
        }
1460
        return $fieldsforbackup;
1461
    }
1462
 
1463
    /**
1464
     * Get custom fields array for grouping
1465
     * @param int $courseid
1466
     * @return array
1467
     */
1468
    protected function get_grouping_custom_fields_for_backup(int $courseid): array {
1469
        global $DB;
1470
        $handler = \core_group\customfield\grouping_handler::create();
1471
        $fieldsforbackup = [];
1472
        if ($groupings = $DB->get_records('groupings', ['courseid' => $courseid], '', 'id')) {
1473
            foreach ($groupings as $grouping) {
1474
                $fieldsforbackup = array_merge($fieldsforbackup, $handler->get_instance_data_for_backup($grouping->id));
1475
            }
1476
        }
1477
        return $fieldsforbackup;
1478
    }
1479
}
1480
 
1481
/**
1482
 * structure step in charge of constructing the main users.xml file for all the users already
1483
 * annotated (final). Includes custom profile fields, preferences, tags, role assignments and
1484
 * overrides.
1485
 */
1486
class backup_users_structure_step extends backup_structure_step {
1487
 
1488
    protected function define_structure() {
1489
        global $CFG;
1490
 
1491
        // To know if we are anonymizing users
1492
        $anonymize = $this->get_setting_value('anonymize');
1493
        // To know if we are including role assignments
1494
        $roleassignments = $this->get_setting_value('role_assignments');
1495
 
1496
        // Define each element separate.
1497
 
1498
        $users = new backup_nested_element('users');
1499
 
1500
        // Create the array of user fields by hand, as far as we have various bits to control
1501
        // anonymize option, password backup, mnethostid...
1502
 
1503
        // First, the fields not needing anonymization nor special handling
1504
        $normalfields = array(
1505
            'confirmed', 'policyagreed', 'deleted',
1506
            'lang', 'theme', 'timezone', 'firstaccess',
1507
            'lastaccess', 'lastlogin', 'currentlogin',
1508
            'mailformat', 'maildigest', 'maildisplay',
1509
            'autosubscribe', 'trackforums', 'timecreated',
1510
            'timemodified', 'trustbitmask');
1511
 
1512
        // Then, the fields potentially needing anonymization
1513
        $anonfields = array(
1514
            'username', 'idnumber', 'email', 'phone1',
1515
            'phone2', 'institution', 'department', 'address',
1516
            'city', 'country', 'lastip', 'picture',
1517
            'description', 'descriptionformat', 'imagealt', 'auth');
1518
        $anonfields = array_merge($anonfields, \core_user\fields::get_name_fields());
1519
 
1520
        // Add anonymized fields to $userfields with custom final element
1521
        foreach ($anonfields as $field) {
1522
            if ($anonymize) {
1523
                $userfields[] = new anonymizer_final_element($field);
1524
            } else {
1525
                $userfields[] = $field; // No anonymization, normally added
1526
            }
1527
        }
1528
 
1529
        // mnethosturl requires special handling (custom final element)
1530
        $userfields[] = new mnethosturl_final_element('mnethosturl');
1531
 
1532
        // password added conditionally
1533
        if (!empty($CFG->includeuserpasswordsinbackup)) {
1534
            $userfields[] = 'password';
1535
        }
1536
 
1537
        // Merge all the fields
1538
        $userfields = array_merge($userfields, $normalfields);
1539
 
1540
        $user = new backup_nested_element('user', array('id', 'contextid'), $userfields);
1541
 
1542
        $customfields = new backup_nested_element('custom_fields');
1543
 
1544
        $customfield = new backup_nested_element('custom_field', array('id'), array(
1545
            'field_name', 'field_type', 'field_data'));
1546
 
1547
        $tags = new backup_nested_element('tags');
1548
 
1549
        $tag = new backup_nested_element('tag', array('id'), array(
1550
            'name', 'rawname'));
1551
 
1552
        $preferences = new backup_nested_element('preferences');
1553
 
1554
        $preference = new backup_nested_element('preference', array('id'), array(
1555
            'name', 'value'));
1556
 
1557
        $roles = new backup_nested_element('roles');
1558
 
1559
        $overrides = new backup_nested_element('role_overrides');
1560
 
1561
        $override = new backup_nested_element('override', array('id'), array(
1562
            'roleid', 'capability', 'permission', 'timemodified',
1563
            'modifierid'));
1564
 
1565
        $assignments = new backup_nested_element('role_assignments');
1566
 
1567
        $assignment = new backup_nested_element('assignment', array('id'), array(
1568
            'roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here
1569
            'sortorder'));
1570
 
1571
        // Build the tree
1572
 
1573
        $users->add_child($user);
1574
 
1575
        $user->add_child($customfields);
1576
        $customfields->add_child($customfield);
1577
 
1578
        $user->add_child($tags);
1579
        $tags->add_child($tag);
1580
 
1581
        $user->add_child($preferences);
1582
        $preferences->add_child($preference);
1583
 
1584
        $user->add_child($roles);
1585
 
1586
        $roles->add_child($overrides);
1587
        $roles->add_child($assignments);
1588
 
1589
        $overrides->add_child($override);
1590
        $assignments->add_child($assignment);
1591
 
1592
        // Define sources
1593
 
1594
        $user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturl
1595
                                 FROM {user} u
1596
                                 JOIN {backup_ids_temp} bi ON bi.itemid = u.id
1597
                            LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . '
1598
                            LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
1599
                                WHERE bi.backupid = ?
1600
                                  AND bi.itemname = ?', array(
1601
                                      backup_helper::is_sqlparam($this->get_backupid()),
1602
                                      backup_helper::is_sqlparam('userfinal')));
1603
 
1604
        // All the rest on information is only added if we arent
1605
        // in an anonymized backup
1606
        if (!$anonymize) {
1607
            $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data
1608
                                            FROM {user_info_field} f
1609
                                            JOIN {user_info_data} d ON d.fieldid = f.id
1610
                                           WHERE d.userid = ?', array(backup::VAR_PARENTID));
1611
 
1612
            $customfield->set_source_alias('shortname', 'field_name');
1613
            $customfield->set_source_alias('datatype',  'field_type');
1614
            $customfield->set_source_alias('data',      'field_data');
1615
 
1616
            $tag->set_source_sql('SELECT t.id, t.name, t.rawname
1617
                                    FROM {tag} t
1618
                                    JOIN {tag_instance} ti ON ti.tagid = t.id
1619
                                   WHERE ti.itemtype = ?
1620
                                     AND ti.itemid = ?', array(
1621
                                         backup_helper::is_sqlparam('user'),
1622
                                         backup::VAR_PARENTID));
1623
 
1624
            $preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));
1625
 
1626
            $override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));
1627
 
1628
            // Assignments only added if specified
1629
            if ($roleassignments) {
1630
                $assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));
1631
            }
1632
 
1633
            // Define id annotations (as final)
1634
            $override->annotate_ids('rolefinal', 'roleid');
1635
        }
1636
        // Return root element (users)
1637
        return $users;
1638
    }
1639
}
1640
 
1641
/**
1642
 * structure step in charge of constructing the block.xml file for one
1643
 * given block (instance and positions). If the block has custom DB structure
1644
 * that will go to a separate file (different step defined in block class)
1645
 */
1646
class backup_block_instance_structure_step extends backup_structure_step {
1647
 
1648
    protected function define_structure() {
1649
        global $DB;
1650
 
1651
        // Define each element separated
1652
 
1653
        $block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
1654
                'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
1655
                'subpagepattern', 'defaultregion', 'defaultweight', 'configdata',
1656
                'timecreated', 'timemodified'));
1657
 
1658
        $positions = new backup_nested_element('block_positions');
1659
 
1660
        $position = new backup_nested_element('block_position', array('id'), array(
1661
            'contextid', 'pagetype', 'subpage', 'visible',
1662
            'region', 'weight'));
1663
 
1664
        // Build the tree
1665
 
1666
        $block->add_child($positions);
1667
        $positions->add_child($position);
1668
 
1669
        // Transform configdata information if needed (process links and friends)
1670
        $blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));
1671
        if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {
1672
            $configdata = array_filter(
1673
                (array) unserialize_object(base64_decode($blockrec->configdata)),
1674
                static function($value): bool {
1675
                    return !($value instanceof __PHP_Incomplete_Class);
1676
                }
1677
            );
1678
 
1679
            foreach ($configdata as $attribute => $value) {
1680
                if (in_array($attribute, $attrstotransform)) {
1681
                    $configdata[$attribute] = $this->contenttransformer->process($value);
1682
                }
1683
            }
1684
            $blockrec->configdata = base64_encode(serialize((object)$configdata));
1685
        }
1686
        $blockrec->contextid = $this->task->get_contextid();
1687
        // Get the version of the block
1688
        $blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version');
1689
 
1690
        // Define sources
1691
 
1692
        $block->set_source_array(array($blockrec));
1693
 
1694
        $position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
1695
 
1696
        // File anotations (for fileareas specified on each block)
1697
        foreach ($this->task->get_fileareas() as $filearea) {
1698
            $block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);
1699
        }
1700
 
1701
        // Return the root element (block)
1702
        return $block;
1703
    }
1704
}
1705
 
1706
/**
1707
 * structure step in charge of constructing the logs.xml file for all the log records found
1708
 * in course. Note that we are sending to backup ALL the log records having cmid = 0. That
1709
 * includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do
1710
 * that just in case they become restored some day in the future
1711
 */
1712
class backup_course_logs_structure_step extends backup_structure_step {
1713
 
1714
    protected function define_structure() {
1715
 
1716
        // Define each element separated
1717
 
1718
        $logs = new backup_nested_element('logs');
1719
 
1720
        $log = new backup_nested_element('log', array('id'), array(
1721
            'time', 'userid', 'ip', 'module',
1722
            'action', 'url', 'info'));
1723
 
1724
        // Build the tree
1725
 
1726
        $logs->add_child($log);
1727
 
1728
        // Define sources (all the records belonging to the course, having cmid = 0)
1729
 
1730
        $log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0)));
1731
 
1732
        // Annotations
1733
        // NOTE: We don't annotate users from logs as far as they MUST be
1734
        //       always annotated by the course (enrol, ras... whatever)
1735
 
1736
        // Return the root element (logs)
1737
 
1738
        return $logs;
1739
    }
1740
}
1741
 
1742
/**
1743
 * structure step in charge of constructing the logs.xml file for all the log records found
1744
 * in activity
1745
 */
1746
class backup_activity_logs_structure_step extends backup_structure_step {
1747
 
1748
    protected function define_structure() {
1749
 
1750
        // Define each element separated
1751
 
1752
        $logs = new backup_nested_element('logs');
1753
 
1754
        $log = new backup_nested_element('log', array('id'), array(
1755
            'time', 'userid', 'ip', 'module',
1756
            'action', 'url', 'info'));
1757
 
1758
        // Build the tree
1759
 
1760
        $logs->add_child($log);
1761
 
1762
        // Define sources
1763
 
1764
        $log->set_source_table('log', array('cmid' => backup::VAR_MODID));
1765
 
1766
        // Annotations
1767
        // NOTE: We don't annotate users from logs as far as they MUST be
1768
        //       always annotated by the activity (true participants).
1769
 
1770
        // Return the root element (logs)
1771
 
1772
        return $logs;
1773
    }
1774
}
1775
 
1776
/**
1777
 * Structure step in charge of constructing the logstores.xml file for the course logs.
1778
 *
1779
 * This backup step will backup the logs for all the enabled logstore subplugins supporting
1780
 * it, for logs belonging to the course level.
1781
 */
1782
class backup_course_logstores_structure_step extends backup_structure_step {
1783
 
1784
    protected function define_structure() {
1785
 
1786
        // Define the structure of logstores container.
1787
        $logstores = new backup_nested_element('logstores');
1788
        $logstore = new backup_nested_element('logstore');
1789
        $logstores->add_child($logstore);
1790
 
1791
        // Add the tool_log logstore subplugins information to the logstore element.
1792
        $this->add_subplugin_structure('logstore', $logstore, true, 'tool', 'log');
1793
 
1794
        return $logstores;
1795
    }
1796
}
1797
 
1798
/**
1799
 * Structure step in charge of constructing the loglastaccess.xml file for the course logs.
1800
 *
1801
 * This backup step will backup the logs of the user_lastaccess table.
1802
 */
1803
class backup_course_loglastaccess_structure_step extends backup_structure_step {
1804
 
1805
    /**
1806
     *  This function creates the structures for the loglastaccess.xml file.
1807
     *  Expected structure would look like this.
1808
     *  <loglastaccesses>
1809
     *      <loglastaccess id=2>
1810
     *          <userid>5</userid>
1811
     *          <timeaccess>1616887341</timeaccess>
1812
     *      </loglastaccess>
1813
     *  </loglastaccesses>
1814
     *
1815
     * @return backup_nested_element
1816
     */
1817
    protected function define_structure() {
1818
 
1819
        // To know if we are including userinfo.
1820
        $userinfo = $this->get_setting_value('users');
1821
 
1822
        // Define the structure of logstores container.
1823
        $lastaccesses = new backup_nested_element('lastaccesses');
1824
        $lastaccess = new backup_nested_element('lastaccess', array('id'), array('userid', 'timeaccess'));
1825
 
1826
        // Define build tree.
1827
        $lastaccesses->add_child($lastaccess);
1828
 
1829
        // This element should only happen if we are including user info.
1830
        if ($userinfo) {
1831
            // Define sources.
1832
            $lastaccess->set_source_sql('
1833
                SELECT id, userid, timeaccess
1834
                  FROM {user_lastaccess}
1835
                 WHERE courseid = ?',
1836
                array(backup::VAR_COURSEID));
1837
 
1838
            // Define userid annotation to user.
1839
            $lastaccess->annotate_ids('user', 'userid');
1840
        }
1841
 
1842
        // Return the root element (lastaccessess).
1843
        return $lastaccesses;
1844
    }
1845
}
1846
 
1847
/**
1848
 * Structure step in charge of constructing the logstores.xml file for the activity logs.
1849
 *
1850
 * Note: Activity structure is completely equivalent to the course one, so just extend it.
1851
 */
1852
class backup_activity_logstores_structure_step extends backup_course_logstores_structure_step {
1853
}
1854
 
1855
/**
1856
 * Course competencies backup structure step.
1857
 */
1858
class backup_course_competencies_structure_step extends backup_structure_step {
1859
 
1860
    protected function define_structure() {
1861
        $userinfo = $this->get_setting_value('users');
1862
 
1863
        $wrapper = new backup_nested_element('course_competencies');
1864
 
1865
        $settings = new backup_nested_element('settings', array('id'), array('pushratingstouserplans'));
1866
        $wrapper->add_child($settings);
1867
 
1868
        $sql = 'SELECT s.pushratingstouserplans
1869
                  FROM {' . \core_competency\course_competency_settings::TABLE . '} s
1870
                 WHERE s.courseid = :courseid';
1871
        $settings->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1872
 
1873
        $competencies = new backup_nested_element('competencies');
1874
        $wrapper->add_child($competencies);
1875
 
1876
        $competency = new backup_nested_element('competency', null, array('id', 'idnumber', 'ruleoutcome',
1877
            'sortorder', 'frameworkid', 'frameworkidnumber'));
1878
        $competencies->add_child($competency);
1879
 
1880
        $sql = 'SELECT c.id, c.idnumber, cc.ruleoutcome, cc.sortorder, f.id AS frameworkid, f.idnumber AS frameworkidnumber
1881
                  FROM {' . \core_competency\course_competency::TABLE . '} cc
1882
                  JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyid
1883
                  JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid
1884
                 WHERE cc.courseid = :courseid
1885
              ORDER BY cc.sortorder';
1886
        $competency->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1887
 
1888
        $usercomps = new backup_nested_element('user_competencies');
1889
        $wrapper->add_child($usercomps);
1890
        if ($userinfo) {
1891
            $usercomp = new backup_nested_element('user_competency', null, array('userid', 'competencyid',
1892
                'proficiency', 'grade'));
1893
            $usercomps->add_child($usercomp);
1894
 
1895
            $sql = 'SELECT ucc.userid, ucc.competencyid, ucc.proficiency, ucc.grade
1896
                      FROM {' . \core_competency\user_competency_course::TABLE . '} ucc
1897
                     WHERE ucc.courseid = :courseid
1898
                       AND ucc.grade IS NOT NULL';
1899
            $usercomp->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));
1900
            $usercomp->annotate_ids('user', 'userid');
1901
        }
1902
 
1903
        return $wrapper;
1904
    }
1905
 
1906
    /**
1907
     * Execute conditions.
1908
     *
1909
     * @return bool
1910
     */
1911
    protected function execute_condition() {
1912
 
1913
        // Do not execute if competencies are not included.
1914
        if (!$this->get_setting_value('competencies')) {
1915
            return false;
1916
        }
1917
 
1918
        return true;
1919
    }
1920
}
1921
 
1922
/**
1923
 * Activity competencies backup structure step.
1924
 */
1925
class backup_activity_competencies_structure_step extends backup_structure_step {
1926
 
1927
    protected function define_structure() {
1928
        $wrapper = new backup_nested_element('course_module_competencies');
1929
 
1930
        $competencies = new backup_nested_element('competencies');
1931
        $wrapper->add_child($competencies);
1932
 
1933
        $competency = new backup_nested_element('competency', null, array('idnumber', 'ruleoutcome',
1934
            'sortorder', 'frameworkidnumber', 'overridegrade'));
1935
        $competencies->add_child($competency);
1936
 
1937
        $sql = 'SELECT c.idnumber, cmc.ruleoutcome, cmc.overridegrade, cmc.sortorder, f.idnumber AS frameworkidnumber
1938
                  FROM {' . \core_competency\course_module_competency::TABLE . '} cmc
1939
                  JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cmc.competencyid
1940
                  JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid
1941
                 WHERE cmc.cmid = :coursemoduleid
1942
              ORDER BY cmc.sortorder';
1943
        $competency->set_source_sql($sql, array('coursemoduleid' => backup::VAR_MODID));
1944
 
1945
        return $wrapper;
1946
    }
1947
 
1948
    /**
1949
     * Execute conditions.
1950
     *
1951
     * @return bool
1952
     */
1953
    protected function execute_condition() {
1954
 
1955
        // Do not execute if competencies are not included.
1956
        if (!$this->get_setting_value('competencies')) {
1957
            return false;
1958
        }
1959
 
1960
        return true;
1961
    }
1962
}
1963
 
1964
/**
1965
 * structure in charge of constructing the inforef.xml file for all the items we want
1966
 * to have referenced there (users, roles, files...)
1967
 */
1968
class backup_inforef_structure_step extends backup_structure_step {
1969
 
1970
    protected function define_structure() {
1971
 
1972
        // Items we want to include in the inforef file.
1973
        $items = backup_helper::get_inforef_itemnames();
1974
 
1975
        // Build the tree
1976
 
1977
        $inforef = new backup_nested_element('inforef');
1978
 
1979
        // For each item, conditionally, if there are already records, build element
1980
        foreach ($items as $itemname) {
1981
            if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {
1982
                $elementroot = new backup_nested_element($itemname . 'ref');
1983
                $element = new backup_nested_element($itemname, array(), array('id'));
1984
                $inforef->add_child($elementroot);
1985
                $elementroot->add_child($element);
1986
                $element->set_source_sql("
1987
                    SELECT itemid AS id
1988
                     FROM {backup_ids_temp}
1989
                    WHERE backupid = ?
1990
                      AND itemname = ?",
1991
                   array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));
1992
            }
1993
        }
1994
 
1995
        // We don't annotate anything there, but rely in the next step
1996
        // (move_inforef_annotations_to_final) that will change all the
1997
        // already saved 'inforref' entries to their 'final' annotations.
1998
        return $inforef;
1999
    }
2000
}
2001
 
2002
/**
2003
 * This step will get all the annotations already processed to inforef.xml file and
2004
 * transform them into 'final' annotations.
2005
 */
2006
class move_inforef_annotations_to_final extends backup_execution_step {
2007
 
2008
    protected function define_execution() {
2009
 
2010
        // Items we want to include in the inforef file
2011
        $items = backup_helper::get_inforef_itemnames();
2012
        $progress = $this->task->get_progress();
2013
        $progress->start_progress($this->get_name(), count($items));
2014
        $done = 1;
2015
        foreach ($items as $itemname) {
2016
            // Delegate to dbops
2017
            backup_structure_dbops::move_annotations_to_final($this->get_backupid(),
2018
                    $itemname, $progress);
2019
            $progress->progress($done++);
2020
        }
2021
        $progress->end_progress();
2022
    }
2023
}
2024
 
2025
/**
2026
 * structure in charge of constructing the files.xml file with all the
2027
 * annotated (final) files along the process. At, the same time, and
2028
 * using one specialised nested_element, will copy them form moodle storage
2029
 * to backup storage
2030
 */
2031
class backup_final_files_structure_step extends backup_structure_step {
2032
 
2033
    protected function define_structure() {
2034
 
2035
        // Define elements
2036
 
2037
        $files = new backup_nested_element('files');
2038
 
2039
        $file = new file_nested_element('file', array('id'), array(
2040
            'contenthash', 'contextid', 'component', 'filearea', 'itemid',
2041
            'filepath', 'filename', 'userid', 'filesize',
2042
            'mimetype', 'status', 'timecreated', 'timemodified',
2043
            'source', 'author', 'license', 'sortorder',
2044
            'repositorytype', 'repositoryid', 'reference'));
2045
 
2046
        // Build the tree
2047
 
2048
        $files->add_child($file);
2049
 
2050
        // Define sources
2051
 
2052
        $file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.reference
2053
                                 FROM {files} f
2054
                                      LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid
2055
                                      LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid
2056
                                      LEFT JOIN {repository} r ON r.id = ri.typeid
2057
                                      JOIN {backup_ids_temp} bi ON f.id = bi.itemid
2058
                                WHERE bi.backupid = ?
2059
                                  AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));
2060
 
2061
        return $files;
2062
    }
2063
}
2064
 
2065
/**
2066
 * Structure step in charge of creating the main moodle_backup.xml file
2067
 * where all the information related to the backup, settings, license and
2068
 * other information needed on restore is added*/
2069
class backup_main_structure_step extends backup_structure_step {
2070
 
2071
    protected function define_structure() {
2072
 
2073
        global $CFG;
2074
 
2075
        $info = array();
2076
 
2077
        $info['name'] = $this->get_setting_value('filename');
2078
        $info['moodle_version'] = $CFG->version;
2079
        $info['moodle_release'] = $CFG->release;
2080
        $info['backup_version'] = $CFG->backup_version;
2081
        $info['backup_release'] = $CFG->backup_release;
2082
        $info['backup_date']    = time();
2083
        $info['backup_uniqueid']= $this->get_backupid();
2084
        $info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());
2085
        $info['include_files'] = backup_controller_dbops::backup_includes_files($this->get_backupid());
2086
        $info['include_file_references_to_external_content'] =
2087
                backup_controller_dbops::backup_includes_file_references($this->get_backupid());
2088
        $info['original_wwwroot']=$CFG->wwwroot;
2089
        $info['original_site_identifier_hash'] = md5(get_site_identifier());
2090
        $info['original_course_id'] = $this->get_courseid();
2091
        $originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());
2092
        $info['original_course_format'] = $originalcourseinfo->format;
2093
        $info['original_course_fullname']  = $originalcourseinfo->fullname;
2094
        $info['original_course_shortname'] = $originalcourseinfo->shortname;
2095
        $info['original_course_startdate'] = $originalcourseinfo->startdate;
2096
        $info['original_course_enddate']   = $originalcourseinfo->enddate;
2097
        $info['original_course_contextid'] = context_course::instance($this->get_courseid())->id;
2098
        $info['original_system_contextid'] = context_system::instance()->id;
2099
 
2100
        // Get more information from controller
2101
        list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information(
2102
                $this->get_backupid(), $this->get_task()->get_progress());
2103
 
2104
        // Define elements
2105
 
2106
        $moodle_backup = new backup_nested_element('moodle_backup');
2107
 
2108
        $information = new backup_nested_element('information', null, array(
2109
            'name', 'moodle_version', 'moodle_release', 'backup_version',
2110
            'backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot',
2111
            'original_site_identifier_hash', 'original_course_id', 'original_course_format',
2112
            'original_course_fullname', 'original_course_shortname', 'original_course_startdate', 'original_course_enddate',
2113
            'original_course_contextid', 'original_system_contextid'));
2114
 
2115
        $details = new backup_nested_element('details');
2116
 
2117
        $detail = new backup_nested_element('detail', array('backup_id'), array(
2118
            'type', 'format', 'interactive', 'mode',
2119
            'execution', 'executiontime'));
2120
 
2121
        $contents = new backup_nested_element('contents');
2122
 
2123
        $activities = new backup_nested_element('activities');
2124
 
2125
        $activity = new backup_nested_element('activity', null, array(
2126
            'moduleid', 'sectionid', 'modulename', 'title',
2127
            'directory'));
2128
 
2129
        $sections = new backup_nested_element('sections');
2130
 
2131
        $section = new backup_nested_element('section', null, array(
2132
            'sectionid', 'title', 'directory'));
2133
 
2134
        $course = new backup_nested_element('course', null, array(
2135
            'courseid', 'title', 'directory'));
2136
 
2137
        $settings = new backup_nested_element('settings');
2138
 
2139
        $setting = new backup_nested_element('setting', null, array(
2140
            'level', 'section', 'activity', 'name', 'value'));
2141
 
2142
        // Build the tree
2143
 
2144
        $moodle_backup->add_child($information);
2145
 
2146
        $information->add_child($details);
2147
        $details->add_child($detail);
2148
 
2149
        $information->add_child($contents);
2150
        if (!empty($cinfo['activities'])) {
2151
            $contents->add_child($activities);
2152
            $activities->add_child($activity);
2153
        }
2154
        if (!empty($cinfo['sections'])) {
2155
            $contents->add_child($sections);
2156
            $sections->add_child($section);
2157
        }
2158
        if (!empty($cinfo['course'])) {
2159
            $contents->add_child($course);
2160
        }
2161
 
2162
        $information->add_child($settings);
2163
        $settings->add_child($setting);
2164
 
2165
 
2166
        // Set the sources
2167
 
2168
        $information->set_source_array(array((object)$info));
2169
 
2170
        $detail->set_source_array($dinfo);
2171
 
2172
        $activity->set_source_array($cinfo['activities']);
2173
 
2174
        $section->set_source_array($cinfo['sections']);
2175
 
2176
        $course->set_source_array($cinfo['course']);
2177
 
2178
        $setting->set_source_array($sinfo);
2179
 
2180
        // Prepare some information to be sent to main moodle_backup.xml file
2181
        return $moodle_backup;
2182
    }
2183
 
2184
}
2185
 
2186
/**
2187
 * Execution step that will generate the final zip (.mbz) file with all the contents
2188
 */
2189
class backup_zip_contents extends backup_execution_step implements file_progress {
2190
    /**
2191
     * @var bool True if we have started tracking progress
2192
     */
2193
    protected $startedprogress;
2194
 
2195
    protected function define_execution() {
2196
 
2197
        // Get basepath
2198
        $basepath = $this->get_basepath();
2199
 
2200
        // Get the list of files in directory
2201
        $filestemp = get_directory_list($basepath, '', false, true, true);
2202
        $files = array();
2203
        foreach ($filestemp as $file) { // Add zip paths and fs paths to all them
2204
            $files[$file] = $basepath . '/' . $file;
2205
        }
2206
 
2207
        // Add the log file if exists
2208
        $logfilepath = $basepath . '.log';
2209
        if (file_exists($logfilepath)) {
2210
             $files['moodle_backup.log'] = $logfilepath;
2211
        }
2212
 
2213
        // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
2214
        $zipfile = $basepath . '/backup.mbz';
2215
 
2216
        // Get the zip packer
2217
        $zippacker = get_file_packer('application/vnd.moodle.backup');
2218
 
2219
        // Track overall progress for the 2 long-running steps (archive to
2220
        // pathname, get backup information).
2221
        $reporter = $this->task->get_progress();
2222
        $reporter->start_progress('backup_zip_contents', 2);
2223
 
2224
        // Zip files
2225
        $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this);
2226
 
2227
        // If any sub-progress happened, end it.
2228
        if ($this->startedprogress) {
2229
            $this->task->get_progress()->end_progress();
2230
            $this->startedprogress = false;
2231
        } else {
2232
            // No progress was reported, manually move it on to the next overall task.
2233
            $reporter->progress(1);
2234
        }
2235
 
2236
        // Something went wrong.
2237
        if ($result === false) {
2238
            @unlink($zipfile);
2239
            throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip');
2240
        }
2241
        // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid.
2242
        try {
2243
            backup_general_helper::get_backup_information_from_mbz($zipfile, $this);
2244
        } catch (backup_helper_exception $e) {
2245
            @unlink($zipfile);
2246
            throw new backup_step_exception('error_zip_packing', '', $e->debuginfo);
2247
        }
2248
 
2249
        // If any sub-progress happened, end it.
2250
        if ($this->startedprogress) {
2251
            $this->task->get_progress()->end_progress();
2252
            $this->startedprogress = false;
2253
        } else {
2254
            $reporter->progress(2);
2255
        }
2256
        $reporter->end_progress();
2257
    }
2258
 
2259
    /**
2260
     * Implementation for file_progress interface to display unzip progress.
2261
     *
2262
     * @param int $progress Current progress
2263
     * @param int $max Max value
2264
     */
2265
    public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
2266
        $reporter = $this->task->get_progress();
2267
 
2268
        // Start tracking progress if necessary.
2269
        if (!$this->startedprogress) {
2270
            $reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE)
2271
                    ? \core\progress\base::INDETERMINATE : $max);
2272
            $this->startedprogress = true;
2273
        }
2274
 
2275
        // Pass progress through to whatever handles it.
2276
        $reporter->progress(($progress == file_progress::INDETERMINATE)
2277
                ? \core\progress\base::INDETERMINATE : $progress);
2278
     }
2279
}
2280
 
2281
/**
2282
 * This step will send the generated backup file to its final destination
2283
 */
2284
class backup_store_backup_file extends backup_execution_step {
2285
 
2286
    protected function define_execution() {
2287
 
2288
        // Get basepath
2289
        $basepath = $this->get_basepath();
2290
 
2291
        // Calculate the zip fullpath (in OS temp area it's always backup.mbz)
2292
        $zipfile = $basepath . '/backup.mbz';
2293
 
2294
        $has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid());
2295
        // Perform storage and return it (TODO: shouldn't be array but proper result object)
2296
        return array(
2297
            'backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile,
2298
                    $this->task->get_progress()),
2299
            'include_file_references_to_external_content' => $has_file_references
2300
        );
2301
    }
2302
}
2303
 
2304
 
2305
/**
2306
 * This step will search for all the activity (not calculations, categories nor aggregations) grade items
2307
 * and put them to the backup_ids tables, to be used later as base to backup them
2308
 */
2309
class backup_activity_grade_items_to_ids extends backup_execution_step {
2310
 
2311
    protected function define_execution() {
2312
 
2313
        // Fetch all activity grade items
2314
        if ($items = grade_item::fetch_all(array(
2315
                         'itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),
2316
                         'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {
2317
            // Annotate them in backup_ids
2318
            foreach ($items as $item) {
2319
                backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);
2320
            }
2321
        }
2322
    }
2323
}
2324
 
2325
 
2326
/**
2327
 * This step allows enrol plugins to annotate custom fields.
2328
 *
2329
 * @package   core_backup
2330
 * @copyright 2014 University of Wisconsin
2331
 * @author    Matt Petro
2332
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2333
 */
2334
class backup_enrolments_execution_step extends backup_execution_step {
2335
 
2336
    /**
2337
     * Function that will contain all the code to be executed.
2338
     */
2339
    protected function define_execution() {
2340
        global $DB;
2341
 
2342
        $plugins = enrol_get_plugins(true);
2343
        $enrols = $DB->get_records('enrol', array(
2344
                'courseid' => $this->task->get_courseid()));
2345
 
2346
        // Allow each enrol plugin to add annotations.
2347
        foreach ($enrols as $enrol) {
2348
            if (isset($plugins[$enrol->enrol])) {
2349
                $plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol);
2350
            }
2351
        }
2352
    }
2353
 
2354
    /**
2355
     * Annotate a single name/id pair.
2356
     * This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}.
2357
     *
2358
     * @param string $itemname
2359
     * @param int $itemid
2360
     */
2361
    public function annotate_id($itemname, $itemid) {
2362
        backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid);
2363
    }
2364
}
2365
 
2366
/**
2367
 * This step will annotate all the groups and groupings belonging to the course
2368
 */
2369
class backup_annotate_course_groups_and_groupings extends backup_execution_step {
2370
 
2371
    protected function define_execution() {
2372
        global $DB;
2373
 
2374
        // Get all the course groups
2375
        if ($groups = $DB->get_records('groups', array(
2376
                'courseid' => $this->task->get_courseid()))) {
2377
            foreach ($groups as $group) {
2378
                backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id);
2379
            }
2380
        }
2381
 
2382
        // Get all the course groupings
2383
        if ($groupings = $DB->get_records('groupings', array(
2384
                'courseid' => $this->task->get_courseid()))) {
2385
            foreach ($groupings as $grouping) {
2386
                backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id);
2387
            }
2388
        }
2389
    }
2390
}
2391
 
2392
/**
2393
 * This step will annotate all the groups belonging to already annotated groupings
2394
 */
2395
class backup_annotate_groups_from_groupings extends backup_execution_step {
2396
 
2397
    protected function define_execution() {
2398
        global $DB;
2399
 
2400
        // Fetch all the annotated groupings
2401
        if ($groupings = $DB->get_records('backup_ids_temp', array(
2402
                'backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {
2403
            foreach ($groupings as $grouping) {
2404
                if ($groups = $DB->get_records('groupings_groups', array(
2405
                        'groupingid' => $grouping->itemid))) {
2406
                    foreach ($groups as $group) {
2407
                        backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);
2408
                    }
2409
                }
2410
            }
2411
        }
2412
    }
2413
}
2414
 
2415
/**
2416
 * This step will annotate all the scales belonging to already annotated outcomes
2417
 */
2418
class backup_annotate_scales_from_outcomes extends backup_execution_step {
2419
 
2420
    protected function define_execution() {
2421
        global $DB;
2422
 
2423
        // Fetch all the annotated outcomes
2424
        if ($outcomes = $DB->get_records('backup_ids_temp', array(
2425
                'backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {
2426
            foreach ($outcomes as $outcome) {
2427
                if ($scale = $DB->get_record('grade_outcomes', array(
2428
                        'id' => $outcome->itemid))) {
2429
                    // Annotate as scalefinal because it's > 0
2430
                    backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);
2431
                }
2432
            }
2433
        }
2434
    }
2435
}
2436
 
2437
/**
2438
 * This step will generate all the file annotations for the already
2439
 * annotated (final) question_categories. It calculates the different
2440
 * contexts that are being backup and, annotates all the files
2441
 * on every context belonging to the "question" component. As far as
2442
 * we are always including *complete* question banks it is safe and
2443
 * optimal to do that in this (one pass) way
2444
 */
2445
class backup_annotate_all_question_files extends backup_execution_step {
2446
 
2447
    protected function define_execution() {
2448
        global $DB;
2449
 
2450
        // Get all the different contexts for the final question_categories
2451
        // annotated along the whole backup
2452
        $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid
2453
                                        FROM {question_categories} qc
2454
                                        JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
2455
                                       WHERE bi.backupid = ?
2456
                                         AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
2457
        // To know about qtype specific components/fileareas
2458
        $components = backup_qtype_plugin::get_components_and_fileareas();
2459
        $progress = $this->task->get_progress();
2460
        $progress->start_progress($this->get_name());
2461
        // Let's loop
2462
        foreach($rs as $record) {
2463
            // Backup all the file areas the are managed by the core question component.
2464
            // That is, by the question_type base class. In particular, we don't want
2465
            // to include files belonging to responses here.
2466
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'questiontext', null,
2467
                                        $progress);
2468
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'generalfeedback', null,
2469
                                        $progress);
2470
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answer', null,
2471
                                        $progress);
2472
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answerfeedback', null,
2473
                                        $progress);
2474
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'hint', null,
2475
                                        $progress);
2476
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'correctfeedback', null,
2477
                                        $progress);
2478
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question',
2479
                                        'partiallycorrectfeedback', null, $progress);
2480
            backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'incorrectfeedback', null,
2481
                                        $progress);
2482
 
2483
            // For files belonging to question types, we make the leap of faith that
2484
            // all the files belonging to the question type are part of the question definition,
2485
            // so we can just backup all the files in bulk, without specifying each
2486
            // file area name separately.
2487
            foreach ($components as $component => $fileareas) {
2488
                backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null,
2489
                                            $progress);
2490
            }
2491
        }
2492
        $progress->end_progress();
2493
        $rs->close();
2494
    }
2495
}
2496
 
2497
/**
2498
 * structure step in charge of constructing the questions.xml file for all the
2499
 * question categories and questions required by the backup
2500
 * and letters related to one activity.
2501
 */
2502
class backup_questions_structure_step extends backup_structure_step {
2503
 
2504
    protected function define_structure() {
2505
 
2506
        // Define each element separately.
2507
        $qcategories = new backup_nested_element('question_categories');
2508
 
2509
        $qcategory = new backup_nested_element('question_category', ['id'],
2510
            [
2511
                'name',
2512
                'contextid',
2513
                'contextlevel',
2514
                'contextinstanceid',
2515
                'info',
2516
                'infoformat',
2517
                'stamp',
2518
                'parent',
2519
                'sortorder',
2520
                'idnumber',
2521
            ]);
2522
 
2523
        $questionbankentries = new backup_nested_element('question_bank_entries');
2524
 
2525
        $questionbankentry = new backup_nested_element('question_bank_entry', ['id'],
2526
            [
2527
                'questioncategoryid',
2528
                'idnumber',
2529
                'ownerid',
2530
            ]);
2531
 
2532
        $questionversions = new backup_nested_element('question_version');
2533
 
2534
        $questionverion = new backup_nested_element('question_versions', ['id'], ['version', 'status']);
2535
 
2536
        $questions = new backup_nested_element('questions');
2537
 
2538
        $question = new backup_nested_element('question', ['id'],
2539
            [
2540
                'parent',
2541
                'name',
2542
                'questiontext',
2543
                'questiontextformat',
2544
                'generalfeedback',
2545
                'generalfeedbackformat',
2546
                'defaultmark',
2547
                'penalty',
2548
                'qtype',
2549
                'length',
2550
                'stamp',
2551
                'timecreated',
2552
                'timemodified',
2553
                'createdby',
2554
                'modifiedby',
2555
            ]);
2556
 
2557
        // Attach qtype plugin structure to $question element, only one allowed.
2558
        $this->add_plugin_structure('qtype', $question, false);
2559
 
2560
        // Attach qbank plugin stucture to $question element, multiple allowed.
2561
        $this->add_plugin_structure('qbank', $question, true);
2562
 
2563
        // attach local plugin stucture to $question element, multiple allowed
2564
        $this->add_plugin_structure('local', $question, true);
2565
 
2566
        $qhints = new backup_nested_element('question_hints');
2567
 
2568
        $qhint = new backup_nested_element('question_hint', ['id'],
2569
            [
2570
                'hint',
2571
                'hintformat',
2572
                'shownumcorrect',
2573
                'clearwrong',
2574
                'options',
2575
            ]);
2576
 
2577
        $tags = new backup_nested_element('tags');
2578
 
2579
        $tag = new backup_nested_element('tag', ['id', 'contextid'], ['name', 'rawname']);
2580
 
2581
        // Build the initial tree.
2582
        $qcategories->add_child($qcategory);
2583
        $qcategory->add_child($questionbankentries);
2584
        $questionbankentries->add_child($questionbankentry);
2585
        $questionbankentry->add_child($questionversions);
2586
        $questionversions->add_child($questionverion);
2587
        $questionverion->add_child($questions);
2588
        $questions->add_child($question);
2589
        $question->add_child($qhints);
2590
        $qhints->add_child($qhint);
2591
 
2592
        // Add question tags.
2593
        $question->add_child($tags);
2594
        $tags->add_child($tag);
2595
 
2596
        $qcategory->set_source_sql("
2597
            SELECT gc.*,
2598
                   contextlevel,
2599
                   instanceid AS contextinstanceid
2600
              FROM {question_categories} gc
2601
              JOIN {backup_ids_temp} bi ON bi.itemid = gc.id
2602
              JOIN {context} co ON co.id = gc.contextid
2603
             WHERE bi.backupid = ?
2604
               AND bi.itemname = 'question_categoryfinal'", [backup::VAR_BACKUPID]);
2605
 
2606
        $questionbankentry->set_source_table('question_bank_entries', ['questioncategoryid' => backup::VAR_PARENTID]);
2607
 
2608
        $questionverion->set_source_table('question_versions', ['questionbankentryid' => backup::VAR_PARENTID]);
2609
 
2610
        $question->set_source_sql('
2611
                SELECT q.*
2612
                 FROM {question} q
2613
                 JOIN {question_versions} qv ON qv.questionid = q.id
2614
                 JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
2615
                WHERE qv.id = ?', [backup::VAR_PARENTID]);
2616
 
2617
        $qhint->set_source_sql('
2618
                SELECT *
2619
                 FROM {question_hints}
2620
                WHERE questionid = :questionid
2621
             ORDER BY id', ['questionid' => backup::VAR_PARENTID]);
2622
 
2623
        $tag->set_source_sql("SELECT t.id, ti.contextid, t.name, t.rawname
2624
                                FROM {tag} t
2625
                                JOIN {tag_instance} ti ON ti.tagid = t.id
2626
                               WHERE ti.itemid = ?
2627
                                 AND ti.itemtype = 'question'
2628
                                 AND ti.component = 'core_question'", [backup::VAR_PARENTID]);
2629
 
2630
        // Don't need to annotate ids nor files.
2631
        // ...(already done by {@see backup_annotate_all_question_files()}.
2632
 
2633
        return $qcategories;
2634
    }
2635
}
2636
 
2637
 
2638
 
2639
/**
2640
 * This step will generate all the file  annotations for the already
2641
 * annotated (final) users. Need to do this here because each user
2642
 * has its own context and structure tasks only are able to handle
2643
 * one context. Also, this step will guarantee that every user has
2644
 * its context created (req for other steps)
2645
 */
2646
class backup_annotate_all_user_files extends backup_execution_step {
2647
 
2648
    protected function define_execution() {
2649
        global $DB;
2650
 
2651
        // List of fileareas we are going to annotate
2652
        $fileareas = array('profile', 'icon');
2653
 
2654
        // Fetch all annotated (final) users
2655
        $rs = $DB->get_recordset('backup_ids_temp', array(
2656
            'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
2657
        $progress = $this->task->get_progress();
2658
        $progress->start_progress($this->get_name());
2659
        foreach ($rs as $record) {
2660
            $userid = $record->itemid;
2661
            $userctx = context_user::instance($userid, IGNORE_MISSING);
2662
            if (!$userctx) {
2663
                continue; // User has not context, sure it's a deleted user, so cannot have files
2664
            }
2665
            // Proceed with every user filearea
2666
            foreach ($fileareas as $filearea) {
2667
                // We don't need to specify itemid ($userid - 5th param) as far as by
2668
                // context we can get all the associated files. See MDL-22092
2669
                backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null);
2670
                $progress->progress();
2671
            }
2672
        }
2673
        $progress->end_progress();
2674
        $rs->close();
2675
    }
2676
}
2677
 
2678
 
2679
/**
2680
 * Defines the backup step for advanced grading methods attached to the activity module
2681
 */
2682
class backup_activity_grading_structure_step extends backup_structure_step {
2683
 
2684
    /**
2685
     * Include the grading.xml only if the module supports advanced grading
2686
     */
2687
    protected function execute_condition() {
2688
 
2689
        // No grades on the front page.
2690
        if ($this->get_courseid() == SITEID) {
2691
            return false;
2692
        }
2693
 
2694
        return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false);
2695
    }
2696
 
2697
    /**
2698
     * Declares the gradable areas structures and data sources
2699
     */
2700
    protected function define_structure() {
2701
 
2702
        // To know if we are including userinfo
2703
        $userinfo = $this->get_setting_value('userinfo');
2704
 
2705
        // Define the elements
2706
 
2707
        $areas = new backup_nested_element('areas');
2708
 
2709
        $area = new backup_nested_element('area', array('id'), array(
2710
            'areaname', 'activemethod'));
2711
 
2712
        $definitions = new backup_nested_element('definitions');
2713
 
2714
        $definition = new backup_nested_element('definition', array('id'), array(
2715
            'method', 'name', 'description', 'descriptionformat', 'status',
2716
            'timecreated', 'timemodified', 'options'));
2717
 
2718
        $instances = new backup_nested_element('instances');
2719
 
2720
        $instance = new backup_nested_element('instance', array('id'), array(
2721
            'raterid', 'itemid', 'rawgrade', 'status', 'feedback',
2722
            'feedbackformat', 'timemodified'));
2723
 
2724
        // Build the tree including the method specific structures
2725
        // (beware - the order of how gradingform plugins structures are attached is important)
2726
        $areas->add_child($area);
2727
        // attach local plugin stucture to $area element, multiple allowed
2728
        $this->add_plugin_structure('local', $area, true);
2729
        $area->add_child($definitions);
2730
        $definitions->add_child($definition);
2731
        $this->add_plugin_structure('gradingform', $definition, true);
2732
        // attach local plugin stucture to $definition element, multiple allowed
2733
        $this->add_plugin_structure('local', $definition, true);
2734
        $definition->add_child($instances);
2735
        $instances->add_child($instance);
2736
        $this->add_plugin_structure('gradingform', $instance, false);
2737
        // attach local plugin stucture to $instance element, multiple allowed
2738
        $this->add_plugin_structure('local', $instance, true);
2739
 
2740
        // Define data sources
2741
 
2742
        $area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID,
2743
            'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename())));
2744
 
2745
        $definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID));
2746
 
2747
        if ($userinfo) {
2748
            $instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID));
2749
        }
2750
 
2751
        // Annotate references
2752
        $definition->annotate_files('grading', 'description', 'id');
2753
        $instance->annotate_ids('user', 'raterid');
2754
 
2755
        // Return the root element
2756
        return $areas;
2757
    }
2758
}
2759
 
2760
 
2761
/**
2762
 * structure step in charge of constructing the grades.xml file for all the grade items
2763
 * and letters related to one activity
2764
 */
2765
class backup_activity_grades_structure_step extends backup_structure_step {
2766
 
2767
    /**
2768
     * No grades on the front page.
2769
     * @return bool
2770
     */
2771
    protected function execute_condition() {
2772
        return ($this->get_courseid() != SITEID);
2773
    }
2774
 
2775
    protected function define_structure() {
2776
        global $CFG;
2777
 
2778
        require_once($CFG->libdir . '/grade/constants.php');
2779
 
2780
        // To know if we are including userinfo
2781
        $userinfo = $this->get_setting_value('userinfo');
2782
 
2783
        // Define each element separated
2784
 
2785
        $book = new backup_nested_element('activity_gradebook');
2786
 
2787
        $items = new backup_nested_element('grade_items');
2788
 
2789
        $item = new backup_nested_element('grade_item', array('id'), array(
2790
            'categoryid', 'itemname', 'itemtype', 'itemmodule',
2791
            'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
2792
            'calculation', 'gradetype', 'grademax', 'grademin',
2793
            'scaleid', 'outcomeid', 'gradepass', 'multfactor',
2794
            'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
2795
            'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
2796
            'needsupdate', 'timecreated', 'timemodified'));
2797
 
2798
        $grades = new backup_nested_element('grade_grades');
2799
 
2800
        $grade = new backup_nested_element('grade_grade', array('id'), array(
2801
            'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
2802
            'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
2803
            'locked', 'locktime', 'exported', 'overridden',
2804
            'excluded', 'feedback', 'feedbackformat', 'information',
2805
            'informationformat', 'timecreated', 'timemodified',
2806
            'aggregationstatus', 'aggregationweight'));
2807
 
2808
        $letters = new backup_nested_element('grade_letters');
2809
 
2810
        $letter = new backup_nested_element('grade_letter', 'id', array(
2811
            'lowerboundary', 'letter'));
2812
 
2813
        // Build the tree
2814
 
2815
        $book->add_child($items);
2816
        $items->add_child($item);
2817
 
2818
        $item->add_child($grades);
2819
        $grades->add_child($grade);
2820
 
2821
        $book->add_child($letters);
2822
        $letters->add_child($letter);
2823
 
2824
        // Define sources
2825
 
2826
        $item->set_source_sql("SELECT gi.*
2827
                               FROM {grade_items} gi
2828
                               JOIN {backup_ids_temp} bi ON gi.id = bi.itemid
2829
                               WHERE bi.backupid = ?
2830
                               AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2831
 
2832
        // This only happens if we are including user info
2833
        if ($userinfo) {
2834
            $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
2835
            $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, 'id');
2836
        }
2837
 
2838
        $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
2839
 
2840
        // Annotations
2841
 
2842
        $item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
2843
        $item->annotate_ids('outcome', 'outcomeid');
2844
 
2845
        $grade->annotate_ids('user', 'userid');
2846
        $grade->annotate_ids('user', 'usermodified');
2847
 
2848
        // Return the root element (book)
2849
 
2850
        return $book;
2851
    }
2852
}
2853
 
2854
/**
2855
 * Structure step in charge of constructing the grade history of an activity.
2856
 *
2857
 * This step is added to the task regardless of the setting 'grade_histories'.
2858
 * The reason is to allow for a more flexible step in case the logic needs to be
2859
 * split accross different settings to control the history of items and/or grades.
2860
 */
2861
class backup_activity_grade_history_structure_step extends backup_structure_step {
2862
 
2863
    /**
2864
     * No grades on the front page.
2865
     * @return bool
2866
     */
2867
    protected function execute_condition() {
2868
        return ($this->get_courseid() != SITEID);
2869
    }
2870
 
2871
    protected function define_structure() {
2872
        global $CFG;
2873
 
2874
        require_once($CFG->libdir . '/grade/constants.php');
2875
 
2876
        // Settings to use.
2877
        $userinfo = $this->get_setting_value('userinfo');
2878
        $history = $this->get_setting_value('grade_histories');
2879
 
2880
        // Create the nested elements.
2881
        $bookhistory = new backup_nested_element('grade_history');
2882
        $grades = new backup_nested_element('grade_grades');
2883
        $grade = new backup_nested_element('grade_grade', array('id'), array(
2884
            'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
2885
            'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
2886
            'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
2887
            'excluded', 'feedback', 'feedbackformat', 'information',
2888
            'informationformat', 'timemodified'));
2889
 
2890
        // Build the tree.
2891
        $bookhistory->add_child($grades);
2892
        $grades->add_child($grade);
2893
 
2894
        // This only happens if we are including user info and history.
2895
        if ($userinfo && $history) {
2896
            // Define sources. Only select the history related to existing activity items.
2897
            $grade->set_source_sql("SELECT ggh.*
2898
                                     FROM {grade_grades_history} ggh
2899
                                     JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid
2900
                                    WHERE bi.backupid = ?
2901
                                      AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
2902
            $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA, 'id');
2903
        }
2904
 
2905
        // Annotations.
2906
        $grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0.
2907
        $grade->annotate_ids('user', 'loggeduser');
2908
        $grade->annotate_ids('user', 'userid');
2909
        $grade->annotate_ids('user', 'usermodified');
2910
 
2911
        // Return the root element.
2912
        return $bookhistory;
2913
    }
2914
}
2915
 
2916
/**
2917
 * Backups up the course completion information for the course.
2918
 */
2919
class backup_course_completion_structure_step extends backup_structure_step {
2920
 
2921
    protected function execute_condition() {
2922
 
2923
        // No completion on front page.
2924
        if ($this->get_courseid() == SITEID) {
2925
            return false;
2926
        }
2927
 
2928
        // Check that all activities have been included
2929
        if ($this->task->is_excluding_activities()) {
2930
            return false;
2931
        }
2932
        return true;
2933
    }
2934
 
2935
    /**
2936
     * The structure of the course completion backup
2937
     *
2938
     * @return backup_nested_element
2939
     */
2940
    protected function define_structure() {
2941
 
2942
        // To know if we are including user completion info
2943
        $userinfo = $this->get_setting_value('userscompletion');
2944
 
2945
        $cc = new backup_nested_element('course_completion');
2946
 
2947
        $criteria = new backup_nested_element('course_completion_criteria', array('id'), array(
2948
            'course', 'criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod',
2949
            'timeend', 'gradepass', 'role', 'roleshortname'
2950
        ));
2951
 
2952
        $criteriacompletions = new backup_nested_element('course_completion_crit_completions');
2953
 
2954
        $criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array(
2955
            'criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted'
2956
        ));
2957
 
2958
        $coursecompletions = new backup_nested_element('course_completions', array('id'), array(
2959
            'userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate'
2960
        ));
2961
 
2962
        $aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array(
2963
            'course','criteriatype','method','value'
2964
        ));
2965
 
2966
        $cc->add_child($criteria);
2967
            $criteria->add_child($criteriacompletions);
2968
                $criteriacompletions->add_child($criteriacomplete);
2969
        $cc->add_child($coursecompletions);
2970
        $cc->add_child($aggregatemethod);
2971
 
2972
        // We need some extra data for the restore.
2973
        // - courseinstances shortname rather than an ID.
2974
        // - roleshortname in case restoring on a different site.
2975
        $sourcesql = "SELECT ccc.*, c.shortname AS courseinstanceshortname, r.shortname AS roleshortname
2976
                        FROM {course_completion_criteria} ccc
2977
                   LEFT JOIN {course} c ON c.id = ccc.courseinstance
2978
                   LEFT JOIN {role} r ON r.id = ccc.role
2979
                       WHERE ccc.course = ?";
2980
        $criteria->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
2981
 
2982
        $aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));
2983
 
2984
        if ($userinfo) {
2985
            $criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));
2986
            $coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));
2987
        }
2988
 
2989
        $criteria->annotate_ids('role', 'role');
2990
        $criteriacomplete->annotate_ids('user', 'userid');
2991
        $coursecompletions->annotate_ids('user', 'userid');
2992
 
2993
        return $cc;
2994
 
2995
    }
2996
}
2997
 
2998
/**
2999
 * Backup completion defaults for each module type.
3000
 *
3001
 * @package     core_backup
3002
 * @copyright   2017 Marina Glancy
3003
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3004
 */
3005
class backup_completion_defaults_structure_step extends backup_structure_step {
3006
 
3007
    /**
3008
     * To conditionally decide if one step will be executed or no
3009
     */
3010
    protected function execute_condition() {
3011
        // No completion on front page.
3012
        if ($this->get_courseid() == SITEID) {
3013
            return false;
3014
        }
3015
        return true;
3016
    }
3017
 
3018
    /**
3019
     * The structure of the course completion backup
3020
     *
3021
     * @return backup_nested_element
3022
     */
3023
    protected function define_structure() {
3024
 
3025
        $cc = new backup_nested_element('course_completion_defaults');
3026
 
3027
        $defaults = new backup_nested_element('course_completion_default', array('id'), array(
3028
            'modulename', 'completion', 'completionview', 'completionusegrade', 'completionpassgrade',
3029
            'completionexpected', 'customrules'
3030
        ));
3031
 
3032
        // Use module name instead of module id so we can insert into another site later.
3033
        $sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade,
3034
                  d.completionpassgrade, d.completionexpected, d.customrules
3035
                FROM {course_completion_defaults} d join {modules} m on d.module = m.id
3036
                WHERE d.course = ?";
3037
        $defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID));
3038
 
3039
        $cc->add_child($defaults);
3040
        return $cc;
3041
 
3042
    }
3043
}
3044
 
3045
/**
3046
 * Structure step in charge of constructing the contentbank.xml file for all the contents found in a given context
3047
 */
3048
class backup_contentbankcontent_structure_step extends backup_structure_step {
3049
 
3050
    /**
3051
     * Define structure for content bank step
3052
     */
3053
    protected function define_structure() {
3054
 
3055
        // Define each element separated.
3056
        $contents = new backup_nested_element('contents');
3057
        $content = new backup_nested_element('content', ['id'], [
3058
            'name', 'contenttype', 'instanceid', 'configdata', 'usercreated', 'usermodified', 'timecreated', 'timemodified']);
3059
 
3060
        // Build the tree.
3061
        $contents->add_child($content);
3062
 
3063
        // Define sources.
3064
        $content->set_source_table('contentbank_content', ['contextid' => backup::VAR_CONTEXTID]);
3065
 
3066
        // Define annotations.
3067
        $content->annotate_ids('user', 'usercreated');
3068
        $content->annotate_ids('user', 'usermodified');
3069
        $content->annotate_files('contentbank', 'public', 'id');
3070
 
3071
        // Return the root element (contents).
3072
        return $contents;
3073
    }
3074
}
3075
 
3076
/**
3077
 * Structure step in charge of constructing the xapistate.xml file for all the xAPI states found in a given context.
3078
 */
3079
class backup_xapistate_structure_step extends backup_structure_step {
3080
 
3081
    /**
3082
     * Define structure for content bank step
3083
     */
3084
    protected function define_structure() {
3085
 
3086
        // Define each element separated.
3087
        $states = new backup_nested_element('states');
3088
        $state = new backup_nested_element(
3089
            'state',
3090
            ['id'],
3091
            ['component', 'userid', 'itemid', 'stateid', 'statedata', 'registration', 'timecreated', 'timemodified']
3092
        );
3093
 
3094
        // Build the tree.
3095
        $states->add_child($state);
3096
 
3097
        // Define sources.
3098
        $state->set_source_table('xapi_states', ['itemid' => backup::VAR_CONTEXTID]);
3099
 
3100
        // Define annotations.
3101
        $state->annotate_ids('user', 'userid');
3102
 
3103
        // Return the root element (contents).
3104
        return $states;
3105
    }
3106
}