Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Defines various backup steps that will be used by common tasks in backup** @package core_backup* @subpackage moodle2* @category backup* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/defined('MOODLE_INTERNAL') || die();/*** Create the temp dir where backup/restore will happen and create temp ids table.*/class create_and_clean_temp_stuff extends backup_execution_step {protected function define_execution() {$progress = $this->task->get_progress();$progress->start_progress('Deleting backup directories');backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dirbackup_helper::clear_backup_dir($this->get_backupid(), $progress); // Empty temp dir, just in casebackup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp tablebackup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table$progress->end_progress();}}/*** Delete the temp dir used by backup/restore (conditionally) and drop temp ids table.* Note we delete the directory but not the corresponding log file that will be* there until cron cleans it up.*/class drop_and_clean_temp_stuff extends backup_execution_step {protected $skipcleaningtempdir = false;protected function define_execution() {global $CFG;backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table// Delete temp dir conditionally:// 1) If $CFG->keeptempdirectoriesonbackup is not enabled// 2) If backup temp dir deletion has been marked to be avoidedif (empty($CFG->keeptempdirectoriesonbackup) && !$this->skipcleaningtempdir) {$progress = $this->task->get_progress();$progress->start_progress('Deleting backup dir');backup_helper::delete_backup_dir($this->get_backupid(), $progress); // Empty backup dir$progress->end_progress();}}public function skip_cleaning_temp_dir($skip) {$this->skipcleaningtempdir = $skip;}}/*** Create the directory where all the task (activity/block...) information will be stored*/class create_taskbasepath_directory extends backup_execution_step {protected function define_execution() {global $CFG;$basepath = $this->task->get_taskbasepath();if (!check_dir_exists($basepath, true, true)) {throw new backup_step_exception('cannot_create_taskbasepath_directory', $basepath);}}}/*** Abstract structure step, parent of all the activity structure steps. Used to wrap the* activity structure definition within the main <activity ...> tag.*/abstract class backup_activity_structure_step extends backup_structure_step {/*** Wraps any activity backup structure within the common 'activity' element* that will include common to all activities information like id, context...** @param backup_nested_element $activitystructure the element to wrap* @return backup_nested_element the $activitystructure wrapped by the common 'activity' element*/protected function prepare_activity_structure($activitystructure) {// Create the wrap element$activity = new backup_nested_element('activity', array('id', 'moduleid', 'modulename', 'contextid'), null);// Build the tree$activity->add_child($activitystructure);// Set the source$activityarr = array((object)array('id' => $this->task->get_activityid(),'moduleid' => $this->task->get_moduleid(),'modulename' => $this->task->get_modulename(),'contextid' => $this->task->get_contextid()));$activity->set_source_array($activityarr);// Return the root element (activity)return $activity;}}/*** Helper code for use by any plugin that stores question attempt data that it needs to back up.*/trait backup_questions_attempt_data_trait {/*** Attach to $element (usually attempts) the needed backup structures* for question_usages and all the associated data.** @param backup_nested_element $element the element that will contain all the question_usages data.* @param string $usageidname the name of the element that holds the usageid.* This must be child of $element, and must be a final element.* @param string $nameprefix this prefix is added to all the element names we create.* Element names in the XML must be unique, so if you are using usages in* two different ways, you must give a prefix to at least one of them. If* you only use one sort of usage, then you can just use the default empty prefix.* This should include a trailing underscore. For example "myprefix_"*/protected function add_question_usages($element, $usageidname, $nameprefix = '') {global $CFG;require_once($CFG->dirroot . '/question/engine/lib.php');// Check $element is one nested_backup_elementif (! $element instanceof backup_nested_element) {throw new backup_step_exception('question_states_bad_parent_element', $element);}if (! $element->get_final_element($usageidname)) {throw new backup_step_exception('question_states_bad_question_attempt_element', $usageidname);}$quba = new backup_nested_element($nameprefix . 'question_usage', array('id'),array('component', 'preferredbehaviour'));$qas = new backup_nested_element($nameprefix . 'question_attempts');$qa = new backup_nested_element($nameprefix . 'question_attempt', array('id'), array('slot', 'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'maxfraction','flagged', 'questionsummary', 'rightanswer', 'responsesummary','timemodified'));$steps = new backup_nested_element($nameprefix . 'steps');$step = new backup_nested_element($nameprefix . 'step', array('id'), array('sequencenumber', 'state', 'fraction', 'timecreated', 'userid'));$response = new backup_nested_element($nameprefix . 'response');$variable = new backup_nested_element($nameprefix . 'variable', null, array('name', 'value'));// Build the tree$element->add_child($quba);$quba->add_child($qas);$qas->add_child($qa);$qa->add_child($steps);$steps->add_child($step);$step->add_child($response);$response->add_child($variable);// Set the sources$quba->set_source_table('question_usages',array('id' => '../' . $usageidname));$qa->set_source_table('question_attempts', array('questionusageid' => backup::VAR_PARENTID), 'slot ASC');$step->set_source_table('question_attempt_steps', array('questionattemptid' => backup::VAR_PARENTID), 'sequencenumber ASC');$variable->set_source_table('question_attempt_step_data', array('attemptstepid' => backup::VAR_PARENTID));// Annotate ids$qa->annotate_ids('question', 'questionid');$step->annotate_ids('user', 'userid');// Annotate files$fileareas = question_engine::get_all_response_file_areas();foreach ($fileareas as $filearea) {$step->annotate_files('question', $filearea, 'id');}}}/*** Helper to backup question reference data for an instance.*/trait backup_question_reference_data_trait {/*** Backup the related data from reference table for the instance.** @param backup_nested_element $element* @param string $component* @param string $questionarea*/protected function add_question_references($element, $component, $questionarea) {// Check $element is one nested_backup_element.if (! $element instanceof backup_nested_element) {throw new backup_step_exception('question_states_bad_parent_element', $element);}$reference = new backup_nested_element('question_reference', ['id'],['usingcontextid', 'component', 'questionarea', 'questionbankentryid', 'version']);$element->add_child($reference);$reference->set_source_table('question_references', ['usingcontextid' => backup::VAR_CONTEXTID,'component' => backup_helper::is_sqlparam($component),'questionarea' => backup_helper::is_sqlparam($questionarea),'itemid' => backup::VAR_PARENTID]);}}/*** Helper to backup question set reference data for an instance.*/trait backup_question_set_reference_trait {/*** Backup the related data from set_reference table for the instance.** @param backup_nested_element $element* @param string $component* @param string $questionarea*/protected function add_question_set_references($element, $component, $questionarea) {// Check $element is one nested_backup_element.if (! $element instanceof backup_nested_element) {throw new backup_step_exception('question_states_bad_parent_element', $element);}$setreference = new backup_nested_element('question_set_reference', ['id'],['usingcontextid', 'component', 'questionarea', 'questionscontextid', 'filtercondition']);$element->add_child($setreference);$setreference->set_source_table('question_set_references', ['usingcontextid' => backup::VAR_CONTEXTID,'component' => backup_helper::is_sqlparam($component),'questionarea' => backup_helper::is_sqlparam($questionarea),'itemid' => backup::VAR_PARENTID]);}}/*** Abstract structure step to help activities that store question attempt data, reference data and set reference data.** @copyright 2011 The Open University* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {use backup_questions_attempt_data_trait;use backup_question_reference_data_trait;use backup_question_set_reference_trait;}/*** backup structure step in charge of calculating the categories to be* included in backup, based in the context being backuped (module/course)* and the already annotated questions present in backup_ids_temp*/class backup_calculate_question_categories extends backup_execution_step {protected function define_execution() {backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid());}}/*** backup structure step in charge of deleting all the questions annotated* in the backup_ids_temp table*/class backup_delete_temp_questions extends backup_execution_step {protected function define_execution() {backup_question_dbops::delete_temp_questions($this->get_backupid());}}/*** Abstract structure step, parent of all the block structure steps. Used to wrap the* block structure definition within the main <block ...> tag*/abstract class backup_block_structure_step extends backup_structure_step {protected function prepare_block_structure($blockstructure) {// Create the wrap element$block = new backup_nested_element('block', array('id', 'blockname', 'contextid'), null);// Build the tree$block->add_child($blockstructure);// Set the source$blockarr = array((object)array('id' => $this->task->get_blockid(),'blockname' => $this->task->get_blockname(),'contextid' => $this->task->get_contextid()));$block->set_source_array($blockarr);// Return the root element (block)return $block;}}/*** structure step that will generate the module.xml file for the activity,* accumulating various information about the activity, annotating groupings* and completion/avail conf*/class backup_module_structure_step extends backup_structure_step {protected function define_structure() {global $DB;// Define each element separated$module = new backup_nested_element('module', array('id', 'version'), array('modulename', 'sectionid', 'sectionnumber', 'idnumber','added', 'score', 'indent', 'visible', 'visibleoncoursepage','visibleold', 'groupmode', 'groupingid','completion', 'completiongradeitemnumber', 'completionpassgrade','completionview', 'completionexpected','availability', 'showdescription', 'downloadcontent', 'lang'));$tags = new backup_nested_element('tags');$tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));// attach format plugin structure to $module element, only one allowed$this->add_plugin_structure('format', $module, false);// Attach report plugin structure to $module element, multiple allowed.$this->add_plugin_structure('report', $module, true);// attach plagiarism plugin structure to $module element, there can be potentially// many plagiarism plugins storing information about this course$this->add_plugin_structure('plagiarism', $module, true);// attach local plugin structure to $module, multiple allowed$this->add_plugin_structure('local', $module, true);// Attach admin tools plugin structure to $module.$this->add_plugin_structure('tool', $module, true);$module->add_child($tags);$tags->add_child($tag);// Set the sources$concat = $DB->sql_concat("'mod_'", 'm.name');$module->set_source_sql("SELECT cm.*, cp.value AS version, m.name AS modulename, s.id AS sectionid, s.section AS sectionnumberFROM {course_modules} cmJOIN {modules} m ON m.id = cm.moduleJOIN {config_plugins} cp ON cp.plugin = $concat AND cp.name = 'version'JOIN {course_sections} s ON s.id = cm.sectionWHERE cm.id = ?", array(backup::VAR_MODID));$tag->set_source_sql("SELECT t.id, t.name, t.rawnameFROM {tag} tJOIN {tag_instance} ti ON ti.tagid = t.idWHERE ti.itemtype = 'course_modules'AND ti.component = 'core'AND ti.itemid = ?", array(backup::VAR_MODID));// Define annotations$module->annotate_ids('grouping', 'groupingid');// Return the root element ($module)return $module;}}/*** structure step that will generate the section.xml file for the section* annotating files*/class backup_section_structure_step extends backup_structure_step {protected function define_structure() {// Define each element separated$section = new backup_nested_element('section',['id'],['number', 'name', 'summary', 'summaryformat', 'sequence', 'visible','availabilityjson', 'component', 'itemid', 'timemodified',]);// attach format plugin structure to $section element, only one allowed$this->add_plugin_structure('format', $section, false);// attach local plugin structure to $section element, multiple allowed$this->add_plugin_structure('local', $section, true);// Add nested elements for course_format_options table$formatoptions = new backup_nested_element('course_format_options', array('id'), array('format', 'name', 'value'));$section->add_child($formatoptions);// Define sources.$section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));$formatoptions->set_source_sql('SELECT cfo.id, cfo.format, cfo.name, cfo.valueFROM {course} cJOIN {course_format_options} cfoON cfo.courseid = c.id AND cfo.format = c.formatWHERE c.id = ? AND cfo.sectionid = ?',array(backup::VAR_COURSEID, backup::VAR_SECTIONID));// Aliases$section->set_source_alias('section', 'number');// The 'availability' field needs to be renamed because it clashes with// the old nested element structure for availability data.$section->set_source_alias('availability', 'availabilityjson');// Set annotations$section->annotate_files('course', 'section', 'id');return $section;}}/*** structure step that will generate the course.xml file for the course, including* course category reference, tags, modules restriction information* and some annotations (files & groupings)*/class backup_course_structure_step extends backup_structure_step {protected function define_structure() {global $DB;// Define each element separated$course = new backup_nested_element('course', array('id', 'contextid'), array('shortname', 'fullname', 'idnumber','summary', 'summaryformat', 'format', 'showgrades','newsitems', 'startdate', 'enddate','marker', 'maxbytes', 'legacyfiles', 'showreports','visible', 'groupmode', 'groupmodeforce','defaultgroupingid', 'lang', 'theme','timecreated', 'timemodified','requested','showactivitydates','showcompletionconditions', 'pdfexportfont','enablecompletion', 'completionstartonenrol', 'completionnotify'));$category = new backup_nested_element('category', array('id'), array('name', 'description'));$tags = new backup_nested_element('tags');$tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));$customfields = new backup_nested_element('customfields');$customfield = new backup_nested_element('customfield', array('id'), array('shortname', 'type', 'value', 'valueformat', 'valuetrust',));$courseformatoptions = new backup_nested_element('courseformatoptions');$courseformatoption = new backup_nested_element('courseformatoption', [], ['courseid', 'format', 'sectionid', 'name', 'value']);// attach format plugin structure to $course element, only one allowed$this->add_plugin_structure('format', $course, false);// attach theme plugin structure to $course element; multiple themes can// save course data (in case of user theme, legacy theme, etc)$this->add_plugin_structure('theme', $course, true);// attach general report plugin structure to $course element; multiple// reports can save course data if required$this->add_plugin_structure('report', $course, true);// attach course report plugin structure to $course element; multiple// course reports can save course data if required$this->add_plugin_structure('coursereport', $course, true);// attach plagiarism plugin structure to $course element, there can be potentially// many plagiarism plugins storing information about this course$this->add_plugin_structure('plagiarism', $course, true);// attach local plugin structure to $course element; multiple local plugins// can save course data if required$this->add_plugin_structure('local', $course, true);// Attach admin tools plugin structure to $course element; multiple plugins// can save course data if required.$this->add_plugin_structure('tool', $course, true);// Build the tree$course->add_child($category);$course->add_child($tags);$tags->add_child($tag);$course->add_child($customfields);$customfields->add_child($customfield);$course->add_child($courseformatoptions);$courseformatoptions->add_child($courseformatoption);// Set the sources$courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));$courserec->contextid = $this->task->get_contextid();// Add 'numsections' in order to be able to restore in previous versions of Moodle.// Even though Moodle does not officially support restore into older verions of Moodle from the// version where backup was made, without 'numsections' restoring will go very wrong.if (!property_exists($courserec, 'numsections') && course_get_format($courserec)->uses_sections()) {$courserec->numsections = course_get_format($courserec)->get_last_section_number();}$course->set_source_array(array($courserec));$categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category));$category->set_source_array(array($categoryrec));$tag->set_source_sql('SELECT t.id, t.name, t.rawnameFROM {tag} tJOIN {tag_instance} ti ON ti.tagid = t.idWHERE ti.itemtype = ?AND ti.itemid = ?', array(backup_helper::is_sqlparam('course'),backup::VAR_PARENTID));// Section level settings are dealt with in backup_section_structure_step.// We only need to deal with course level (sectionid = 0) here.$courseformatoption->set_source_sql('SELECT id, format, sectionid, name, valueFROM {course_format_options}WHERE courseid = ? AND sectionid = 0', [ backup::VAR_PARENTID ]);$handler = core_course\customfield\course_handler::create();$fieldsforbackup = $handler->get_instance_data_for_backup($this->task->get_courseid());$handler->backup_define_structure($this->task->get_courseid(), $customfield);$customfield->set_source_array($fieldsforbackup);// Some annotations$course->annotate_ids('grouping', 'defaultgroupingid');$course->annotate_files('course', 'summary', null);$course->annotate_files('course', 'overviewfiles', null);if ($this->get_setting_value('legacyfiles')) {$course->annotate_files('course', 'legacy', null);}// Return root element ($course)return $course;}}/*** structure step that will generate the enrolments.xml file for the given course*/class backup_enrolments_structure_step extends backup_structure_step {/*** Skip enrolments on the front page.* @return bool*/protected function execute_condition() {return ($this->get_courseid() != SITEID);}protected function define_structure() {global $DB;// To know if we are including users$users = $this->get_setting_value('users');$keptroles = $this->task->get_kept_roles();// Define each element separated$enrolments = new backup_nested_element('enrolments');$enrols = new backup_nested_element('enrols');$enrol = new backup_nested_element('enrol', array('id'), array('enrol', 'status', 'name', 'enrolperiod', 'enrolstartdate','enrolenddate', 'expirynotify', 'expirythreshold', 'notifyall','password', 'cost', 'currency', 'roleid','customint1', 'customint2', 'customint3', 'customint4', 'customint5', 'customint6', 'customint7', 'customint8','customchar1', 'customchar2', 'customchar3','customdec1', 'customdec2','customtext1', 'customtext2', 'customtext3', 'customtext4','timecreated', 'timemodified'));$userenrolments = new backup_nested_element('user_enrolments');$enrolment = new backup_nested_element('enrolment', array('id'), array('status', 'userid', 'timestart', 'timeend', 'modifierid','timemodified'));// Build the tree$enrolments->add_child($enrols);$enrols->add_child($enrol);$enrol->add_child($userenrolments);$userenrolments->add_child($enrolment);// Define sources - the instances are restored using the same sortorder, we do not need to store it in xml and deal with it afterwards.$enrol->set_source_table('enrol', array('courseid' => backup::VAR_COURSEID), 'sortorder ASC');// User enrolments only added only if users included.if (empty($keptroles) && $users) {$enrolment->set_source_table('user_enrolments', array('enrolid' => backup::VAR_PARENTID));$enrolment->annotate_ids('user', 'userid');} else if (!empty($keptroles)) {list($insql, $inparams) = $DB->get_in_or_equal($keptroles);$params = array(backup::VAR_CONTEXTID,backup::VAR_PARENTID);foreach ($inparams as $inparam) {$params[] = backup_helper::is_sqlparam($inparam);}$enrolment->set_source_sql("SELECT ue.*FROM {user_enrolments} ueINNER JOIN {role_assignments} ra ON ue.userid = ra.useridWHERE ra.contextid = ?AND ue.enrolid = ?AND ra.roleid $insql",$params);$enrolment->annotate_ids('user', 'userid');}$enrol->annotate_ids('role', 'roleid');// Add enrol plugin structure.$this->add_plugin_structure('enrol', $enrol, true);return $enrolments;}}/*** structure step that will generate the roles.xml file for the given context, observing* the role_assignments setting to know if that part needs to be included*/class backup_roles_structure_step extends backup_structure_step {protected function define_structure() {// To know if we are including role assignments$roleassignments = $this->get_setting_value('role_assignments');// Define each element separated$roles = new backup_nested_element('roles');$overrides = new backup_nested_element('role_overrides');$override = new backup_nested_element('override', array('id'), array('roleid', 'capability', 'permission', 'timemodified','modifierid'));$assignments = new backup_nested_element('role_assignments');$assignment = new backup_nested_element('assignment', array('id'), array('roleid', 'userid', 'timemodified', 'modifierid', 'component', 'itemid','sortorder'));// Build the tree$roles->add_child($overrides);$roles->add_child($assignments);$overrides->add_child($override);$assignments->add_child($assignment);// Define sources$override->set_source_table('role_capabilities', array('contextid' => backup::VAR_CONTEXTID));// Assignments only added if specifiedif ($roleassignments) {$assignment->set_source_table('role_assignments', array('contextid' => backup::VAR_CONTEXTID));}// Define id annotations$override->annotate_ids('role', 'roleid');$assignment->annotate_ids('role', 'roleid');$assignment->annotate_ids('user', 'userid');//TODO: how do we annotate the itemid? the meaning depends on the content of component table (skodak)return $roles;}}/*** structure step that will generate the roles.xml containing the* list of roles used along the whole backup process. Just raw* list of used roles from role table*/class backup_final_roles_structure_step extends backup_structure_step {protected function define_structure() {// Define elements$rolesdef = new backup_nested_element('roles_definition');$role = new backup_nested_element('role', array('id'), array('name', 'shortname', 'nameincourse', 'description','sortorder', 'archetype'));// Build the tree$rolesdef->add_child($role);// Define sources$role->set_source_sql("SELECT r.*, rn.name AS nameincourseFROM {role} rJOIN {backup_ids_temp} bi ON r.id = bi.itemidLEFT JOIN {role_names} rn ON r.id = rn.roleid AND rn.contextid = ?WHERE bi.backupid = ?AND bi.itemname = 'rolefinal'", array(backup::VAR_CONTEXTID, backup::VAR_BACKUPID));// Return main element (rolesdef)return $rolesdef;}}/*** structure step that will generate the scales.xml containing the* list of scales used along the whole backup process.*/class backup_final_scales_structure_step extends backup_structure_step {protected function define_structure() {// Define elements$scalesdef = new backup_nested_element('scales_definition');$scale = new backup_nested_element('scale', array('id'), array('courseid', 'userid', 'name', 'scale','description', 'descriptionformat', 'timemodified'));// Build the tree$scalesdef->add_child($scale);// Define sources$scale->set_source_sql("SELECT s.*FROM {scale} sJOIN {backup_ids_temp} bi ON s.id = bi.itemidWHERE bi.backupid = ?AND bi.itemname = 'scalefinal'", array(backup::VAR_BACKUPID));// Annotate scale files (they store files in system context, so pass it instead of default one)$scale->annotate_files('grade', 'scale', 'id', context_system::instance()->id);// Return main element (scalesdef)return $scalesdef;}}/*** structure step that will generate the outcomes.xml containing the* list of outcomes used along the whole backup process.*/class backup_final_outcomes_structure_step extends backup_structure_step {protected function define_structure() {// Define elements$outcomesdef = new backup_nested_element('outcomes_definition');$outcome = new backup_nested_element('outcome', array('id'), array('courseid', 'userid', 'shortname', 'fullname','scaleid', 'description', 'descriptionformat', 'timecreated','timemodified','usermodified'));// Build the tree$outcomesdef->add_child($outcome);// Define sources$outcome->set_source_sql("SELECT o.*FROM {grade_outcomes} oJOIN {backup_ids_temp} bi ON o.id = bi.itemidWHERE bi.backupid = ?AND bi.itemname = 'outcomefinal'", array(backup::VAR_BACKUPID));// Annotate outcome files (they store files in system context, so pass it instead of default one)$outcome->annotate_files('grade', 'outcome', 'id', context_system::instance()->id);// Return main element (outcomesdef)return $outcomesdef;}}/*** structure step in charge of constructing the filters.xml file for all the filters found* in activity*/class backup_filters_structure_step extends backup_structure_step {protected function define_structure() {// Define each element separated$filters = new backup_nested_element('filters');$actives = new backup_nested_element('filter_actives');$active = new backup_nested_element('filter_active', null, array('filter', 'active'));$configs = new backup_nested_element('filter_configs');$config = new backup_nested_element('filter_config', null, array('filter', 'name', 'value'));// Build the tree$filters->add_child($actives);$filters->add_child($configs);$actives->add_child($active);$configs->add_child($config);// Define sourceslist($activearr, $configarr) = filter_get_all_local_settings($this->task->get_contextid());$active->set_source_array($activearr);$config->set_source_array($configarr);// Return the root element (filters)return $filters;}}/*** Structure step in charge of constructing the comments.xml file for all the comments found in a given context.*/class backup_comments_structure_step extends backup_structure_step {protected function define_structure() {// Define each element separated.$comments = new backup_nested_element('comments');$comment = new backup_nested_element('comment', array('id'), array('component', 'commentarea', 'itemid', 'content', 'format','userid', 'timecreated'));// Build the tree.$comments->add_child($comment);// Define sources.$comment->set_source_table('comments', array('contextid' => backup::VAR_CONTEXTID));// Define id annotations.$comment->annotate_ids('user', 'userid');// Return the root element (comments).return $comments;}}/*** structure step in charge of constructing the badges.xml file for all the badges found* in a given context*/class backup_badges_structure_step extends backup_structure_step {protected function execute_condition() {// Check that all activities have been included.if ($this->task->is_excluding_activities()) {return false;}return true;}protected function define_structure() {global $CFG;require_once($CFG->libdir . '/badgeslib.php');// Define each element separated.$badges = new backup_nested_element('badges');$badge = new backup_nested_element('badge', array('id'), array('name', 'description','timecreated', 'timemodified', 'usercreated', 'usermodified', 'issuername','issuerurl', 'issuercontact', 'expiredate', 'expireperiod', 'type', 'courseid','message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron','version', 'language', 'imageauthorname', 'imageauthoremail', 'imageauthorurl','imagecaption'));$criteria = new backup_nested_element('criteria');$criterion = new backup_nested_element('criterion', array('id'), array('badgeid','criteriatype', 'method', 'description', 'descriptionformat'));$endorsement = new backup_nested_element('endorsement', array('id'), array('badgeid','issuername', 'issuerurl', 'issueremail', 'claimid', 'claimcomment', 'dateissued'));$alignments = new backup_nested_element('alignments');$alignment = new backup_nested_element('alignment', array('id'), array('badgeid','targetname', 'targeturl', 'targetdescription', 'targetframework', 'targetcode'));$relatedbadges = new backup_nested_element('relatedbadges');$relatedbadge = new backup_nested_element('relatedbadge', array('id'), array('badgeid','relatedbadgeid'));$parameters = new backup_nested_element('parameters');$parameter = new backup_nested_element('parameter', array('id'), array('critid','name', 'value', 'criteriatype'));$manual_awards = new backup_nested_element('manual_awards');$manual_award = new backup_nested_element('manual_award', array('id'), array('badgeid','recipientid', 'issuerid', 'issuerrole', 'datemet'));$tags = new backup_nested_element('tags');$tag = new backup_nested_element('tag', ['id'], ['name', 'rawname']);// Build the tree.$badges->add_child($badge);$badge->add_child($criteria);$criteria->add_child($criterion);$criterion->add_child($parameters);$parameters->add_child($parameter);$badge->add_child($endorsement);$badge->add_child($alignments);$alignments->add_child($alignment);$badge->add_child($relatedbadges);$relatedbadges->add_child($relatedbadge);$badge->add_child($manual_awards);$manual_awards->add_child($manual_award);$badge->add_child($tags);$tags->add_child($tag);// Define sources.$parametersql = 'SELECT *FROM {badge}WHERE courseid = :courseidAND status != ' . BADGE_STATUS_ARCHIVED;$parameterparams = ['courseid' => backup::VAR_COURSEID];$badge->set_source_sql($parametersql, $parameterparams);$criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID));$endorsement->set_source_table('badge_endorsement', array('badgeid' => backup::VAR_PARENTID));$alignment->set_source_table('badge_alignment', array('badgeid' => backup::VAR_PARENTID));$relatedbadge->set_source_table('badge_related', array('badgeid' => backup::VAR_PARENTID));$parametersql = 'SELECT cp.*, c.criteriatypeFROM {badge_criteria_param} cp JOIN {badge_criteria} cON cp.critid = c.idWHERE critid = :critid';$parameterparams = array('critid' => backup::VAR_PARENTID);$parameter->set_source_sql($parametersql, $parameterparams);$manual_award->set_source_table('badge_manual_award', array('badgeid' => backup::VAR_PARENTID));$tag->set_source_sql('SELECT t.id, t.name, t.rawnameFROM {tag} tJOIN {tag_instance} ti ON ti.tagid = t.idWHERE ti.itemtype = ?AND ti.itemid = ?', [backup_helper::is_sqlparam('badge'), backup::VAR_PARENTID]);// Define id annotations.$badge->annotate_ids('user', 'usercreated');$badge->annotate_ids('user', 'usermodified');$criterion->annotate_ids('badge', 'badgeid');$parameter->annotate_ids('criterion', 'critid');$endorsement->annotate_ids('badge', 'badgeid');$alignment->annotate_ids('badge', 'badgeid');$relatedbadge->annotate_ids('badge', 'badgeid');$relatedbadge->annotate_ids('badge', 'relatedbadgeid');$badge->annotate_files('badges', 'badgeimage', 'id');$manual_award->annotate_ids('badge', 'badgeid');$manual_award->annotate_ids('user', 'recipientid');$manual_award->annotate_ids('user', 'issuerid');$manual_award->annotate_ids('role', 'issuerrole');// Return the root element ($badges).return $badges;}}/*** structure step in charge of constructing the calender.xml file for all the events found* in a given context*/class backup_calendarevents_structure_step extends backup_structure_step {protected function define_structure() {// Define each element separated$events = new backup_nested_element('events');$event = new backup_nested_element('event', array('id'), array('name', 'description', 'format', 'courseid', 'groupid', 'userid','repeatid', 'modulename', 'instance', 'type', 'eventtype', 'timestart','timeduration', 'timesort', 'visible', 'uuid', 'sequence', 'timemodified','priority', 'location'));// Build the tree$events->add_child($event);// Define sourcesif ($this->name == 'course_calendar') {$calendar_items_sql ="SELECT * FROM {event}WHERE courseid = :courseidAND (eventtype = 'course' OR eventtype = 'group')";$calendar_items_params = array('courseid'=>backup::VAR_COURSEID);$event->set_source_sql($calendar_items_sql, $calendar_items_params);} else if ($this->name == 'activity_calendar') {// We don't backup action events.$params = array('instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME,'type' => array('sqlparam' => CALENDAR_EVENT_TYPE_ACTION));// If we don't want to include the userinfo in the backup then setting the courseid// will filter out all of the user override events (which have a course id of zero).$coursewhere = "";if (!$this->get_setting_value('userinfo')) {$params['courseid'] = backup::VAR_COURSEID;$coursewhere = " AND courseid = :courseid";}$calendarsql = "SELECT * FROM {event}WHERE instance = :instanceAND type <> :typeAND modulename = :modulename";$calendarsql = $calendarsql . $coursewhere;$event->set_source_sql($calendarsql, $params);} else {$event->set_source_table('event', array('courseid' => backup::VAR_COURSEID, 'instance' => backup::VAR_ACTIVITYID, 'modulename' => backup::VAR_MODNAME));}// Define id annotations$event->annotate_ids('user', 'userid');$event->annotate_ids('group', 'groupid');$event->annotate_files('calendar', 'event_description', 'id');// Return the root element (events)return $events;}}/*** structure step in charge of constructing the gradebook.xml file for all the gradebook config in the course* NOTE: the backup of the grade items themselves is handled by backup_activity_grades_structure_step*/class backup_gradebook_structure_step extends backup_structure_step {/*** We need to decide conditionally, based on dynamic information* about the execution of this step. Only will be executed if all* the module gradeitems have been already included in backup*/protected function execute_condition() {$courseid = $this->get_courseid();if ($courseid == SITEID) {return false;}return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());}protected function define_structure() {global $CFG, $DB;// are we including user info?$userinfo = $this->get_setting_value('users');$gradebook = new backup_nested_element('gradebook');//grade_letters are done in backup_activity_grades_structure_step()//calculated grade items$grade_items = new backup_nested_element('grade_items');$grade_item = new backup_nested_element('grade_item', array('id'), array('categoryid', 'itemname', 'itemtype', 'itemmodule','iteminstance', 'itemnumber', 'iteminfo', 'idnumber','calculation', 'gradetype', 'grademax', 'grademin','scaleid', 'outcomeid', 'gradepass', 'multfactor','plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride','sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime','needsupdate', 'timecreated', 'timemodified'));$this->add_plugin_structure('local', $grade_item, true);$grade_grades = new backup_nested_element('grade_grades');$grade_grade = new backup_nested_element('grade_grade', array('id'), array('userid', 'rawgrade', 'rawgrademax', 'rawgrademin','rawscaleid', 'usermodified', 'finalgrade', 'hidden','locked', 'locktime', 'exported', 'overridden','excluded', 'feedback', 'feedbackformat', 'information','informationformat', 'timecreated', 'timemodified','aggregationstatus', 'aggregationweight'));//grade_categories$grade_categories = new backup_nested_element('grade_categories');$grade_category = new backup_nested_element('grade_category', array('id'), array(//'courseid','parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh','droplow', 'aggregateonlygraded', 'aggregateoutcomes','timecreated', 'timemodified', 'hidden'));$letters = new backup_nested_element('grade_letters');$letter = new backup_nested_element('grade_letter', 'id', array('lowerboundary', 'letter'));$grade_settings = new backup_nested_element('grade_settings');$grade_setting = new backup_nested_element('grade_setting', 'id', array('name', 'value'));$gradebook_attributes = new backup_nested_element('attributes', null, array('calculations_freeze'));// Build the tree$gradebook->add_child($gradebook_attributes);$gradebook->add_child($grade_categories);$grade_categories->add_child($grade_category);$gradebook->add_child($grade_items);$grade_items->add_child($grade_item);$grade_item->add_child($grade_grades);$grade_grades->add_child($grade_grade);$gradebook->add_child($letters);$letters->add_child($letter);$gradebook->add_child($grade_settings);$grade_settings->add_child($grade_setting);// Define sources// Add attribute with gradebook calculation freeze date if needed.$attributes = new stdClass();$gradebookcalculationfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid());if ($gradebookcalculationfreeze) {$attributes->calculations_freeze = $gradebookcalculationfreeze;}$gradebook_attributes->set_source_array([$attributes]);//Include manual, category and the course grade item$grade_items_sql ="SELECT * FROM {grade_items}WHERE courseid = :courseidAND (itemtype='manual' OR itemtype='course' OR itemtype='category')";$grade_items_params = array('courseid'=>backup::VAR_COURSEID);$grade_item->set_source_sql($grade_items_sql, $grade_items_params);if ($userinfo) {$grade_grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));}$grade_category_sql = "SELECT gc.*, gi.sortorderFROM {grade_categories} gcJOIN {grade_items} gi ON (gi.iteminstance = gc.id)WHERE gc.courseid = :courseidAND (gi.itemtype='course' OR gi.itemtype='category')ORDER BY gc.parent ASC";//need parent categories before their children$grade_category_params = array('courseid'=>backup::VAR_COURSEID);$grade_category->set_source_sql($grade_category_sql, $grade_category_params);$letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));// Set the grade settings source, forcing the inclusion of minmaxtouse if not present.$settings = array();$rs = $DB->get_recordset('grade_settings', array('courseid' => $this->get_courseid()));foreach ($rs as $record) {$settings[$record->name] = $record;}$rs->close();if (!isset($settings['minmaxtouse'])) {$settings['minmaxtouse'] = (object) array('name' => 'minmaxtouse', 'value' => $CFG->grade_minmaxtouse);}$grade_setting->set_source_array($settings);// Annotations (both as final as far as they are going to be exported in next steps)$grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0$grade_item->annotate_ids('outcomefinal', 'outcomeid');//just in case there are any users not already annotated by the activities$grade_grade->annotate_ids('userfinal', 'userid');// Return the root elementreturn $gradebook;}}/*** Step in charge of constructing the grade_history.xml file containing the grade histories.*/class backup_grade_history_structure_step extends backup_structure_step {/*** Limit the execution.** This applies the same logic than the one applied to {@link backup_gradebook_structure_step},* because we do not want to save the history of items which are not backed up. At least for now.*/protected function execute_condition() {$courseid = $this->get_courseid();if ($courseid == SITEID) {return false;}return backup_plan_dbops::require_gradebook_backup($courseid, $this->get_backupid());}protected function define_structure() {// Settings to use.$userinfo = $this->get_setting_value('users');$history = $this->get_setting_value('grade_histories');// Create the nested elements.$bookhistory = new backup_nested_element('grade_history');$grades = new backup_nested_element('grade_grades');$grade = new backup_nested_element('grade_grade', array('id'), array('action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid','rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid','usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden','excluded', 'feedback', 'feedbackformat', 'information','informationformat', 'timemodified'));// Build the tree.$bookhistory->add_child($grades);$grades->add_child($grade);// This only happens if we are including user info and history.if ($userinfo && $history) {// Only keep the history of grades related to items which have been backed up, The query is// similar (but not identical) to the one used in backup_gradebook_structure_step::define_structure().$gradesql = "SELECT ggh.*FROM {grade_grades_history} gghJOIN {grade_items} gi ON ggh.itemid = gi.idWHERE gi.courseid = :courseidAND (gi.itemtype = 'manual' OR gi.itemtype = 'course' OR gi.itemtype = 'category')";$grade->set_source_sql($gradesql, array('courseid' => backup::VAR_COURSEID));}// Annotations. (Final annotations as this step is part of the final task).$grade->annotate_ids('scalefinal', 'rawscaleid');$grade->annotate_ids('userfinal', 'loggeduser');$grade->annotate_ids('userfinal', 'userid');$grade->annotate_ids('userfinal', 'usermodified');// Return the root element.return $bookhistory;}}/*** structure step in charge if constructing the completion.xml file for all the users completion* information in a given activity*/class backup_userscompletion_structure_step extends backup_structure_step {/*** Skip completion on the front page.* @return bool*/protected function execute_condition() {return ($this->get_courseid() != SITEID);}protected function define_structure() {// Define each element separated$completions = new backup_nested_element('completions');$completion = new backup_nested_element('completion', array('id'), array('userid', 'completionstate', 'viewed', 'timemodified'));// Build the tree$completions->add_child($completion);// Define sources$completion->set_source_table('course_modules_completion', array('coursemoduleid' => backup::VAR_MODID));// Define id annotations$completion->annotate_ids('user', 'userid');$completionviews = new backup_nested_element('completionviews');$completionview = new backup_nested_element('completionview', ['id'], ['userid', 'timecreated']);// Build the tree.$completionviews->add_child($completionview);// Define sources.$completionview->set_source_table('course_modules_viewed', ['coursemoduleid' => backup::VAR_MODID]);// Define id annotations.$completionview->annotate_ids('user', 'userid');$completions->add_child($completionviews);// Return the root element (completions).return $completions;}}/*** structure step in charge of constructing the main groups.xml file for all the groups and* groupings information already annotated*/class backup_groups_structure_step extends backup_structure_step {protected function define_structure() {// To know if we are including users.$userinfo = $this->get_setting_value('users');// To know if we are including groups and groupings.$groupinfo = $this->get_setting_value('groups');// Define each element separated$groups = new backup_nested_element('groups');$group = new backup_nested_element('group', array('id'), array('name', 'idnumber', 'description', 'descriptionformat', 'enrolmentkey','picture', 'visibility', 'participation', 'timecreated', 'timemodified'));$groupcustomfields = new backup_nested_element('groupcustomfields');$groupcustomfield = new backup_nested_element('groupcustomfield', ['id'], ['shortname', 'type', 'value', 'valueformat', 'valuetrust', 'groupid']);$members = new backup_nested_element('group_members');$member = new backup_nested_element('group_member', array('id'), array('userid', 'timeadded', 'component', 'itemid'));$groupings = new backup_nested_element('groupings');$grouping = new backup_nested_element('grouping', 'id', array('name', 'idnumber', 'description', 'descriptionformat', 'configdata','timecreated', 'timemodified'));$groupingcustomfields = new backup_nested_element('groupingcustomfields');$groupingcustomfield = new backup_nested_element('groupingcustomfield', ['id'], ['shortname', 'type', 'value', 'valueformat', 'valuetrust', 'groupingid']);$groupinggroups = new backup_nested_element('grouping_groups');$groupinggroup = new backup_nested_element('grouping_group', array('id'), array('groupid', 'timeadded'));// Build the tree$groups->add_child($group);$groups->add_child($groupcustomfields);$groupcustomfields->add_child($groupcustomfield);$groups->add_child($groupings);$group->add_child($members);$members->add_child($member);$groupings->add_child($grouping);$groupings->add_child($groupingcustomfields);$groupingcustomfields->add_child($groupingcustomfield);$grouping->add_child($groupinggroups);$groupinggroups->add_child($groupinggroup);// Define sources// This only happens if we are including groups/groupings.if ($groupinfo) {$group->set_source_sql("SELECT g.*FROM {groups} gJOIN {backup_ids_temp} bi ON g.id = bi.itemidWHERE bi.backupid = ?AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID));$grouping->set_source_sql("SELECT g.*FROM {groupings} gJOIN {backup_ids_temp} bi ON g.id = bi.itemidWHERE bi.backupid = ?AND bi.itemname = 'groupingfinal'", array(backup::VAR_BACKUPID));$groupinggroup->set_source_table('groupings_groups', array('groupingid' => backup::VAR_PARENTID));// This only happens if we are including users.if ($userinfo) {$member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));}$courseid = $this->task->get_courseid();$groupcustomfield->set_source_array($this->get_group_custom_fields_for_backup($courseid));$groupingcustomfield->set_source_array($this->get_grouping_custom_fields_for_backup($courseid));}// Define id annotations (as final)$member->annotate_ids('userfinal', 'userid');// Define file annotations$group->annotate_files('group', 'description', 'id');$group->annotate_files('group', 'icon', 'id');$grouping->annotate_files('grouping', 'description', 'id');// Return the root element (groups)return $groups;}/*** Get custom fields array for group* @param int $courseid* @return array*/protected function get_group_custom_fields_for_backup(int $courseid): array {global $DB;$handler = \core_group\customfield\group_handler::create();$fieldsforbackup = [];if ($groups = $DB->get_records('groups', ['courseid' => $courseid], '', 'id')) {foreach ($groups as $group) {$fieldsforbackup = array_merge($fieldsforbackup, $handler->get_instance_data_for_backup($group->id));}}return $fieldsforbackup;}/*** Get custom fields array for grouping* @param int $courseid* @return array*/protected function get_grouping_custom_fields_for_backup(int $courseid): array {global $DB;$handler = \core_group\customfield\grouping_handler::create();$fieldsforbackup = [];if ($groupings = $DB->get_records('groupings', ['courseid' => $courseid], '', 'id')) {foreach ($groupings as $grouping) {$fieldsforbackup = array_merge($fieldsforbackup, $handler->get_instance_data_for_backup($grouping->id));}}return $fieldsforbackup;}}/*** structure step in charge of constructing the main users.xml file for all the users already* annotated (final). Includes custom profile fields, preferences, tags, role assignments and* overrides.*/class backup_users_structure_step extends backup_structure_step {protected function define_structure() {global $CFG;// To know if we are anonymizing users$anonymize = $this->get_setting_value('anonymize');// To know if we are including role assignments$roleassignments = $this->get_setting_value('role_assignments');// Define each element separate.$users = new backup_nested_element('users');// Create the array of user fields by hand, as far as we have various bits to control// anonymize option, password backup, mnethostid...// First, the fields not needing anonymization nor special handling$normalfields = array('confirmed', 'policyagreed', 'deleted','lang', 'theme', 'timezone', 'firstaccess','lastaccess', 'lastlogin', 'currentlogin','mailformat', 'maildigest', 'maildisplay','autosubscribe', 'trackforums', 'timecreated','timemodified', 'trustbitmask');// Then, the fields potentially needing anonymization$anonfields = array('username', 'idnumber', 'email', 'phone1','phone2', 'institution', 'department', 'address','city', 'country', 'lastip', 'picture','description', 'descriptionformat', 'imagealt', 'auth');$anonfields = array_merge($anonfields, \core_user\fields::get_name_fields());// Add anonymized fields to $userfields with custom final elementforeach ($anonfields as $field) {if ($anonymize) {$userfields[] = new anonymizer_final_element($field);} else {$userfields[] = $field; // No anonymization, normally added}}// mnethosturl requires special handling (custom final element)$userfields[] = new mnethosturl_final_element('mnethosturl');// password added conditionallyif (!empty($CFG->includeuserpasswordsinbackup)) {$userfields[] = 'password';}// Merge all the fields$userfields = array_merge($userfields, $normalfields);$user = new backup_nested_element('user', array('id', 'contextid'), $userfields);$customfields = new backup_nested_element('custom_fields');$customfield = new backup_nested_element('custom_field', array('id'), array('field_name', 'field_type', 'field_data'));$tags = new backup_nested_element('tags');$tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));$preferences = new backup_nested_element('preferences');$preference = new backup_nested_element('preference', array('id'), array('name', 'value'));$roles = new backup_nested_element('roles');$overrides = new backup_nested_element('role_overrides');$override = new backup_nested_element('override', array('id'), array('roleid', 'capability', 'permission', 'timemodified','modifierid'));$assignments = new backup_nested_element('role_assignments');$assignment = new backup_nested_element('assignment', array('id'), array('roleid', 'userid', 'timemodified', 'modifierid', 'component', //TODO: MDL-22793 add itemid here'sortorder'));// Build the tree$users->add_child($user);$user->add_child($customfields);$customfields->add_child($customfield);$user->add_child($tags);$tags->add_child($tag);$user->add_child($preferences);$preferences->add_child($preference);$user->add_child($roles);$roles->add_child($overrides);$roles->add_child($assignments);$overrides->add_child($override);$assignments->add_child($assignment);// Define sources$user->set_source_sql('SELECT u.*, c.id AS contextid, m.wwwroot AS mnethosturlFROM {user} uJOIN {backup_ids_temp} bi ON bi.itemid = u.idLEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . 'LEFT JOIN {mnet_host} m ON m.id = u.mnethostidWHERE bi.backupid = ?AND bi.itemname = ?', array(backup_helper::is_sqlparam($this->get_backupid()),backup_helper::is_sqlparam('userfinal')));// All the rest on information is only added if we arent// in an anonymized backupif (!$anonymize) {$customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.dataFROM {user_info_field} fJOIN {user_info_data} d ON d.fieldid = f.idWHERE d.userid = ?', array(backup::VAR_PARENTID));$customfield->set_source_alias('shortname', 'field_name');$customfield->set_source_alias('datatype', 'field_type');$customfield->set_source_alias('data', 'field_data');$tag->set_source_sql('SELECT t.id, t.name, t.rawnameFROM {tag} tJOIN {tag_instance} ti ON ti.tagid = t.idWHERE ti.itemtype = ?AND ti.itemid = ?', array(backup_helper::is_sqlparam('user'),backup::VAR_PARENTID));$preference->set_source_table('user_preferences', array('userid' => backup::VAR_PARENTID));$override->set_source_table('role_capabilities', array('contextid' => '/users/user/contextid'));// Assignments only added if specifiedif ($roleassignments) {$assignment->set_source_table('role_assignments', array('contextid' => '/users/user/contextid'));}// Define id annotations (as final)$override->annotate_ids('rolefinal', 'roleid');}// Return root element (users)return $users;}}/*** structure step in charge of constructing the block.xml file for one* given block (instance and positions). If the block has custom DB structure* that will go to a separate file (different step defined in block class)*/class backup_block_instance_structure_step extends backup_structure_step {protected function define_structure() {global $DB;// Define each element separated$block = new backup_nested_element('block', array('id', 'contextid', 'version'), array('blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern','subpagepattern', 'defaultregion', 'defaultweight', 'configdata','timecreated', 'timemodified'));$positions = new backup_nested_element('block_positions');$position = new backup_nested_element('block_position', array('id'), array('contextid', 'pagetype', 'subpage', 'visible','region', 'weight'));// Build the tree$block->add_child($positions);$positions->add_child($position);// Transform configdata information if needed (process links and friends)$blockrec = $DB->get_record('block_instances', array('id' => $this->task->get_blockid()));if ($attrstotransform = $this->task->get_configdata_encoded_attributes()) {$configdata = array_filter((array) unserialize_object(base64_decode($blockrec->configdata)),static function($value): bool {return !($value instanceof __PHP_Incomplete_Class);});foreach ($configdata as $attribute => $value) {if (in_array($attribute, $attrstotransform)) {$configdata[$attribute] = $this->contenttransformer->process($value);}}$blockrec->configdata = base64_encode(serialize((object)$configdata));}$blockrec->contextid = $this->task->get_contextid();// Get the version of the block$blockrec->version = get_config('block_'.$this->task->get_blockname(), 'version');// Define sources$block->set_source_array(array($blockrec));$position->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));// File anotations (for fileareas specified on each block)foreach ($this->task->get_fileareas() as $filearea) {$block->annotate_files('block_' . $this->task->get_blockname(), $filearea, null);}// Return the root element (block)return $block;}}/*** structure step in charge of constructing the logs.xml file for all the log records found* in course. Note that we are sending to backup ALL the log records having cmid = 0. That* includes some records that won't be restoreable (like 'upload', 'calendar'...) but we do* that just in case they become restored some day in the future*/class backup_course_logs_structure_step extends backup_structure_step {protected function define_structure() {// Define each element separated$logs = new backup_nested_element('logs');$log = new backup_nested_element('log', array('id'), array('time', 'userid', 'ip', 'module','action', 'url', 'info'));// Build the tree$logs->add_child($log);// Define sources (all the records belonging to the course, having cmid = 0)$log->set_source_table('log', array('course' => backup::VAR_COURSEID, 'cmid' => backup_helper::is_sqlparam(0)));// Annotations// NOTE: We don't annotate users from logs as far as they MUST be// always annotated by the course (enrol, ras... whatever)// Return the root element (logs)return $logs;}}/*** structure step in charge of constructing the logs.xml file for all the log records found* in activity*/class backup_activity_logs_structure_step extends backup_structure_step {protected function define_structure() {// Define each element separated$logs = new backup_nested_element('logs');$log = new backup_nested_element('log', array('id'), array('time', 'userid', 'ip', 'module','action', 'url', 'info'));// Build the tree$logs->add_child($log);// Define sources$log->set_source_table('log', array('cmid' => backup::VAR_MODID));// Annotations// NOTE: We don't annotate users from logs as far as they MUST be// always annotated by the activity (true participants).// Return the root element (logs)return $logs;}}/*** Structure step in charge of constructing the logstores.xml file for the course logs.** This backup step will backup the logs for all the enabled logstore subplugins supporting* it, for logs belonging to the course level.*/class backup_course_logstores_structure_step extends backup_structure_step {protected function define_structure() {// Define the structure of logstores container.$logstores = new backup_nested_element('logstores');$logstore = new backup_nested_element('logstore');$logstores->add_child($logstore);// Add the tool_log logstore subplugins information to the logstore element.$this->add_subplugin_structure('logstore', $logstore, true, 'tool', 'log');return $logstores;}}/*** Structure step in charge of constructing the loglastaccess.xml file for the course logs.** This backup step will backup the logs of the user_lastaccess table.*/class backup_course_loglastaccess_structure_step extends backup_structure_step {/*** This function creates the structures for the loglastaccess.xml file.* Expected structure would look like this.* <loglastaccesses>* <loglastaccess id=2>* <userid>5</userid>* <timeaccess>1616887341</timeaccess>* </loglastaccess>* </loglastaccesses>** @return backup_nested_element*/protected function define_structure() {// To know if we are including userinfo.$userinfo = $this->get_setting_value('users');// Define the structure of logstores container.$lastaccesses = new backup_nested_element('lastaccesses');$lastaccess = new backup_nested_element('lastaccess', array('id'), array('userid', 'timeaccess'));// Define build tree.$lastaccesses->add_child($lastaccess);// This element should only happen if we are including user info.if ($userinfo) {// Define sources.$lastaccess->set_source_sql('SELECT id, userid, timeaccessFROM {user_lastaccess}WHERE courseid = ?',array(backup::VAR_COURSEID));// Define userid annotation to user.$lastaccess->annotate_ids('user', 'userid');}// Return the root element (lastaccessess).return $lastaccesses;}}/*** Structure step in charge of constructing the logstores.xml file for the activity logs.** Note: Activity structure is completely equivalent to the course one, so just extend it.*/class backup_activity_logstores_structure_step extends backup_course_logstores_structure_step {}/*** Course competencies backup structure step.*/class backup_course_competencies_structure_step extends backup_structure_step {protected function define_structure() {$userinfo = $this->get_setting_value('users');$wrapper = new backup_nested_element('course_competencies');$settings = new backup_nested_element('settings', array('id'), array('pushratingstouserplans'));$wrapper->add_child($settings);$sql = 'SELECT s.pushratingstouserplansFROM {' . \core_competency\course_competency_settings::TABLE . '} sWHERE s.courseid = :courseid';$settings->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));$competencies = new backup_nested_element('competencies');$wrapper->add_child($competencies);$competency = new backup_nested_element('competency', null, array('id', 'idnumber', 'ruleoutcome','sortorder', 'frameworkid', 'frameworkidnumber'));$competencies->add_child($competency);$sql = 'SELECT c.id, c.idnumber, cc.ruleoutcome, cc.sortorder, f.id AS frameworkid, f.idnumber AS frameworkidnumberFROM {' . \core_competency\course_competency::TABLE . '} ccJOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyidJOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkidWHERE cc.courseid = :courseidORDER BY cc.sortorder';$competency->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));$usercomps = new backup_nested_element('user_competencies');$wrapper->add_child($usercomps);if ($userinfo) {$usercomp = new backup_nested_element('user_competency', null, array('userid', 'competencyid','proficiency', 'grade'));$usercomps->add_child($usercomp);$sql = 'SELECT ucc.userid, ucc.competencyid, ucc.proficiency, ucc.gradeFROM {' . \core_competency\user_competency_course::TABLE . '} uccWHERE ucc.courseid = :courseidAND ucc.grade IS NOT NULL';$usercomp->set_source_sql($sql, array('courseid' => backup::VAR_COURSEID));$usercomp->annotate_ids('user', 'userid');}return $wrapper;}/*** Execute conditions.** @return bool*/protected function execute_condition() {// Do not execute if competencies are not included.if (!$this->get_setting_value('competencies')) {return false;}return true;}}/*** Activity competencies backup structure step.*/class backup_activity_competencies_structure_step extends backup_structure_step {protected function define_structure() {$wrapper = new backup_nested_element('course_module_competencies');$competencies = new backup_nested_element('competencies');$wrapper->add_child($competencies);$competency = new backup_nested_element('competency', null, array('idnumber', 'ruleoutcome','sortorder', 'frameworkidnumber', 'overridegrade'));$competencies->add_child($competency);$sql = 'SELECT c.idnumber, cmc.ruleoutcome, cmc.overridegrade, cmc.sortorder, f.idnumber AS frameworkidnumberFROM {' . \core_competency\course_module_competency::TABLE . '} cmcJOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cmc.competencyidJOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkidWHERE cmc.cmid = :coursemoduleidORDER BY cmc.sortorder';$competency->set_source_sql($sql, array('coursemoduleid' => backup::VAR_MODID));return $wrapper;}/*** Execute conditions.** @return bool*/protected function execute_condition() {// Do not execute if competencies are not included.if (!$this->get_setting_value('competencies')) {return false;}return true;}}/*** structure in charge of constructing the inforef.xml file for all the items we want* to have referenced there (users, roles, files...)*/class backup_inforef_structure_step extends backup_structure_step {protected function define_structure() {// Items we want to include in the inforef file.$items = backup_helper::get_inforef_itemnames();// Build the tree$inforef = new backup_nested_element('inforef');// For each item, conditionally, if there are already records, build elementforeach ($items as $itemname) {if (backup_structure_dbops::annotations_exist($this->get_backupid(), $itemname)) {$elementroot = new backup_nested_element($itemname . 'ref');$element = new backup_nested_element($itemname, array(), array('id'));$inforef->add_child($elementroot);$elementroot->add_child($element);$element->set_source_sql("SELECT itemid AS idFROM {backup_ids_temp}WHERE backupid = ?AND itemname = ?",array(backup::VAR_BACKUPID, backup_helper::is_sqlparam($itemname)));}}// We don't annotate anything there, but rely in the next step// (move_inforef_annotations_to_final) that will change all the// already saved 'inforref' entries to their 'final' annotations.return $inforef;}}/*** This step will get all the annotations already processed to inforef.xml file and* transform them into 'final' annotations.*/class move_inforef_annotations_to_final extends backup_execution_step {protected function define_execution() {// Items we want to include in the inforef file$items = backup_helper::get_inforef_itemnames();$progress = $this->task->get_progress();$progress->start_progress($this->get_name(), count($items));$done = 1;foreach ($items as $itemname) {// Delegate to dbopsbackup_structure_dbops::move_annotations_to_final($this->get_backupid(),$itemname, $progress);$progress->progress($done++);}$progress->end_progress();}}/*** structure in charge of constructing the files.xml file with all the* annotated (final) files along the process. At, the same time, and* using one specialised nested_element, will copy them form moodle storage* to backup storage*/class backup_final_files_structure_step extends backup_structure_step {protected function define_structure() {// Define elements$files = new backup_nested_element('files');$file = new file_nested_element('file', array('id'), array('contenthash', 'contextid', 'component', 'filearea', 'itemid','filepath', 'filename', 'userid', 'filesize','mimetype', 'status', 'timecreated', 'timemodified','source', 'author', 'license', 'sortorder','repositorytype', 'repositoryid', 'reference'));// Build the tree$files->add_child($file);// Define sources$file->set_source_sql("SELECT f.*, r.type AS repositorytype, fr.repositoryid, fr.referenceFROM {files} fLEFT JOIN {files_reference} fr ON fr.id = f.referencefileidLEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryidLEFT JOIN {repository} r ON r.id = ri.typeidJOIN {backup_ids_temp} bi ON f.id = bi.itemidWHERE bi.backupid = ?AND bi.itemname = 'filefinal'", array(backup::VAR_BACKUPID));return $files;}}/*** Structure step in charge of creating the main moodle_backup.xml file* where all the information related to the backup, settings, license and* other information needed on restore is added*/class backup_main_structure_step extends backup_structure_step {protected function define_structure() {global $CFG;$info = array();$info['name'] = $this->get_setting_value('filename');$info['moodle_version'] = $CFG->version;$info['moodle_release'] = $CFG->release;$info['backup_version'] = $CFG->backup_version;$info['backup_release'] = $CFG->backup_release;$info['backup_date'] = time();$info['backup_uniqueid']= $this->get_backupid();$info['mnet_remoteusers']=backup_controller_dbops::backup_includes_mnet_remote_users($this->get_backupid());$info['include_files'] = backup_controller_dbops::backup_includes_files($this->get_backupid());$info['include_file_references_to_external_content'] =backup_controller_dbops::backup_includes_file_references($this->get_backupid());$info['original_wwwroot']=$CFG->wwwroot;$info['original_site_identifier_hash'] = md5(get_site_identifier());$info['original_course_id'] = $this->get_courseid();$originalcourseinfo = backup_controller_dbops::backup_get_original_course_info($this->get_courseid());$info['original_course_format'] = $originalcourseinfo->format;$info['original_course_fullname'] = $originalcourseinfo->fullname;$info['original_course_shortname'] = $originalcourseinfo->shortname;$info['original_course_startdate'] = $originalcourseinfo->startdate;$info['original_course_enddate'] = $originalcourseinfo->enddate;$info['original_course_contextid'] = context_course::instance($this->get_courseid())->id;$info['original_system_contextid'] = context_system::instance()->id;// Get more information from controllerlist($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($this->get_backupid(), $this->get_task()->get_progress());// Define elements$moodle_backup = new backup_nested_element('moodle_backup');$information = new backup_nested_element('information', null, array('name', 'moodle_version', 'moodle_release', 'backup_version','backup_release', 'backup_date', 'mnet_remoteusers', 'include_files', 'include_file_references_to_external_content', 'original_wwwroot','original_site_identifier_hash', 'original_course_id', 'original_course_format','original_course_fullname', 'original_course_shortname', 'original_course_startdate', 'original_course_enddate','original_course_contextid', 'original_system_contextid'));$details = new backup_nested_element('details');$detail = new backup_nested_element('detail', array('backup_id'), array('type', 'format', 'interactive', 'mode','execution', 'executiontime'));$contents = new backup_nested_element('contents');$activities = new backup_nested_element('activities');$activity = new backup_nested_element('activity', null, array('moduleid', 'sectionid', 'modulename', 'title','directory'));$sections = new backup_nested_element('sections');$section = new backup_nested_element('section', null, array('sectionid', 'title', 'directory'));$course = new backup_nested_element('course', null, array('courseid', 'title', 'directory'));$settings = new backup_nested_element('settings');$setting = new backup_nested_element('setting', null, array('level', 'section', 'activity', 'name', 'value'));// Build the tree$moodle_backup->add_child($information);$information->add_child($details);$details->add_child($detail);$information->add_child($contents);if (!empty($cinfo['activities'])) {$contents->add_child($activities);$activities->add_child($activity);}if (!empty($cinfo['sections'])) {$contents->add_child($sections);$sections->add_child($section);}if (!empty($cinfo['course'])) {$contents->add_child($course);}$information->add_child($settings);$settings->add_child($setting);// Set the sources$information->set_source_array(array((object)$info));$detail->set_source_array($dinfo);$activity->set_source_array($cinfo['activities']);$section->set_source_array($cinfo['sections']);$course->set_source_array($cinfo['course']);$setting->set_source_array($sinfo);// Prepare some information to be sent to main moodle_backup.xml filereturn $moodle_backup;}}/*** Execution step that will generate the final zip (.mbz) file with all the contents*/class backup_zip_contents extends backup_execution_step implements file_progress {/*** @var bool True if we have started tracking progress*/protected $startedprogress;protected function define_execution() {// Get basepath$basepath = $this->get_basepath();// Get the list of files in directory$filestemp = get_directory_list($basepath, '', false, true, true);$files = array();foreach ($filestemp as $file) { // Add zip paths and fs paths to all them$files[$file] = $basepath . '/' . $file;}// Add the log file if exists$logfilepath = $basepath . '.log';if (file_exists($logfilepath)) {$files['moodle_backup.log'] = $logfilepath;}// Calculate the zip fullpath (in OS temp area it's always backup.mbz)$zipfile = $basepath . '/backup.mbz';// Get the zip packer$zippacker = get_file_packer('application/vnd.moodle.backup');// Track overall progress for the 2 long-running steps (archive to// pathname, get backup information).$reporter = $this->task->get_progress();$reporter->start_progress('backup_zip_contents', 2);// Zip files$result = $zippacker->archive_to_pathname($files, $zipfile, true, $this);// If any sub-progress happened, end it.if ($this->startedprogress) {$this->task->get_progress()->end_progress();$this->startedprogress = false;} else {// No progress was reported, manually move it on to the next overall task.$reporter->progress(1);}// Something went wrong.if ($result === false) {@unlink($zipfile);throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip');}// Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid.try {backup_general_helper::get_backup_information_from_mbz($zipfile, $this);} catch (backup_helper_exception $e) {@unlink($zipfile);throw new backup_step_exception('error_zip_packing', '', $e->debuginfo);}// If any sub-progress happened, end it.if ($this->startedprogress) {$this->task->get_progress()->end_progress();$this->startedprogress = false;} else {$reporter->progress(2);}$reporter->end_progress();}/*** Implementation for file_progress interface to display unzip progress.** @param int $progress Current progress* @param int $max Max value*/public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {$reporter = $this->task->get_progress();// Start tracking progress if necessary.if (!$this->startedprogress) {$reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE)? \core\progress\base::INDETERMINATE : $max);$this->startedprogress = true;}// Pass progress through to whatever handles it.$reporter->progress(($progress == file_progress::INDETERMINATE)? \core\progress\base::INDETERMINATE : $progress);}}/*** This step will send the generated backup file to its final destination*/class backup_store_backup_file extends backup_execution_step {protected function define_execution() {// Get basepath$basepath = $this->get_basepath();// Calculate the zip fullpath (in OS temp area it's always backup.mbz)$zipfile = $basepath . '/backup.mbz';$has_file_references = backup_controller_dbops::backup_includes_file_references($this->get_backupid());// Perform storage and return it (TODO: shouldn't be array but proper result object)return array('backup_destination' => backup_helper::store_backup_file($this->get_backupid(), $zipfile,$this->task->get_progress()),'include_file_references_to_external_content' => $has_file_references);}}/*** This step will search for all the activity (not calculations, categories nor aggregations) grade items* and put them to the backup_ids tables, to be used later as base to backup them*/class backup_activity_grade_items_to_ids extends backup_execution_step {protected function define_execution() {// Fetch all activity grade itemsif ($items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $this->task->get_modulename(),'iteminstance' => $this->task->get_activityid(), 'courseid' => $this->task->get_courseid()))) {// Annotate them in backup_idsforeach ($items as $item) {backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grade_item', $item->id);}}}}/*** This step allows enrol plugins to annotate custom fields.** @package core_backup* @copyright 2014 University of Wisconsin* @author Matt Petro* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class backup_enrolments_execution_step extends backup_execution_step {/*** Function that will contain all the code to be executed.*/protected function define_execution() {global $DB;$plugins = enrol_get_plugins(true);$enrols = $DB->get_records('enrol', array('courseid' => $this->task->get_courseid()));// Allow each enrol plugin to add annotations.foreach ($enrols as $enrol) {if (isset($plugins[$enrol->enrol])) {$plugins[$enrol->enrol]->backup_annotate_custom_fields($this, $enrol);}}}/*** Annotate a single name/id pair.* This can be called from {@link enrol_plugin::backup_annotate_custom_fields()}.** @param string $itemname* @param int $itemid*/public function annotate_id($itemname, $itemid) {backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), $itemname, $itemid);}}/*** This step will annotate all the groups and groupings belonging to the course*/class backup_annotate_course_groups_and_groupings extends backup_execution_step {protected function define_execution() {global $DB;// Get all the course groupsif ($groups = $DB->get_records('groups', array('courseid' => $this->task->get_courseid()))) {foreach ($groups as $group) {backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->id);}}// Get all the course groupingsif ($groupings = $DB->get_records('groupings', array('courseid' => $this->task->get_courseid()))) {foreach ($groupings as $grouping) {backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'grouping', $grouping->id);}}}}/*** This step will annotate all the groups belonging to already annotated groupings*/class backup_annotate_groups_from_groupings extends backup_execution_step {protected function define_execution() {global $DB;// Fetch all the annotated groupingsif ($groupings = $DB->get_records('backup_ids_temp', array('backupid' => $this->get_backupid(), 'itemname' => 'grouping'))) {foreach ($groupings as $grouping) {if ($groups = $DB->get_records('groupings_groups', array('groupingid' => $grouping->itemid))) {foreach ($groups as $group) {backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'group', $group->groupid);}}}}}}/*** This step will annotate all the scales belonging to already annotated outcomes*/class backup_annotate_scales_from_outcomes extends backup_execution_step {protected function define_execution() {global $DB;// Fetch all the annotated outcomesif ($outcomes = $DB->get_records('backup_ids_temp', array('backupid' => $this->get_backupid(), 'itemname' => 'outcome'))) {foreach ($outcomes as $outcome) {if ($scale = $DB->get_record('grade_outcomes', array('id' => $outcome->itemid))) {// Annotate as scalefinal because it's > 0backup_structure_dbops::insert_backup_ids_record($this->get_backupid(), 'scalefinal', $scale->scaleid);}}}}}/*** This step will generate all the file annotations for the already* annotated (final) question_categories. It calculates the different* contexts that are being backup and, annotates all the files* on every context belonging to the "question" component. As far as* we are always including *complete* question banks it is safe and* optimal to do that in this (one pass) way*/class backup_annotate_all_question_files extends backup_execution_step {protected function define_execution() {global $DB;// Get all the different contexts for the final question_categories// annotated along the whole backup$rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextidFROM {question_categories} qcJOIN {backup_ids_temp} bi ON bi.itemid = qc.idWHERE bi.backupid = ?AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));// To know about qtype specific components/fileareas$components = backup_qtype_plugin::get_components_and_fileareas();$progress = $this->task->get_progress();$progress->start_progress($this->get_name());// Let's loopforeach($rs as $record) {// Backup all the file areas the are managed by the core question component.// That is, by the question_type base class. In particular, we don't want// to include files belonging to responses here.backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'questiontext', null,$progress);backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'generalfeedback', null,$progress);backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answer', null,$progress);backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'answerfeedback', null,$progress);backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'hint', null,$progress);backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'correctfeedback', null,$progress);backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question','partiallycorrectfeedback', null, $progress);backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', 'incorrectfeedback', null,$progress);// For files belonging to question types, we make the leap of faith that// all the files belonging to the question type are part of the question definition,// so we can just backup all the files in bulk, without specifying each// file area name separately.foreach ($components as $component => $fileareas) {backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null,$progress);}}$progress->end_progress();$rs->close();}}/*** structure step in charge of constructing the questions.xml file for all the* question categories and questions required by the backup* and letters related to one activity.*/class backup_questions_structure_step extends backup_structure_step {protected function define_structure() {// Define each element separately.$qcategories = new backup_nested_element('question_categories');$qcategory = new backup_nested_element('question_category', ['id'],['name','contextid','contextlevel','contextinstanceid','info','infoformat','stamp','parent','sortorder','idnumber',]);$questionbankentries = new backup_nested_element('question_bank_entries');$questionbankentry = new backup_nested_element('question_bank_entry', ['id'],['questioncategoryid','idnumber','ownerid',]);$questionversions = new backup_nested_element('question_version');$questionverion = new backup_nested_element('question_versions', ['id'], ['version', 'status']);$questions = new backup_nested_element('questions');$question = new backup_nested_element('question', ['id'],['parent','name','questiontext','questiontextformat','generalfeedback','generalfeedbackformat','defaultmark','penalty','qtype','length','stamp','timecreated','timemodified','createdby','modifiedby',]);// Attach qtype plugin structure to $question element, only one allowed.$this->add_plugin_structure('qtype', $question, false);// Attach qbank plugin stucture to $question element, multiple allowed.$this->add_plugin_structure('qbank', $question, true);// attach local plugin stucture to $question element, multiple allowed$this->add_plugin_structure('local', $question, true);$qhints = new backup_nested_element('question_hints');$qhint = new backup_nested_element('question_hint', ['id'],['hint','hintformat','shownumcorrect','clearwrong','options',]);$tags = new backup_nested_element('tags');$tag = new backup_nested_element('tag', ['id', 'contextid'], ['name', 'rawname']);// Build the initial tree.$qcategories->add_child($qcategory);$qcategory->add_child($questionbankentries);$questionbankentries->add_child($questionbankentry);$questionbankentry->add_child($questionversions);$questionversions->add_child($questionverion);$questionverion->add_child($questions);$questions->add_child($question);$question->add_child($qhints);$qhints->add_child($qhint);// Add question tags.$question->add_child($tags);$tags->add_child($tag);$qcategory->set_source_sql("SELECT gc.*,contextlevel,instanceid AS contextinstanceidFROM {question_categories} gcJOIN {backup_ids_temp} bi ON bi.itemid = gc.idJOIN {context} co ON co.id = gc.contextidWHERE bi.backupid = ?AND bi.itemname = 'question_categoryfinal'", [backup::VAR_BACKUPID]);$questionbankentry->set_source_table('question_bank_entries', ['questioncategoryid' => backup::VAR_PARENTID]);$questionverion->set_source_table('question_versions', ['questionbankentryid' => backup::VAR_PARENTID]);$question->set_source_sql('SELECT q.*FROM {question} qJOIN {question_versions} qv ON qv.questionid = q.idJOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryidWHERE qv.id = ?', [backup::VAR_PARENTID]);$qhint->set_source_sql('SELECT *FROM {question_hints}WHERE questionid = :questionidORDER BY id', ['questionid' => backup::VAR_PARENTID]);$tag->set_source_sql("SELECT t.id, ti.contextid, t.name, t.rawnameFROM {tag} tJOIN {tag_instance} ti ON ti.tagid = t.idWHERE ti.itemid = ?AND ti.itemtype = 'question'AND ti.component = 'core_question'", [backup::VAR_PARENTID]);// Don't need to annotate ids nor files.// ...(already done by {@see backup_annotate_all_question_files()}.return $qcategories;}}/*** This step will generate all the file annotations for the already* annotated (final) users. Need to do this here because each user* has its own context and structure tasks only are able to handle* one context. Also, this step will guarantee that every user has* its context created (req for other steps)*/class backup_annotate_all_user_files extends backup_execution_step {protected function define_execution() {global $DB;// List of fileareas we are going to annotate$fileareas = array('profile', 'icon');// Fetch all annotated (final) users$rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));$progress = $this->task->get_progress();$progress->start_progress($this->get_name());foreach ($rs as $record) {$userid = $record->itemid;$userctx = context_user::instance($userid, IGNORE_MISSING);if (!$userctx) {continue; // User has not context, sure it's a deleted user, so cannot have files}// Proceed with every user fileareaforeach ($fileareas as $filearea) {// We don't need to specify itemid ($userid - 5th param) as far as by// context we can get all the associated files. See MDL-22092backup_structure_dbops::annotate_files($this->get_backupid(), $userctx->id, 'user', $filearea, null);$progress->progress();}}$progress->end_progress();$rs->close();}}/*** Defines the backup step for advanced grading methods attached to the activity module*/class backup_activity_grading_structure_step extends backup_structure_step {/*** Include the grading.xml only if the module supports advanced grading*/protected function execute_condition() {// No grades on the front page.if ($this->get_courseid() == SITEID) {return false;}return plugin_supports('mod', $this->get_task()->get_modulename(), FEATURE_ADVANCED_GRADING, false);}/*** Declares the gradable areas structures and data sources*/protected function define_structure() {// To know if we are including userinfo$userinfo = $this->get_setting_value('userinfo');// Define the elements$areas = new backup_nested_element('areas');$area = new backup_nested_element('area', array('id'), array('areaname', 'activemethod'));$definitions = new backup_nested_element('definitions');$definition = new backup_nested_element('definition', array('id'), array('method', 'name', 'description', 'descriptionformat', 'status','timecreated', 'timemodified', 'options'));$instances = new backup_nested_element('instances');$instance = new backup_nested_element('instance', array('id'), array('raterid', 'itemid', 'rawgrade', 'status', 'feedback','feedbackformat', 'timemodified'));// Build the tree including the method specific structures// (beware - the order of how gradingform plugins structures are attached is important)$areas->add_child($area);// attach local plugin stucture to $area element, multiple allowed$this->add_plugin_structure('local', $area, true);$area->add_child($definitions);$definitions->add_child($definition);$this->add_plugin_structure('gradingform', $definition, true);// attach local plugin stucture to $definition element, multiple allowed$this->add_plugin_structure('local', $definition, true);$definition->add_child($instances);$instances->add_child($instance);$this->add_plugin_structure('gradingform', $instance, false);// attach local plugin stucture to $instance element, multiple allowed$this->add_plugin_structure('local', $instance, true);// Define data sources$area->set_source_table('grading_areas', array('contextid' => backup::VAR_CONTEXTID,'component' => array('sqlparam' => 'mod_'.$this->get_task()->get_modulename())));$definition->set_source_table('grading_definitions', array('areaid' => backup::VAR_PARENTID));if ($userinfo) {$instance->set_source_table('grading_instances', array('definitionid' => backup::VAR_PARENTID));}// Annotate references$definition->annotate_files('grading', 'description', 'id');$instance->annotate_ids('user', 'raterid');// Return the root elementreturn $areas;}}/*** structure step in charge of constructing the grades.xml file for all the grade items* and letters related to one activity*/class backup_activity_grades_structure_step extends backup_structure_step {/*** No grades on the front page.* @return bool*/protected function execute_condition() {return ($this->get_courseid() != SITEID);}protected function define_structure() {global $CFG;require_once($CFG->libdir . '/grade/constants.php');// To know if we are including userinfo$userinfo = $this->get_setting_value('userinfo');// Define each element separated$book = new backup_nested_element('activity_gradebook');$items = new backup_nested_element('grade_items');$item = new backup_nested_element('grade_item', array('id'), array('categoryid', 'itemname', 'itemtype', 'itemmodule','iteminstance', 'itemnumber', 'iteminfo', 'idnumber','calculation', 'gradetype', 'grademax', 'grademin','scaleid', 'outcomeid', 'gradepass', 'multfactor','plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride','sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime','needsupdate', 'timecreated', 'timemodified'));$grades = new backup_nested_element('grade_grades');$grade = new backup_nested_element('grade_grade', array('id'), array('userid', 'rawgrade', 'rawgrademax', 'rawgrademin','rawscaleid', 'usermodified', 'finalgrade', 'hidden','locked', 'locktime', 'exported', 'overridden','excluded', 'feedback', 'feedbackformat', 'information','informationformat', 'timecreated', 'timemodified','aggregationstatus', 'aggregationweight'));$letters = new backup_nested_element('grade_letters');$letter = new backup_nested_element('grade_letter', 'id', array('lowerboundary', 'letter'));// Build the tree$book->add_child($items);$items->add_child($item);$item->add_child($grades);$grades->add_child($grade);$book->add_child($letters);$letters->add_child($letter);// Define sources$item->set_source_sql("SELECT gi.*FROM {grade_items} giJOIN {backup_ids_temp} bi ON gi.id = bi.itemidWHERE bi.backupid = ?AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));// This only happens if we are including user infoif ($userinfo) {$grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));$grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, 'id');}$letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));// Annotations$item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0$item->annotate_ids('outcome', 'outcomeid');$grade->annotate_ids('user', 'userid');$grade->annotate_ids('user', 'usermodified');// Return the root element (book)return $book;}}/*** Structure step in charge of constructing the grade history of an activity.** This step is added to the task regardless of the setting 'grade_histories'.* The reason is to allow for a more flexible step in case the logic needs to be* split accross different settings to control the history of items and/or grades.*/class backup_activity_grade_history_structure_step extends backup_structure_step {/*** No grades on the front page.* @return bool*/protected function execute_condition() {return ($this->get_courseid() != SITEID);}protected function define_structure() {global $CFG;require_once($CFG->libdir . '/grade/constants.php');// Settings to use.$userinfo = $this->get_setting_value('userinfo');$history = $this->get_setting_value('grade_histories');// Create the nested elements.$bookhistory = new backup_nested_element('grade_history');$grades = new backup_nested_element('grade_grades');$grade = new backup_nested_element('grade_grade', array('id'), array('action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid','rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid','usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden','excluded', 'feedback', 'feedbackformat', 'information','informationformat', 'timemodified'));// Build the tree.$bookhistory->add_child($grades);$grades->add_child($grade);// This only happens if we are including user info and history.if ($userinfo && $history) {// Define sources. Only select the history related to existing activity items.$grade->set_source_sql("SELECT ggh.*FROM {grade_grades_history} gghJOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemidWHERE bi.backupid = ?AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));$grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA, 'id');}// Annotations.$grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0.$grade->annotate_ids('user', 'loggeduser');$grade->annotate_ids('user', 'userid');$grade->annotate_ids('user', 'usermodified');// Return the root element.return $bookhistory;}}/*** Backups up the course completion information for the course.*/class backup_course_completion_structure_step extends backup_structure_step {protected function execute_condition() {// No completion on front page.if ($this->get_courseid() == SITEID) {return false;}// Check that all activities have been includedif ($this->task->is_excluding_activities()) {return false;}return true;}/*** The structure of the course completion backup** @return backup_nested_element*/protected function define_structure() {// To know if we are including user completion info$userinfo = $this->get_setting_value('userscompletion');$cc = new backup_nested_element('course_completion');$criteria = new backup_nested_element('course_completion_criteria', array('id'), array('course', 'criteriatype', 'module', 'moduleinstance', 'courseinstanceshortname', 'enrolperiod','timeend', 'gradepass', 'role', 'roleshortname'));$criteriacompletions = new backup_nested_element('course_completion_crit_completions');$criteriacomplete = new backup_nested_element('course_completion_crit_compl', array('id'), array('criteriaid', 'userid', 'gradefinal', 'unenrolled', 'timecompleted'));$coursecompletions = new backup_nested_element('course_completions', array('id'), array('userid', 'course', 'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate'));$aggregatemethod = new backup_nested_element('course_completion_aggr_methd', array('id'), array('course','criteriatype','method','value'));$cc->add_child($criteria);$criteria->add_child($criteriacompletions);$criteriacompletions->add_child($criteriacomplete);$cc->add_child($coursecompletions);$cc->add_child($aggregatemethod);// We need some extra data for the restore.// - courseinstances shortname rather than an ID.// - roleshortname in case restoring on a different site.$sourcesql = "SELECT ccc.*, c.shortname AS courseinstanceshortname, r.shortname AS roleshortnameFROM {course_completion_criteria} cccLEFT JOIN {course} c ON c.id = ccc.courseinstanceLEFT JOIN {role} r ON r.id = ccc.roleWHERE ccc.course = ?";$criteria->set_source_sql($sourcesql, array(backup::VAR_COURSEID));$aggregatemethod->set_source_table('course_completion_aggr_methd', array('course' => backup::VAR_COURSEID));if ($userinfo) {$criteriacomplete->set_source_table('course_completion_crit_compl', array('criteriaid' => backup::VAR_PARENTID));$coursecompletions->set_source_table('course_completions', array('course' => backup::VAR_COURSEID));}$criteria->annotate_ids('role', 'role');$criteriacomplete->annotate_ids('user', 'userid');$coursecompletions->annotate_ids('user', 'userid');return $cc;}}/*** Backup completion defaults for each module type.** @package core_backup* @copyright 2017 Marina Glancy* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class backup_completion_defaults_structure_step extends backup_structure_step {/*** To conditionally decide if one step will be executed or no*/protected function execute_condition() {// No completion on front page.if ($this->get_courseid() == SITEID) {return false;}return true;}/*** The structure of the course completion backup** @return backup_nested_element*/protected function define_structure() {$cc = new backup_nested_element('course_completion_defaults');$defaults = new backup_nested_element('course_completion_default', array('id'), array('modulename', 'completion', 'completionview', 'completionusegrade', 'completionpassgrade','completionexpected', 'customrules'));// Use module name instead of module id so we can insert into another site later.$sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade,d.completionpassgrade, d.completionexpected, d.customrulesFROM {course_completion_defaults} d join {modules} m on d.module = m.idWHERE d.course = ?";$defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID));$cc->add_child($defaults);return $cc;}}/*** Structure step in charge of constructing the contentbank.xml file for all the contents found in a given context*/class backup_contentbankcontent_structure_step extends backup_structure_step {/*** Define structure for content bank step*/protected function define_structure() {// Define each element separated.$contents = new backup_nested_element('contents');$content = new backup_nested_element('content', ['id'], ['name', 'contenttype', 'instanceid', 'configdata', 'usercreated', 'usermodified', 'timecreated', 'timemodified']);// Build the tree.$contents->add_child($content);// Define sources.$content->set_source_table('contentbank_content', ['contextid' => backup::VAR_CONTEXTID]);// Define annotations.$content->annotate_ids('user', 'usercreated');$content->annotate_ids('user', 'usermodified');$content->annotate_files('contentbank', 'public', 'id');// Return the root element (contents).return $contents;}}/*** Structure step in charge of constructing the xapistate.xml file for all the xAPI states found in a given context.*/class backup_xapistate_structure_step extends backup_structure_step {/*** Define structure for content bank step*/protected function define_structure() {// Define each element separated.$states = new backup_nested_element('states');$state = new backup_nested_element('state',['id'],['component', 'userid', 'itemid', 'stateid', 'statedata', 'registration', 'timecreated', 'timemodified']);// Build the tree.$states->add_child($state);// Define sources.$state->set_source_table('xapi_states', ['itemid' => backup::VAR_CONTEXTID]);// Define annotations.$state->annotate_ids('user', 'userid');// Return the root element (contents).return $states;}}