MDL-21432 backup 2.0 - initial commit. moodle2 format
authorEloy Lafuente <stronk7@moodle.org>
Wed, 21 Apr 2010 09:19:30 +0000 (09:19 +0000)
committerEloy Lafuente <stronk7@moodle.org>
Wed, 21 Apr 2010 09:19:30 +0000 (09:19 +0000)
12 files changed:
backup/moodle2/backup_activity_task.class.php [new file with mode: 0644]
backup/moodle2/backup_block_task.class.php [new file with mode: 0644]
backup/moodle2/backup_course_task.class.php [new file with mode: 0644]
backup/moodle2/backup_custom_fields.php [new file with mode: 0644]
backup/moodle2/backup_default_block_task.class.php [new file with mode: 0644]
backup/moodle2/backup_final_task.class.php [new file with mode: 0644]
backup/moodle2/backup_plan_builder.class.php [new file with mode: 0644]
backup/moodle2/backup_root_task.class.php [new file with mode: 0644]
backup/moodle2/backup_section_task.class.php [new file with mode: 0644]
backup/moodle2/backup_settingslib.php [new file with mode: 0644]
backup/moodle2/backup_stepslib.php [new file with mode: 0644]
backup/moodle2/backup_xml_transformer.class.php [new file with mode: 0644]

