Proyectos de Subversion Moodle

Rev

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