Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
<?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 dir
        backup_helper::clear_backup_dir($this->get_backupid(), $progress);           // Empty temp dir, just in case
        backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
        backup_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 avoided
        if (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_element
        if (! $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 sectionnumber
              FROM {course_modules} cm
              JOIN {modules} m ON m.id = cm.module
              JOIN {config_plugins} cp ON cp.plugin = $concat AND cp.name = 'version'
              JOIN {course_sections} s ON s.id = cm.section
             WHERE cm.id = ?", array(backup::VAR_MODID));

        $tag->set_source_sql("SELECT t.id, t.name, t.rawname
                                FROM {tag} t
                                JOIN {tag_instance} ti ON ti.tagid = t.id
                               WHERE 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', array('id'), array(
                'number', 'name', 'summary', 'summaryformat', 'sequence', 'visible',
                'availabilityjson', '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.value
              FROM {course} c
              JOIN {course_format_options} cfo
              ON cfo.courseid = c.id AND cfo.format = c.format
              WHERE 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',
> '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' )); $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.rawname FROM {tag} t JOIN {tag_instance} ti ON ti.tagid = t.id WHERE 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, value FROM {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()); $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} ue INNER JOIN {role_assignments} ra ON ue.userid = ra.userid WHERE 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 specified if ($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 nameincourse FROM {role} r JOIN {backup_ids_temp} bi ON r.id = bi.itemid LEFT 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} s JOIN {backup_ids_temp} bi ON s.id = bi.itemid WHERE 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} o JOIN {backup_ids_temp} bi ON o.id = bi.itemid WHERE 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 sources list($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')); // 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); // Define sources. $parametersql = ' SELECT * FROM {badge} WHERE courseid = :courseid AND 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.criteriatype FROM {badge_criteria_param} cp JOIN {badge_criteria} c ON cp.critid = c.id WHERE 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)); // 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 sources if ($this->name == 'course_calendar') { $calendar_items_sql ="SELECT * FROM {event} WHERE courseid = :courseid AND (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 = :instance AND type <> :type AND 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 = :courseid AND (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.sortorder FROM {grade_categories} gc JOIN {grade_items} gi ON (gi.iteminstance = gc.id) WHERE gc.courseid = :courseid AND (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 element return $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} ggh JOIN {grade_items} gi ON ggh.itemid = gi.id WHERE gi.courseid = :courseid AND (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', 'timecreated', 'timemodified'));
> 'picture', 'visibility', 'participation', 'timecreated', 'timemodified'));
$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')); $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($groupings); $group->add_child($members); $members->add_child($member); $groupings->add_child($grouping); $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} g JOIN {backup_ids_temp} bi ON g.id = bi.itemid WHERE bi.backupid = ? AND bi.itemname = 'groupfinal'", array(backup::VAR_BACKUPID)); $grouping->set_source_sql(" SELECT g.* FROM {groupings} g JOIN {backup_ids_temp} bi ON g.id = bi.itemid WHERE 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)); } } // 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; } } /** * 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 element foreach ($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 conditionally if (!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 mnethosturl FROM {user} u JOIN {backup_ids_temp} bi ON bi.itemid = u.id LEFT JOIN {context} c ON c.instanceid = u.id AND c.contextlevel = ' . CONTEXT_USER . ' LEFT JOIN {mnet_host} m ON m.id = u.mnethostid WHERE 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 backup if (!$anonymize) { $customfield->set_source_sql('SELECT f.id, f.shortname, f.datatype, d.data FROM {user_info_field} f JOIN {user_info_data} d ON d.fieldid = f.id WHERE 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.rawname FROM {tag} t JOIN {tag_instance} ti ON ti.tagid = t.id WHERE 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 specified if ($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, timeaccess FROM {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.pushratingstouserplans FROM {' . \core_competency\course_competency_settings::TABLE . '} s WHERE 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 frameworkidnumber FROM {' . \core_competency\course_competency::TABLE . '} cc JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cc.competencyid JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid WHERE cc.courseid = :courseid ORDER 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.grade FROM {' . \core_competency\user_competency_course::TABLE . '} ucc WHERE ucc.courseid = :courseid AND 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 frameworkidnumber FROM {' . \core_competency\course_module_competency::TABLE . '} cmc JOIN {' . \core_competency\competency::TABLE . '} c ON c.id = cmc.competencyid JOIN {' . \core_competency\competency_framework::TABLE . '} f ON f.id = c.competencyframeworkid WHERE cmc.cmid = :coursemoduleid ORDER 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 element foreach ($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 id FROM {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 dbops backup_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.reference FROM {files} f LEFT JOIN {files_reference} fr ON fr.id = f.referencefileid LEFT JOIN {repository_instances} ri ON ri.id = fr.repositoryid LEFT JOIN {repository} r ON r.id = ri.typeid JOIN {backup_ids_temp} bi ON f.id = bi.itemid WHERE 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 controller list($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 file return $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 items if ($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_ids foreach ($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 groups if ($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 groupings if ($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 groupings if ($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 outcomes if ($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 > 0 backup_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.contextid FROM {question_categories} qc JOIN {backup_ids_temp} bi ON bi.itemid = qc.id WHERE 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 loop foreach($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 contextinstanceid FROM {question_categories} gc JOIN {backup_ids_temp} bi ON bi.itemid = gc.id JOIN {context} co ON co.id = gc.contextid WHERE 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} q JOIN {question_versions} qv ON qv.questionid = q.id JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid WHERE qv.id = ?', [backup::VAR_PARENTID]); $qhint->set_source_sql(' SELECT * FROM {question_hints} WHERE questionid = :questionid ORDER BY id', ['questionid' => backup::VAR_PARENTID]); $tag->set_source_sql("SELECT t.id, ti.contextid, t.name, t.rawname FROM {tag} t JOIN {tag_instance} ti ON ti.tagid = t.id WHERE 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 filearea foreach ($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-22092 backup_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 element return $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} gi JOIN {backup_ids_temp} bi ON gi.id = bi.itemid WHERE bi.backupid = ? AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID)); // This only happens if we are including user info if ($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} ggh JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid WHERE 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 included if ($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 roleshortname FROM {course_completion_criteria} ccc LEFT JOIN {course} c ON c.id = ccc.courseinstance LEFT JOIN {role} r ON r.id = ccc.role WHERE 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.customrules FROM {course_completion_defaults} d join {modules} m on d.module = m.id WHERE 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;