diff --git a/backup/moodle2/backup_activity_task.class.php b/backup/moodle2/backup_activity_task.class.php
new file mode 100644 (file)
index 0000000..daac34a
--- /dev/null
@@ -0,0 +1,248 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * abstract activity task that provides all the properties and common tasks to be performed
+ * when one activity is being backup
+ *
+ * TODO: Finish phpdocs
+ */
+abstract class backup_activity_task extends backup_task {
+
+    protected $moduleid;
+    protected $sectionid;
+    protected $modulename;
+    protected $activityid;
+    protected $contextid;
+
+    /**
+     * Constructor - instantiates one object of this class
+     */
+    public function __construct($name, $moduleid, $plan = null) {
+
+        // Check moduleid exists
+        if (!$coursemodule = get_coursemodule_from_id(false, $moduleid)) {
+            throw backup_task_exception('activity_task_coursemodule_not_found', $moduleid);
+        }
+        // Check activity supports this moodle2 backup format
+        if (!plugin_supports('mod', $coursemodule->modname, FEATURE_BACKUP_MOODLE2)) {
+            throw backup_task_exception('activity_task_activity_lacks_moodle2_backup_support', $coursemodule->modname);
+        }
+
+        $this->moduleid   = $moduleid;
+        $this->sectionid  = $coursemodule->section;
+        $this->modulename = $coursemodule->modname;
+        $this->activityid = $coursemodule->instance;
+        $this->contextid  = get_context_instance(CONTEXT_MODULE, $this->moduleid)->id;
+
+        parent::__construct($name, $plan);
+    }
+
+    public function get_moduleid() {
+        return $this->moduleid;
+    }
+
+    public function get_sectionid() {
+        return $this->sectionid;
+    }
+
+    public function get_modulename() {
+        return $this->modulename;
+    }
+
+    public function get_activityid() {
+        return $this->activityid;
+    }
+
+    public function get_contextid() {
+        return $this->contextid;
+    }
+
+    /**
+     * Activity tasks have their own directory to write files
+     */
+    public function get_taskbasepath() {
+        return $this->get_basepath() . '/activities/' . $this->modulename . '_' . $this->moduleid;
+    }
+
+    /**
+     * Create all the steps that will be part of this task
+     */
+    public function build() {
+
+        // Add some extra settings that related processors are going to need
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_MODID, base_setting::IS_INTEGER, $this->moduleid));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid()));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_SECTIONID, base_setting::IS_INTEGER, $this->sectionid));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_MODNAME, base_setting::IS_FILENAME, $this->modulename));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_ACTIVITYID, base_setting::IS_INTEGER, $this->activityid));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $this->contextid));
+
+        // Create the activity directory
+        $this->add_step(new create_taskbasepath_directory('create_activity_directory'));
+
+        // Generate the module.xml file, contaning general information for the
+        // activity and from its related course_modules record and availability
+        $this->add_step(new backup_module_structure_step('module_info', 'module.xml'));
+
+        // Annotate the groups used in already annotated groupings
+        $this->add_step(new backup_annotate_groups_from_groupings('annotate_groups'));
+
+        // Here we add all the common steps for any activity and, in the point of interest
+        // we call to define_my_steps() is order to get the particular ones inserted in place.
+        $this->define_my_steps();
+
+        // Generate the roles file (optionally role assignments and always role overrides)
+        $this->add_step(new backup_roles_structure_step('activity_roles', 'roles.xml'));
+
+        // Generate the filter file (conditionally)
+        if ($this->get_setting_value('filters')) {
+            $this->add_step(new backup_filters_structure_step('activity_filters', 'filters.xml'));
+        }
+
+        // Generate the comments file (conditionally)
+        if ($this->get_setting_value('comments')) {
+            $this->add_step(new backup_comments_structure_step('activity_comments', 'comments.xml'));
+        }
+
+        // Generate the userscompletion file (conditionally)
+        if ($this->get_setting_value('userscompletion')) {
+            $this->add_step(new backup_userscompletion_structure_step('activity_userscompletion', 'completion.xml'));
+        }
+
+        // Generate the logs file (conditionally)
+        if ($this->get_setting_value('logs')) {
+            $this->add_step(new backup_activity_logs_structure_step('activity_logs', 'logs.xml'));
+        }
+
+        // Fetch all the activity grade items and put them to backup_ids
+        $this->add_step(new backup_activity_grade_items_to_ids('fetch_activity_grade_items'));
+
+        // Generate the grades file
+        $this->add_step(new backup_activity_grades_structure_step('activity_grades', 'grades.xml'));
+
+        // Annotate the scales used in already annotated outcomes
+        $this->add_step(new backup_annotate_scales_from_outcomes('annotate_scales'));
+
+        // NOTE: Historical grade information is saved completely at course level only (see 1.9)
+        // not per activity nor per selected activities (all or nothing).
+
+        // Generate the inforef file (must be after ALL steps gathering annotations of ANY type)
+        $this->add_step(new backup_inforef_structure_step('activity_inforef', 'inforef.xml'));
+
+        // Migrate the already exported inforef entries to final ones
+        $this->add_step(new move_inforef_annotations_to_final('migrate_inforef'));
+
+        // At the end, mark it as built
+        $this->built = true;
+    }
+
+    /**
+     * Exceptionally override the execute method, so, based in the activity_included setting, we are able
+     * to skip the execution of one task completely
+     */
+    public function execute() {
+
+        // Find activity_included_setting
+        if (!$this->get_setting_value('included')) {
+            $this->log('activity skipped by _included setting', backup::LOG_DEBUG, $this->name);
+
+        } else { // Setting tells us it's ok to execute
+            parent::execute();
+        }
+    }
+
+
+    /**
+     * Specialisation that, first of all, looks for the setting within
+     * the task with the the prefix added and later, delegates to parent
+     * without adding anything
+     */
+    public function get_setting($name) {
+        $namewithprefix = $this->modulename . '_' . $this->moduleid . '_' . $name;
+        $result = null;
+        foreach ($this->settings as $key => $setting) {
+            if ($setting->get_name() == $namewithprefix) {
+                if ($result != null) {
+                    throw new base_task_exception('multiple_settings_by_name_found', $namewithprefix);
+                } else {
+                    $result = $setting;
+                }
+            }
+        }
+        if ($result) {
+            return $result;
+        } else {
+            // Fallback to parent
+            return parent::get_setting($name);
+        }
+    }
+
+// Protected API starts here
+
+    /**
+     * Define the common setting that any backup activity will have
+     */
+    protected function define_settings() {
+
+        // All the settings related to this activity will include this prefix
+        $settingprefix = $this->modulename . '_' . $this->moduleid . '_';
+
+        // All these are common settings to be shared by all activities
+
+        // Define activity_include (to decide if the whole task must be really executed)
+        $settingname = $settingprefix . 'included';
+        $activity_userinfo = new backup_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true);
+        $this->add_setting($activity_userinfo);
+
+        // Define activity_userinfo (dependent of root users setting)
+        $settingname = $settingprefix . 'userinfo';
+        $settingname = $this->modulename . '_' . $this->moduleid . '_userinfo';
+        $activity_userinfo = new backup_activity_userinfo_setting($settingname, base_setting::IS_BOOLEAN, true);
+        $this->add_setting($activity_userinfo);
+        // Look for "users" root setting
+        $users = $this->plan->get_setting('users');
+        $users->add_dependency($activity_userinfo);
+
+        // End of common activity settings, let's add the particular ones
+        $this->define_my_settings();
+    }
+
+    /**
+     * Define (add) particular settings that each activity can have
+     */
+    abstract protected function define_my_settings();
+
+    /**
+     * Define (add) particular steps that each activity can have
+     */
+    abstract protected function define_my_steps();
+
+    /**
+     * Code the transformations to perform in the activity in
+     * order to get transportable (encoded) links
+     */
+    abstract static public function encode_content_links($content);
+
+}
diff --git a/backup/moodle2/backup_block_task.class.php b/backup/moodle2/backup_block_task.class.php
new file mode 100644 (file)
index 0000000..113b95a
--- /dev/null
@@ -0,0 +1,200 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * abstract block task that provides all the properties and common steps to be performed
+ * when one block is being backup
+ *
+ * TODO: Finish phpdocs
+ */
+abstract class backup_block_task extends backup_task {
+
+    protected $blockid;
+    protected $blockname;
+    protected $contextid;
+    protected $moduleid;
+    protected $modulename;
+    protected $parentcontextid;
+
+    /**
+     * Constructor - instantiates one object of this class
+     */
+    public function __construct($name, $blockid, $moduleid = null, $plan = null) {
+        global $DB;
+
+        // Check blockid exists
+        if (!$block = $DB->get_record('block_instances', array('id' => $blockid))) {
+            throw backup_task_exception('block_task_block_instance_not_found', $blockid);
+        }
+
+        $this->blockid    = $blockid;
+        $this->blockname  = $block->blockname;
+        $this->contextid  = get_context_instance(CONTEXT_BLOCK, $this->blockid)->id;
+        $this->moduleid   = $moduleid;
+        $this->modulename = null;
+        $this->parentcontextid = null;
+
+        // If moduleid passed, check exists, supports moodle2 format and save info
+        // Check moduleid exists
+        if (!empty($moduleid)) {
+            if (!$coursemodule = get_coursemodule_from_id(false, $moduleid)) {
+                throw new backup_task_exception('block_task_coursemodule_not_found', $moduleid);
+            }
+            // Check activity supports this moodle2 backup format
+            if (!plugin_supports('mod', $coursemodule->modname, FEATURE_BACKUP_MOODLE2)) {
+                throw new backup_task_exception('block_task_activity_lacks_moodle2_backup_support', $coursemodule->modname);
+            }
+
+            $this->moduleid   = $moduleid;
+            $this->modulename = $coursemodule->modname;
+            $this->parentcontextid  = get_context_instance(CONTEXT_MODULE, $this->moduleid)->id;
+        }
+
+        parent::__construct($name, $plan);
+    }
+
+    public function get_blockid() {
+        return $this->blockid;
+    }
+
+    public function get_blockname() {
+        return $this->blockname;
+    }
+
+    public function get_moduleid() {
+        return $this->moduleid;
+    }
+
+    public function get_modulename() {
+        return $this->modulename;
+    }
+
+    public function get_contextid() {
+        return $this->contextid;
+    }
+
+    public function get_parentcontextid() {
+        return $this->parentcontextid;
+    }
+
+    /**
+     * Block tasks have their own directory to write files
+     */
+    public function get_taskbasepath() {
+        $basepath = $this->get_basepath();
+
+        // Module blocks are under module dir
+        if (!empty($this->moduleid)) {
+            $basepath .= '/activities/' . $this->modulename . '_' . $this->moduleid .
+                         '/blocks/' . $this->blockname . '_' . $this->blockid;
+
+        // Course blocks are under course dir
+        } else {
+            $basepath .= '/course/blocks/' . $this->blockname . '_' . $this->blockid;
+        }
+        return $basepath;
+    }
+
+    /**
+     * Create all the steps that will be part of this task
+     */
+    public function build() {
+
+        // If we have decided not to backup blocks, prevent anything to be built
+        if (!$this->get_setting_value('blocks')) {
+            $this->built = true;
+            return;
+        }
+
+        // Add some extra settings that related processors are going to need
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_BLOCKID, base_setting::IS_INTEGER, $this->blockid));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_BLOCKNAME, base_setting::IS_FILENAME, $this->blockname));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_MODID, base_setting::IS_INTEGER, $this->moduleid));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_MODNAME, base_setting::IS_FILENAME, $this->modulename));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid()));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $this->contextid));
+
+        // Create the block directory
+        $this->add_step(new create_taskbasepath_directory('create_block_directory'));
+
+        // Create the block.xml common file (instance + positions)
+        $this->add_step(new backup_block_instance_structure_step('block_commons', 'block.xml'));
+
+        // Here we add all the common steps for any block and, in the point of interest
+        // we call to define_my_steps() is order to get the particular ones inserted in place.
+        $this->define_my_steps();
+
+        // Generate the roles file (optionally role assignments and always role overrides)
+        $this->add_step(new backup_roles_structure_step('block_roles', 'roles.xml'));
+
+        // Generate the comments file (conditionally)
+        if ($this->get_setting_value('comments')) {
+            $this->add_step(new backup_comments_structure_step('block_comments', 'comments.xml'));
+        }
+
+        // Generate the inforef file (must be after ALL steps gathering annotations of ANY type)
+        $this->add_step(new backup_inforef_structure_step('block_inforef', 'inforef.xml'));
+
+        // Migrate the already exported inforef entries to final ones
+        $this->add_step(new move_inforef_annotations_to_final('migrate_inforef'));
+
+        // At the end, mark it as built
+        $this->built = true;
+    }
+
+// Protected API starts here
+
+    /**
+     * Define the common setting that any backup block will have
+     */
+    protected function define_settings() {
+
+        // Nothing to add, blocks doesn't have common settings (for now)
+
+        // End of common activity settings, let's add the particular ones
+        $this->define_my_settings();
+    }
+
+    /**
+     * Define (add) particular settings that each block can have
+     */
+    abstract protected function define_my_settings();
+
+    /**
+     * Define (add) particular steps that each block can have
+     */
+    abstract protected function define_my_steps();
+
+    /**
+     * Define one array() of configdata attributes
+     * that need to be processed by the contenttransformer
+     */
+    abstract public function get_configdata_encoded_attributes();
+
+    /**
+     * Code the transformations to perform in the block in
+     * order to get transportable (encoded) links
+     */
+    abstract static public function encode_content_links($content);
+}
diff --git a/backup/moodle2/backup_course_task.class.php b/backup/moodle2/backup_course_task.class.php
new file mode 100644 (file)
index 0000000..1000310
--- /dev/null
@@ -0,0 +1,116 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * course task that provides all the properties and common steps to be performed
+ * when one course is being backup
+ *
+ * TODO: Finish phpdocs
+ */
+class backup_course_task extends backup_task {
+
+    protected $courseid;
+    protected $contextid;
+
+    /**
+     * Constructor - instantiates one object of this class
+     */
+    public function __construct($name, $courseid, $plan = null) {
+
+        $this->courseid   = $courseid;
+        $this->contextid  = get_context_instance(CONTEXT_COURSE, $this->courseid)->id;
+
+        parent::__construct($name, $plan);
+    }
+
+    public function get_contextid() {
+        return $this->contextid;
+    }
+
+    /**
+     * Course tasks have their own directory to write files
+     */
+    public function get_taskbasepath() {
+
+        return $this->get_basepath() . '/course';
+    }
+
+    /**
+     * Create all the steps that will be part of this task
+     */
+    public function build() {
+
+        // Add some extra settings that related processors are going to need
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid()));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $this->contextid));
+
+        // Create the course directory
+        $this->add_step(new create_taskbasepath_directory('create_course_directory'));
+
+        // Create the course.xml file with course & category information
+        // annotating some bits, metacourse info, tags and module restrictions
+        $this->add_step(new backup_course_structure_step('course_info', 'course.xml'));
+
+        // Annotate the groups used in already annotated groupings
+        $this->add_step(new backup_annotate_groups_from_groupings('annotate_groups'));
+
+        // Generate the roles file (optionally role assignments and always role overrides)
+        $this->add_step(new backup_roles_structure_step('course_roles', 'roles.xml'));
+
+        // Generate the filter file (conditionally)
+        if ($this->get_setting_value('filters')) {
+            $this->add_step(new backup_filters_structure_step('course_filters', 'filters.xml'));
+        }
+
+        // Generate the comments file (conditionally)
+        if ($this->get_setting_value('comments')) {
+            $this->add_step(new backup_comments_structure_step('course_comments', 'comments.xml'));
+        }
+
+        // Generate the logs file (conditionally)
+        if ($this->get_setting_value('logs')) {
+            //$this->add_step(new backup_course_logs_structure_step('course_logs', 'logs.xml'));
+        }
+
+        // Generate the inforef file (must be after ALL steps gathering annotations of ANY type)
+        $this->add_step(new backup_inforef_structure_step('course', 'inforef.xml'));
+
+        // Migrate the already exported inforef entries to final ones
+        $this->add_step(new move_inforef_annotations_to_final('migrate_inforef'));
+
+        // At the end, mark it as built
+        $this->built = true;
+    }
+
+// Protected API starts here
+
+    /**
+     * Define the common setting that any backup section will have
+     */
+    protected function define_settings() {
+
+        // Nothing to add, sections doesn't have common settings (for now)
+
+    }
+}
diff --git a/backup/moodle2/backup_custom_fields.php b/backup/moodle2/backup_custom_fields.php
new file mode 100644 (file)
index 0000000..a56ad65
--- /dev/null
@@ -0,0 +1,103 @@
+<?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/>.
+
+/**
+ * @package    moodlecore
+ * @subpackage backup-moodle2
+ * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Implementation of backup_final_element that provides one interceptor for anonymization of data
+ *
+ * This class overwrites the standard set_value() method, in order to get (by name)
+ * functions from backup_anonymizer_helper executed, producing anonymization of information
+ * to happen in a clean way
+ *
+ * TODO: Finish phpdocs
+ */
+class anonymizer_final_element extends backup_final_element {
+
+    public function set_value($value) {
+        // Get parent name
+        $pname = $this->get_parent()->get_name();
+        // Get my name
+        $myname = $this->get_name();
+        // Define class and function name
+        $classname = 'backup_anonymizer_helper';
+        $methodname= 'process_' . $pname . '_' . $myname;
+        // Invoke the interception method
+        $result = call_user_func(array($classname, $methodname), $value);
+        // Finally set it
+        parent::set_value($result);
+    }
+}
+
+/**
+ * Implementation of backup_final_element that provides special handling of mnethosturl
+ *
+ * This class overwrites the standard set_value() method, in order to decide,
+ * based on various config options, what to do with the field.
+ *
+ * TODO: Finish phpdocs
+ */
+class mnethosturl_final_element extends backup_final_element {
+
+    public function set_value($value) {
+        global $CFG;
+
+        $localhostwwwroot = backup_plan_dbops::get_mnet_localhost_wwwroot();
+
+        // If user wwwroot matches mnet local host one or if
+        // there isn't associated wwwroot, skip sending it to file
+        if ($localhostwwwroot == $value || empty($value)) {
+            // Do nothing
+        } else {
+            parent::set_value($value);
+        }
+    }
+}
+
+/**
+ * Implementation of backup_nested_element that provides special handling of files
+ *
+ * This class overwrites the standard fill_values() method, so it gets intercepted
+ * for each file record being set to xml, in order to copy, at the same file, the
+ * phisical file from moodle file storage to backup file storage
+ *
+ * TODO: Finish phpdocs
+ */
+class file_nested_element extends backup_nested_element {
+
+    protected $backupid;
+
+    public function process($processor) {
+        // Get current backupid from processor, we'll need later
+        if (is_null($this->backupid)) {
+            $this->backupid = $processor->get_var(backup::VAR_BACKUPID);
+        }
+        parent::process($processor);
+    }
+
+    public function fill_values($values) {
+        // Fill values
+        parent::fill_values($values);
+        // Do our own tasks (copy file from moodle to backup)
+        backup_file_manager::copy_file_moodle2backup($this->backupid, $values);
+    }
+}
diff --git a/backup/moodle2/backup_default_block_task.class.php b/backup/moodle2/backup_default_block_task.class.php
new file mode 100644 (file)
index 0000000..4e99c66
--- /dev/null
@@ -0,0 +1,49 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Default block task to backup blocks that haven't own DB structures to be added
+ * when one block is being backup
+ *
+ * TODO: Finish phpdocs
+ */
+class backup_default_block_task extends backup_block_task {
+    // Nothing to do, it's just the backup_block_task in action
+    // with required methods doing nothing special
+
+    protected function define_my_settings() {
+    }
+
+    protected function define_my_steps() {
+    }
+
+    public function get_configdata_encoded_attributes() {
+        return array();
+    }
+
+    static public function encode_content_links($content) {
+        return $content;
+    }
+}
+
diff --git a/backup/moodle2/backup_final_task.class.php b/backup/moodle2/backup_final_task.class.php
new file mode 100644 (file)
index 0000000..b28860a
--- /dev/null
@@ -0,0 +1,98 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Final task that provides all the final steps necessary in order to finish one
+ * backup (mainly gathering references and creating the main xml) apart from
+ * some final cleaning
+ *
+ * TODO: Finish phpdocs
+ */
+class backup_final_task extends backup_task {
+
+    /**
+     * Create all the steps that will be part of this task
+     */
+    public function build() {
+
+        // Set the backup::VAR_CONTEXTID setting to course context as far as next steps require that
+        $coursectxid = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $coursectxid));
+
+        // Generate the groups file with the final annotated groups and groupings
+        // including membership based on setting
+        $this->add_step(new backup_groups_structure_step('groups', 'groups.xml'));
+
+        // Annotate all the user files (conditionally) (private files, and profile)
+        // Because each user has its own context, we need a separate/specialised step here
+        // This step also ensures that the contexts for all the users exist, so next
+        // step can be safely executed (join between users and contexts)
+        // Not executed if backup is without users of anonymized
+        if ($this->get_setting_value('users') && !$this->get_setting_value('anonymize')) {
+            $this->add_step(new backup_annotate_all_user_files('user_files'));
+        }
+
+        // Generate the users file (conditonally) with the final annotated users
+        // including custom profile fields, preferences, tags, role assignments and
+        // overrides
+        if ($this->get_setting_value('users')) {
+            $this->add_step(new backup_users_structure_step('users', 'users.xml'));
+        }
+
+        // Generate the top roles file with all the final annotated roles
+        // that have been detected along the whole process. It's just
+        // the list of role definitions (no assignments nor permissions)
+        $this->add_step(new backup_final_roles_structure_step('roleslist', 'roles.xml'));
+
+        // Generate the scales file with all the annotated scales
+        $this->add_step(new backup_final_scales_structure_step('scaleslist', 'scales.xml'));
+
+        // Generate the outcomes file with all the annotated outcomes
+        $this->add_step(new backup_final_outcomes_structure_step('outcomeslist', 'outcomes.xml'));
+
+        // Migrate the pending annotations to final (prev steps may have added some files)
+        // This must be executed before backup files
+        $this->add_step(new move_inforef_annotations_to_final('migrate_inforef'));
+
+        // Generate the files.xml file with all the (final) annotated files. At the same
+        // time copy all the files from moodle storage to backup storage (uses custom
+        // backup_nested_element for that)
+        $this->add_step(new backup_final_files_structure_step('fileslist', 'files.xml'));
+
+        // Write the main moodle_backup.xml file, with all the information related
+        // to the backup, settings, license, versions and other useful information
+        $this->add_step(new backup_main_structure_step('mainfile', 'moodle_backup.xml'));
+
+        $this->built = true;
+    }
+
+// Protected API starts here
+
+    /**
+     * Define the common setting that any backup type will have
+     */
+    protected function define_settings() {
+        // This task has not settings (could have them, like destination or so in the future, let's see)
+    }
+}
diff --git a/backup/moodle2/backup_plan_builder.class.php b/backup/moodle2/backup_plan_builder.class.php
new file mode 100644 (file)
index 0000000..2542b1a
--- /dev/null
@@ -0,0 +1,166 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/backup/moodle2/backup_root_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_activity_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_section_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_course_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_final_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_block_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_default_block_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_xml_transformer.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_settingslib.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_stepslib.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_custom_fields.php');
+
+// Load all the activity tasks for moodle2 format
+$mods = get_plugin_list('mod');
+foreach ($mods as $mod => $moddir) {
+    if (plugin_supports('mod', $mod, FEATURE_BACKUP_MOODLE2)) {
+        require_once($moddir . '/backup/moodle2/backup_' . $mod . '_activity_task.class.php');
+    }
+}
+
+// Load all the block tasks for moodle2 format
+$blocks = get_plugin_list('block');
+foreach ($blocks as $block => $blockdir) {
+    $taskpath = $blockdir . '/backup/moodle2/backup_' . $block . '_block_task.class.php';
+    if (file_exists($taskpath)) {
+        require_once($taskpath);
+    }
+}
+
+/**
+ * Abstract class defining the static method in charge of building the whole
+ * backup plan, based in @backup_controller preferences.
+ *
+ * TODO: Finish phpdocs
+ */
+abstract class backup_plan_builder {
+
+    /**
+     * Dispatches, based on type to specialised builders
+     */
+    static public function build_plan($controller) {
+
+        $plan = $controller->get_plan();
+
+        // Add the root task, responsible for storing global settings
+        // and some init tasks
+        $plan->add_task(new backup_root_task('root_task'));
+
+        switch ($controller->get_type()) {
+            case backup::TYPE_1ACTIVITY:
+                self::build_activity_plan($controller, $controller->get_id());
+                break;
+            case backup::TYPE_1SECTION:
+                self::build_section_plan($controller, $controller->get_id());
+                break;
+            case backup::TYPE_1COURSE:
+                self::build_course_plan($controller, $controller->get_id());
+                break;
+        }
+
+        // Add the final task, responsible for outputing
+        // all the global xml files (groups, users,
+        // gradebook, questions, roles, files...) and
+        // the main moodle_backup.xml file
+        // and perform other various final actions.
+        $plan->add_task(new backup_final_task('final_task'));
+    }
+
+
+    /**
+     * Return one array of supported backup types
+     */
+    static public function supported_backup_types() {
+        return array(backup::TYPE_1COURSE, backup::TYPE_1SECTION, backup::TYPE_1ACTIVITY);
+    }
+
+// Protected API starts here
+
+    /**
+     * Build one 1-activity backup
+     */
+    static protected function build_activity_plan($controller, $id) {
+
+        $plan = $controller->get_plan();
+
+        // Add the activity task, responsible for outputing
+        // all the module related information
+        $plan->add_task(backup_factory::get_backup_activity_task($controller->get_format(), $id));
+
+        // For the given activity, add as many block tasks as necessary
+        $blockids = backup_plan_dbops::get_blockids_from_moduleid($id);
+        foreach ($blockids as $blockid) {
+            $plan->add_task(backup_factory::get_backup_block_task($controller->get_format(), $blockid, $id));
+        }
+    }
+
+    /**
+     * Build one 1-section backup
+     */
+    static protected function build_section_plan($controller, $id) {
+
+        $plan = $controller->get_plan();
+
+        // Add the section task, responsible for outputing
+        // all the section related information
+        $plan->add_task(backup_factory::get_backup_section_task($controller->get_format(), $id));
+
+        // For the given section, add as many activity tasks as necessary
+        $coursemodules = backup_plan_dbops::get_modules_from_sectionid($id);
+        foreach ($coursemodules as $coursemodule) {
+            if (plugin_supports('mod', $coursemodule->modname, $controller->get_format())) { // Check we support the format
+                self::build_activity_plan($controller, $coursemodule->id);
+            } else {
+                // TODO: Debug information about module not supported
+            }
+        }
+    }
+
+    /**
+     * Build one 1-course backup
+     */
+    static protected function build_course_plan($controller, $id) {
+
+        $plan = $controller->get_plan();
+
+        // Add the course task, responsible for outputing
+        // all the course related information
+        $plan->add_task(backup_factory::get_backup_course_task($controller->get_format(), $id));
+
+        // For the given course, add as many section tasks as necessary
+        $sections = backup_plan_dbops::get_sections_from_courseid($id);
+        foreach ($sections as $section) {
+            self::build_section_plan($controller, $section);
+        }
+
+        // For the given course, add as many block tasks as necessary
+        $blockids = backup_plan_dbops::get_blockids_from_courseid($id);
+        foreach ($blockids as $blockid) {
+            $plan->add_task(backup_factory::get_backup_block_task($controller->get_format(), $blockid));
+        }
+    }
+}
diff --git a/backup/moodle2/backup_root_task.class.php b/backup/moodle2/backup_root_task.class.php
new file mode 100644 (file)
index 0000000..6cd5a7b
--- /dev/null
@@ -0,0 +1,99 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Start task that provides all the settings common to all backups and some initializaton steps
+ *
+ * TODO: Finish phpdocs
+ */
+class backup_root_task extends backup_task {
+
+    /**
+     * Create all the steps that will be part of this task
+     */
+    public function build() {
+
+        // Add all the steps needed to prepare any moodle2 backup to work
+        $this->add_step(new create_and_clean_temp_stuff('create_and_clean_temp_stuff'));
+
+        $this->built = true;
+    }
+
+// Protected API starts here
+
+    /**
+     * Define the common setting that any backup type will have
+     */
+    protected function define_settings() {
+
+        // Define filename setting
+        $this->add_setting(new backup_filename_setting('filename', base_setting::IS_FILENAME, 'backup.zip'));
+
+        // Define users setting (keeping it on hand to define dependencies)
+        $users = new backup_users_setting('users', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($users);
+
+        // Define anonymize (dependent of users)
+        $anonymize = new backup_anonymize_setting('anonymize', base_setting::IS_BOOLEAN, false);
+        $this->add_setting($anonymize);
+        $users->add_dependency($anonymize);
+
+        // Define role_assignments (dependent of users)
+        $roleassignments = new backup_role_assignments_setting('role_assignments', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($roleassignments);
+        $users->add_dependency($roleassignments);
+
+        // Define user_files (dependent of users)
+        $userfiles = new backup_user_files_setting('user_files', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($userfiles);
+        $users->add_dependency($userfiles);
+
+        // Define blocks
+        $blocks = new backup_generic_setting('blocks', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($blocks);
+
+        // Define filters
+        $filters = new backup_generic_setting('filters', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($filters);
+
+        // Define comments (dependent of users)
+        $comments = new backup_comments_setting('comments', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($comments);
+        $users->add_dependency($comments);
+
+        // Define completion (dependent of users)
+        $completion = new backup_userscompletion_setting('userscompletion', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($completion);
+        $users->add_dependency($completion);
+
+        // Define logs (dependent of users)
+        $logs = new backup_logs_setting('logs', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($logs);
+        $users->add_dependency($logs);
+
+        // Define grade_histories
+        $gradehistories = new backup_generic_setting('grade_histories', base_setting::IS_BOOLEAN, true);
+        $this->add_setting($gradehistories);
+    }
+}
diff --git a/backup/moodle2/backup_section_task.class.php b/backup/moodle2/backup_section_task.class.php
new file mode 100644 (file)
index 0000000..6ec9e85
--- /dev/null
@@ -0,0 +1,102 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * section task that provides all the properties and common steps to be performed
+ * when one section is being backup
+ *
+ * TODO: Finish phpdocs
+ */
+class backup_section_task extends backup_task {
+
+    protected $sectionid;
+
+    /**
+     * Constructor - instantiates one object of this class
+     */
+    public function __construct($name, $sectionid, $plan = null) {
+        global $DB;
+
+        // Check section exists
+        if (!$section = $DB->get_record('course_sections', array('id' => $sectionid))) {
+            throw backup_task_exception('section_task_section_not_found', $sectionid);
+        }
+
+        $this->sectionid  = $sectionid;
+
+        parent::__construct($name, $plan);
+    }
+
+    public function get_sectionid() {
+        return $this->sectionid;
+    }
+
+    /**
+     * Section tasks have their own directory to write files
+     */
+    public function get_taskbasepath() {
+
+        return $this->get_basepath() . '/sections/section_' . $this->sectionid;
+    }
+
+    /**
+     * Create all the steps that will be part of this task
+     */
+    public function build() {
+
+        // Set the backup::VAR_CONTEXTID setting to course context as far as next steps require that
+        $coursectxid = get_context_instance(CONTEXT_COURSE, $this->get_courseid())->id;
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_CONTEXTID, base_setting::IS_INTEGER, $coursectxid));
+
+        // Add some extra settings that related processors are going to need
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_SECTIONID, base_setting::IS_INTEGER, $this->sectionid));
+        $this->add_setting(new backup_activity_generic_setting(backup::VAR_COURSEID, base_setting::IS_INTEGER, $this->get_courseid()));
+
+        // Create the section directory
+        $this->add_step(new create_taskbasepath_directory('create_section_directory'));
+
+        // Create the section.xml common file (course_sections)
+        $this->add_step(new backup_section_structure_step('section_commons', 'section.xml'));
+
+        // Generate the inforef file (must be after ALL steps gathering annotations of ANY type)
+        $this->add_step(new backup_inforef_structure_step('section_inforef', 'inforef.xml'));
+
+        // Migrate the already exported inforef entries to final ones
+        $this->add_step(new move_inforef_annotations_to_final('migrate_inforef'));
+
+        // At the end, mark it as built
+        $this->built = true;
+    }
+
+// Protected API starts here
+
+    /**
+     * Define the common setting that any backup section will have
+     */
+    protected function define_settings() {
+
+        // Nothing to add, sections doesn't have common settings (for now)
+
+    }
+}
diff --git a/backup/moodle2/backup_settingslib.php b/backup/moodle2/backup_settingslib.php
new file mode 100644 (file)
index 0000000..20dc0cd
--- /dev/null
@@ -0,0 +1,167 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// Root backup settings
+
+/**
+ * root generic setting to store different things without dependencies
+ */
+class backup_generic_setting extends root_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // Nothing to do, no dependencies
+    }
+}
+
+/**
+ * root setting to handle backup file names (no dependencies nor anything else)
+ */
+class backup_filename_setting extends backup_generic_setting {
+}
+
+/**
+ * root setting to control if backup will include user information
+ * A lot of other settings are dependant of this (module's user info,
+ * grades user info, messages, blogs...
+ */
+class backup_users_setting extends backup_generic_setting {
+}
+
+/**
+ * root setting to control if backup will generate anonymized
+ * user info or no, depends of @backup_users_setting so only is
+ * availabe if the former is enabled (apart from security
+ * that can change it
+ */
+class backup_anonymize_setting extends root_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // If change detected in backup_users_setting, proceed
+        if ($setting instanceof backup_users_setting) {
+            switch ($ctype) {
+                case self::CHANGED_VALUE: // backup_users = false, this too, and locked
+                    if (!$setting->get_value()) {
+                        $this->set_value(false);
+                        $this->set_status(self::LOCKED_BY_HIERARCHY);
+                    }
+                    break;
+                case self::CHANGED_VISIBILITY: // backup_users not visible, this too
+                    if (!$setting->get_visibility()) {
+                        $this->set_visibility(false);
+                    }
+                    break;
+                case self::CHANGED_STATUS: // backup_users unlocked, this too
+                    if ($setting->get_status() == self::NOT_LOCKED) {
+                        $this->set_status(self::NOT_LOCKED);
+                    }
+                    break;
+            }
+        }
+    }
+}
+
+/**
+ * root setting to control if backup will include
+ * user files or no (images, local storage), depends of @backup_users_setting
+ * exactly in the same way than @backup_anonymize_setting so we extend from it
+ */
+class backup_user_files_setting extends backup_anonymize_setting {
+    // Nothing to do. All the logic is in backup_anonymize_setting
+}
+
+/**
+ * root setting to control if backup will include
+ * role assignments or no (any level), depends of @backup_users_setting
+ * exactly in the same way than @backup_anonymize_setting so we extend from it
+ */
+class backup_role_assignments_setting extends backup_anonymize_setting {
+    // Nothing to do. All the logic is in backup_anonymize_setting
+}
+
+/**
+ * root setting to control if backup will include
+ * logs or no (any level), depends of @backup_users_setting
+ * exactly in the same way than @backup_anonymize_setting so we extend from it
+ */
+class backup_logs_setting extends backup_anonymize_setting {
+    // Nothing to do. All the logic is in backup_anonymize_setting
+}
+
+/**
+ * root setting to control if backup will include
+ * comments or no (any level), depends of @backup_users_setting
+ * exactly in the same way than @backup_anonymize_setting so we extend from it
+ */
+class backup_comments_setting extends backup_anonymize_setting {
+    // Nothing to do. All the logic is in backup_anonymize_setting
+}
+
+/**
+ * root setting to control if backup will include
+ * users completion data or no (any level), depends of @backup_users_setting
+ * exactly in the same way than @backup_anonymize_setting so we extend from it
+ */
+class backup_userscompletion_setting extends backup_anonymize_setting {
+    // Nothing to do. All the logic is in backup_anonymize_setting
+}
+
+
+// Activity backup settings
+
+/**
+ * generic activity setting to pass various settings between tasks and steps
+ */
+class backup_activity_generic_setting extends activity_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // Nothing to do, no dependencies
+    }
+}
+
+/**
+ * activity backup setting to control if activity will include
+ * user information or no, depends of @backup_users_setting
+ */
+class backup_activity_userinfo_setting extends activity_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // If change detected in backup_users_setting, proceed
+        if ($setting instanceof backup_users_setting) {
+            switch ($ctype) {
+                case self::CHANGED_VALUE: // backup_users = false, this too, and locked
+                    if (!$setting->get_value()) {
+                        $this->set_value(false);
+                        $this->set_status(self::LOCKED_BY_HIERARCHY);
+                    }
+                    break;
+                case self::CHANGED_VISIBILITY: // backup_users not visible, this too
+                    if (!$setting->get_visibility()) {
+                        $this->set_visibility(false);
+                    }
+                    break;
+                case self::CHANGED_STATUS: // backup_users unlocked, this too
+                    if ($setting->get_status() == self::NOT_LOCKED) {
+                        $this->set_status(self::NOT_LOCKED);
+                    }
+                    break;
+            }
+        }
+    }
+}
diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php
new file mode 100644 (file)
index 0000000..0092b16
--- /dev/null
@@ -0,0 +1,1227 @@
+<?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/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Define all the backup steps that will be used by common tasks in backup
+ */
+class create_and_clean_temp_stuff extends backup_execution_step {
+
+    protected function define_execution() {
+        backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
+        backup_helper::clear_backup_dir($this->get_backupid());           // Empty temp dir, just in case
+        backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60));    // Delete > 4 hours temp dirs
+        backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
+    }
+}
+
+/**
+ * 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);
+        }
+    }
+}
+
+/**
+ * Abtract tructure 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 {
+
+    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;
+    }
+}
+
+/**
+ * Abtract 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,
+ * acummulating various information about the activity, annotating groupings
+ * and completion/avail conf
+ */
+class backup_module_structure_step extends backup_structure_step {
+
+    protected function define_structure() {
+
+        // Define each element separated
+
+        $module = new backup_nested_element('module', array('id', 'version'), array(
+            'modulename', 'sectionid', 'sectionnumber', 'idnumber',
+            'added', 'score', 'indent', 'visible',
+            'visibleold', 'groupmode', 'groupingid', 'groupmembersonly',
+            'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
+            'availablefrom', 'availableuntil', 'showavailability'));
+
+        $availinfo = new backup_nested_element('availability_info');
+        $availability = new backup_nested_element('availability', array('id'), array(
+            'sourcecmid', 'requiredcompletion', 'gradeitemid', 'grademin', 'grademax'));
+
+        // Define the tree
+        $module->add_child($availinfo);
+        $availinfo->add_child($availability);
+
+        // Set the sources
+
+        $module->set_source_sql('
+            SELECT cm.*, m.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 {course_sections} s ON s.id = cm.section
+             WHERE cm.id = ?', array(backup::VAR_MODID));
+
+        $availability->set_source_table('course_modules_availability', array('coursemoduleid' => backup::VAR_MODID));
+
+        // Define annotations
+        $module->annotate_ids('grouping', 'groupingid');
+
+        // Return the root element ($module)
+        return $module;
+    }
+}
+
+/**
+ * structure step that will genereate 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', 'summary', 'sequence', 'visible'));
+
+        // Define sources
+
+        $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
+
+        // Set annotations
+        $section->annotate_files(array('course_section'), 'id');
+
+        return $section;
+    }
+}
+
+/**
+ * structure step that will generate the course.xml file for the course, including
+ * course category reference, tags, metacourse, 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', 'password',
+            'summary', 'summaryformat', 'format', 'showgrades',
+            'newsitems', 'guest', 'startdate', 'enrolperiod',
+            'numsections', 'marker', 'maxbytes', 'showreports',
+            'visible', 'hiddensections', 'groupmode', 'groupmodeforce',
+            'defaultgroupingid', 'lang', 'theme', 'cost',
+            'currency', 'timecreated', 'timemodified', 'metacourse',
+            'requested', 'restrictmodules', 'expirynotify', 'expirythreshold',
+            'notifystudents', 'enrollable', 'enrolstartdate', 'enrolenddate',
+            'enrol', 'defaultrole', 'enablecompletion'));
+
+        $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'));
+
+        $allowedmodules = new backup_nested_element('allowed_modules');
+
+        $module = new backup_nested_element('module', array('modulename'));
+
+        // Build the tree
+
+        $course->add_child($category);
+
+        $course->add_child($tags);
+        $tags->add_child($tag);
+
+        $course->add_child($allowedmodules);
+        $allowedmodules->add_child($module);
+
+        // Set the sources
+
+        $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid()));
+        $courserec->contextid = $this->task->get_contextid();
+
+        $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(
+                                     $this->is_sqlparam('course'),
+                                     backup::VAR_PARENTID));
+
+        $module->set_source_sql('SELECT m.name AS modulename
+                                   FROM {modules} m
+                                   JOIN {course_allowed_modules} cam ON m.id = cam.module
+                                  WHERE course = ?', array(backup::VAR_COURSEID));
+
+        // Some annotations
+
+        $course->annotate_ids('role', 'defaultrole');
+        $course->annotate_ids('grouping', 'defaultgroupingid');
+
+        $course->annotate_files(array('course_summary', 'course_content'), null);
+
+        // Return root element ($course)
+
+        return $course;
+    }
+}
+
+/**
+ * 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', 'hidden', 'timestart',
+            'timeend', 'timemodified', 'modifierid', 'enrol',
+            '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');
+
+        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));
+
+        // 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));
+
+        // 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(
+            '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 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 {
+
+    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');
+
+        // 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
+        $users = $this->get_setting_value('users');
+
+        // Define each element separated
+
+        $groups = new backup_nested_element('groups');
+
+        $group = new backup_nested_element('group', array('id'), array(
+            'name', 'description', 'descriptionformat', 'enrolmentkey',
+            'picture', 'hidepicture', 'timecreated', 'timemodified'));
+
+        $members = new backup_nested_element('group_members');
+
+        $member = new backup_nested_element('group_member', array('id'), array(
+            'userid', 'timeadded'));
+
+        $groupings = new backup_nested_element('groupings');
+
+        $grouping = new backup_nested_element('grouping', 'id', array(
+            'name', '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
+
+        $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));
+
+        // This only happens if we are including users
+        if ($users) {
+            $member->set_source_table('groups_members', array('groupid' => backup::VAR_PARENTID));
+        }
+
+        $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));
+
+        // Define id annotations (as final)
+
+        $member->annotate_ids('userfinal', 'userid');
+
+        // Define file annotations
+
+        // TODO: Change "course_group_image" file area to the one finally used for group images
+        $group->annotate_files(array('course_group_description', 'course_group_image'), '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 separated
+
+        $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', 'secret',
+            'mailformat', 'maildigest', 'maildisplay', 'htmleditor',
+            'ajax', 'autosubscribe', 'trackforums', 'timecreated',
+            'timemodified', 'trustbitmask', 'screenreader');
+
+        // Then, the fields potentially needing anonymization
+        $anonfields = array(
+            'username', 'idnumber', 'firstname', 'lastname',
+            'email', 'emailstop', 'lastip', 'picture',
+            'url', 'description', 'description_format', 'imagealt', 'auth');
+
+        // 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', 'hidden', 'timestart',
+            'timeend', 'timemodified', 'modifierid', 'enrol',
+            '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
+                                 JOIN {context} c ON c.instanceid = u.id
+                            LEFT JOIN {mnet_host} m ON m.id = u.mnethostid
+                                WHERE bi.backupid = ?
+                                  AND bi.itemname = ?
+                                  AND c.contextlevel = ?', array(
+                                      $this->is_sqlparam($this->get_backupid()),
+                                      $this->is_sqlparam('userfinal'),
+                                      $this->is_sqlparam(CONTEXT_USER)));
+
+        // 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(
+                                         $this->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 (intance 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', 'version'), array(
+            'blockname', 'showinsubcontexts', 'pagetypepattern', 'subpagepattern',
+            'defaultregion', 'defaultweight', 'configdata'));
+
+        $positions = new backup_nested_element('block_positions', null, array(
+            'contextid', 'pagetype', 'subpage', 'visible',
+            'region', 'weight'));
+
+        // Build the tree
+
+        $block->add_child($positions);
+
+        // 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)unserialize(base64_decode($blockrec->configdata));
+            foreach ($configdata as $attribute => $value) {
+                if (in_array($attribute, $attrstotransform)) {
+                    $configdata[$attribute] = $this->contenttransformer->process($value);
+                }
+            }
+            $blockrec->configdata = base64_encode(serialize((object)$configdata));
+        }
+        // Get the version of the block
+        $blockrec->version = $DB->get_field('block', 'version', array('name' => $this->task->get_blockname()));
+
+        // Define sources
+
+        $block->set_source_array(array($blockrec));
+
+        $positions->set_source_table('block_positions', array('blockinstanceid' => backup::VAR_PARENTID));
+
+        // Return the root element (block)
+        return $block;
+    }
+}
+
+/**
+ * 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.
+
+        // Return the root element (logs)
+
+        return $logs;
+    }
+}
+
+/**
+ * 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. NOTE: Important to keep this
+        // list 100% sync with the one in next step! Until we get better place for it (backup:CONST)
+        $items = array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item');
+
+        // 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('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, $this->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. NOTE: Important to keep this
+        // list 100% sync with the one in prev step! Until we get better place for it (backup:CONST)
+        $items = array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item');
+        foreach ($items as $itemname) {
+            // Delegate to dbops
+            backup_structure_dbops::move_annotations_to_final($this->get_backupid(), $itemname);
+        }
+    }
+}
+
+/**
+ * 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', 'filearea', 'itemid',
+            'filepath', 'filename', 'userid', 'filesize',
+            'mimetype', 'status', 'timecreated', 'timemodified',
+            'source', 'author', 'license'));
+
+        // Build the tree
+
+        $files->add_child($file);
+
+        // Define sources
+
+        $file->set_source_sql("SELECT f.*
+                                 FROM {files} f
+                                 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['original_wwwroot']=$CFG->wwwroot;
+        $info['original_site_identifier'] = get_site_identifier();
+        $info['original_course_id'] = $this->get_courseid();
+
+        // Get more information from controller
+        list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($this->get_backupid());
+
+        // 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', 'original_wwwroot',
+            'original_site_identifier', 'original_course_id'));
+
+        $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', '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;
+    }
+
+}
+
+/**
+ * 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 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 user annotations for the already
+ * annottated (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
+        // TODO: Change "user_image" file area to the one finally used for user images
+        $fileareas = array(
+            'user_private', 'user_profile', 'user_image');
+
+        // Fetch all annotated (final) users
+        $rs = $DB->get_recordset('backup_ids_temp', array(
+            'backupid' => $this->get_backupid(), 'itemname' => 'userfinal'));
+        foreach ($rs as $record) {
+            $userid = $record->itemid;
+            $userctxid = get_context_instance(CONTEXT_USER, $userid)->id;
+            // Proceed with every user filearea
+            foreach ($fileareas as $filearea) {
+                // We don't need to specify itemid ($userid - 4th param) as far as by
+                // context we can get all the associated files. See MDL-22092
+                backup_structure_dbops::annotate_files($this->get_backupid(), $userctxid, $filearea, null);
+            }
+        }
+        $rs->close();
+    }
+}
+
+/**
+ * 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 {
+
+    protected function define_structure() {
+
+        // 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', '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'));
+
+        $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));
+        }
+
+        $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;
+    }
+}
diff --git a/backup/moodle2/backup_xml_transformer.class.php b/backup/moodle2/backup_xml_transformer.class.php
new file mode 100644 (file)
index 0000000..081e53d
--- /dev/null
@@ -0,0 +1,138 @@
+<?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/>.
+
+/**
+ * @package    moodlecore
+ * @subpackage backup-moodle2
+ * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Class implementing the @xml_contenttrasnformed logic to be applied in moodle2 backups
+ *
+ * TODO: Finish phpdocs
+ */
+class backup_xml_transformer extends xml_contenttransformer {
+
+    private $absolute_links_encoders; // array of static methods to be called in order to
+                                      // perform the encoding of absolute links to all the
+                                      // contents sent to xml
+    private $courseid;                // courseid this content belongs to
+    private $unicoderegexp;           // to know if the site supports unicode regexp
+
+    public function __construct($courseid) {
+        $this->absolute_links_encoders = array();
+        $this->courseid = $courseid;
+        // Check if we support unicode modifiers in regular expressions
+        $this->unicoderegexp = @preg_match('/\pL/u', 'a'); // This will fail silenty, returning false,
+                                                           // if regexp libraries don't support unicode
+        // Register all the available content link encoders
+        $this->absolute_links_encoders = $this->register_link_encoders();
+    }
+
+    public function process($content) {
+
+        if (is_null($content)) {  // Some cases we know we can skip complete processing
+            return '$@NULL@$';
+        } else if ($content === '') {
+            return '';
+        } else if (is_numeric($content)) {
+            return $content;
+        } else if (strlen($content) < 36) { // Impossible to have one link in 36cc
+            return $content;                // (http://10.0.0.1/mod/url/view.php?id=)
+        }
+
+        $content = $this->process_filephp_links($content); // Replace all calls to file.php by $@FILEPHP@$ in a normalised way
+        $content = $this->encode_absolute_links($content); // Pass the content against all the found encoders
+
+        return $content;
+    }
+
+    private function process_filephp_links($content) {
+        global $CFG;
+
+        //First, we check for every call to file.php inside the course
+        $search = array($CFG->wwwroot.'/file.php/' . $this->courseid,
+                        $CFG->wwwroot.'/file.php?file=/' . $this->courseid,
+                        $CFG->wwwroot.'/file.php?file=%2f' . $this->courseid,
+                        $CFG->wwwroot.'/file.php?file=%2F' . $this->courseid);
+        $replace = array('$@FILEPHP@$', '$@FILEPHP@$', '$@FILEPHP@$', '$@FILEPHP@$');
+        $content = str_replace($search, $replace, $content);
+
+        // Now we look for any '$@FILEPHP@$' URLs, replacing:
+        //     - slashes and %2F by $@SLASH@$
+        //     - &forcedownload=1 &amp;forcedownload=1 and ?forcedownload=1 by $@FORCEDOWNLOAD@$
+        // This way, backup contents will be neutral and independent of slasharguments configuration. MDL-18799
+        // Based in $this->unicoderegexp, decide the regular expression to use
+        if ($this->unicoderegexp) { //We can use unicode modifiers
+            $search = '/(\$@FILEPHP@\$)((?:(?:\/|%2f|%2F))(?:(?:\([-;:@#&=\pL0-9\$~_.+!*\',]*?\))|[-;:@#&=\pL0-9\$~_.+!*\',]|%[a-fA-F0-9]{2}|\/)*)?(\?(?:(?:(?:\([-;:@#&=\pL0-9\$~_.+!*\',]*?\))|[-;:@#&=?\pL0-9\$~_.+!*\',]|%[a-fA-F0-9]{2}|\/)*))?(?<![,.;])/u';
+        } else { //We cannot ue unicode modifiers
+            $search = '/(\$@FILEPHP@\$)((?:(?:\/|%2f|%2F))(?:(?:\([-;:@#&=a-zA-Z0-9\$~_.+!*\',]*?\))|[-;:@#&=a-zA-Z0-9\$~_.+!*\',]|%[a-fA-F0-9]{2}|\/)*)?(\?(?:(?:(?:\([-;:@#&=a-zA-Z0-9\$~_.+!*\',]*?\))|[-;:@#&=?a-zA-Z0-9\$~_.+!*\',]|%[a-fA-F0-9]{2}|\/)*))?(?<![,.;])/';
+        }
+        $content = preg_replace_callback($search, array('backup_xml_transformer', 'process_filephp_uses'), $content);
+
+        return $content;
+    }
+
+    private function encode_absolute_links($content) {
+        foreach ($this->absolute_links_encoders as $classname => $methodname) {
+            $content = call_user_func(array($classname, $methodname), $content);
+        }
+        return $content;
+    }
+
+    static private function process_filephp_uses($matches) {
+
+        // Replace slashes (plain and encoded) and forcedownload=1 parameter
+        $search = array('/', '%2f', '%2F', '?forcedownload=1', '&forcedownload=1', '&amp;forcedownload=1');
+        $replace = array('$@SLASH@$', '$@SLASH@$', '$@SLASH@$', '$@FORCEDOWNLOAD@$', '$@FORCEDOWNLOAD@$', '$@FORCEDOWNLOAD@$');
+
+        $result = $matches[1] . (isset($matches[2]) ? str_replace($search, $replace, $matches[2]) : '') . (isset($matches[3]) ? str_replace($search, $replace, $matches[3]) : '');
+
+        return $result;
+    }
+
+    private function register_link_encoders() {
+        $encoders = array();
+
+        // First of all, add the module ones. Each module supporting moodle2 backups MUST have it
+        $mods = get_plugin_list('mod');
+        foreach ($mods as $mod => $moddir) {
+            if (plugin_supports('mod', $mod, FEATURE_BACKUP_MOODLE2)) {
+                $encoders['backup_' . $mod . '_activity_task'] = 'encode_content_links';
+            }
+        }
+
+        // Add the block encodes
+        $blocks = get_plugin_list('block');
+        foreach ($blocks as $block => $blockdir) {
+            if (class_exists('backup_' . $block . '_block_task')) {
+                $encoders['backup_' . $block . '_block_task'] = 'encode_content_links';
+            }
+        }
+
+        // Add the course format encodes
+        // TODO: Same than blocks, need to know how courseformats are going to handle backup
+        //       (1.9 was based in backuplib function, see code)
+
+        // Add local encodes
+        // TODO: Any interest? 1.9 never had that.
+
+        return $encoders;
+    }
+}