MDL-22138 backup - I know smaller cathedrals than this, yay quizzes!
authorEloy Lafuente <stronk7@moodle.org>
Sun, 24 Oct 2010 10:43:42 +0000 (10:43 +0000)
committerEloy Lafuente <stronk7@moodle.org>
Sun, 24 Oct 2010 10:43:42 +0000 (10:43 +0000)
54 files changed:
backup/moodle2/backup_final_task.class.php
backup/moodle2/backup_qtype_plugin.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_final_task.class.php
backup/moodle2/restore_plan_builder.class.php
backup/moodle2/restore_plugin.class.php [new file with mode: 0644]
backup/moodle2/restore_qtype_plugin.class.php [new file with mode: 0644]
backup/moodle2/restore_root_task.class.php
backup/moodle2/restore_stepslib.php
backup/moodle2/restore_subplugin.class.php
backup/restorelib.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/restore_prechecks_helper.class.php
backup/util/helper/restore_questions_parser_processor.class.php [new file with mode: 0644]
backup/util/includes/restore_includes.php
backup/util/plan/backup_structure_step.class.php
backup/util/plan/restore_structure_step.class.php
lang/en/backup.php
mod/forum/backup/moodle2/backup_forum_stepslib.php
mod/forum/backup/moodle2/restore_forum_stepslib.php
mod/quiz/backup/moodle2/backup_quiz_stepslib.php
mod/quiz/backup/moodle2/restore_quiz_activity_task.class.php [new file with mode: 0644]
mod/quiz/backup/moodle2/restore_quiz_stepslib.php [new file with mode: 0644]
mod/quiz/restorelib.php
mod/quiz/restorelibpre15.php [deleted file]
question/restorelib.php
question/type/calculated/backup/moodle2/backup_qtype_calculated_plugin.class.php
question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php [new file with mode: 0644]
question/type/calculated/questiontype.php
question/type/calculatedmulti/backup/moodle2/backup_qtype_calculatedmulti_plugin.class.php
question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php [new file with mode: 0644]
question/type/calculatedsimple/backup/moodle2/backup_qtype_calculatedsimple_plugin.class.php
question/type/calculatedsimple/backup/moodle2/restore_qtype_calculatedsimple_plugin.class.php [new file with mode: 0644]
question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php [new file with mode: 0644]
question/type/match/backup/moodle2/backup_qtype_match_plugin.class.php
question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php [new file with mode: 0644]
question/type/match/questiontype.php
question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php [new file with mode: 0644]
question/type/multianswer/questiontype.php
question/type/multichoice/backup/moodle2/backup_qtype_multichoice_plugin.class.php
question/type/multichoice/backup/moodle2/restore_qtype_multichoice_plugin.class.php [new file with mode: 0644]
question/type/multichoice/questiontype.php
question/type/numerical/backup/moodle2/backup_qtype_numerical_plugin.class.php
question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php [new file with mode: 0644]
question/type/numerical/questiontype.php
question/type/questiontype.php
question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php [new file with mode: 0644]
question/type/random/questiontype.php
question/type/randomsamatch/backup/moodle2/restore_qtype_randomsamatch_plugin.class.php [new file with mode: 0644]
question/type/randomsamatch/questiontype.php
question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php [new file with mode: 0644]
question/type/shortanswer/questiontype.php
question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php [new file with mode: 0644]
question/type/truefalse/questiontype.php

index 8305886..d39a20e 100644 (file)
@@ -48,6 +48,9 @@ class backup_final_task extends backup_task {
         // including membership based on setting
         $this->add_step(new backup_groups_structure_step('groups', 'groups.xml'));
 
+        // Generate the questions file with the final annotated question_categories
+        $this->add_step(new backup_questions_structure_step('questions', 'questions.xml'));
+
         // Annotate all the question files for the already annotated question
         // categories (this is performed here and not in the structure step because
         // it involves multiple contexts and as far as we are always backup-ing
@@ -55,9 +58,6 @@ class backup_final_task extends backup_task {
         // done in a single pass
         $this->add_step(new backup_annotate_all_question_files('question_files'));
 
-        // Generate the questions file with the final annotated question_categories
-        $this->add_step(new backup_questions_structure_step('questions', 'questions.xml'));
-
         // Annotate all the user files (conditionally) (private, profile and icon files)
         // 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
index e44cf39..c0d4843 100644 (file)
@@ -143,7 +143,7 @@ abstract class backup_qtype_plugin extends backup_plugin {
         $items->add_child($item);
 
         // Set the sources
-        $definition->set_source_sql('SELECT *
+        $definition->set_source_sql('SELECT qdd.*
                                        FROM {question_dataset_definitions} qdd
                                        JOIN {question_datasets} qd ON qd.datasetdefinition = qdd.id
                                       WHERE qd.question = ?', array(backup::VAR_PARENTID));
@@ -155,4 +155,52 @@ abstract class backup_qtype_plugin extends backup_plugin {
 
         // don't need to annotate ids nor files
     }
+
+    /**
+     * Returns all the components and fileareas used by all the installed qtypes
+     *
+     * The method introspects each qtype, asking it about fileareas used. Then,
+     * one 2-level array is returned. 1st level is the component name (qtype_xxxx)
+     * and 2nd level is one array of filearea => mappings to look
+     *
+     * Note that this function is used both in backup and restore, so it is important
+     * to use the same mapping names (usually, name of the table in singular) always
+     *
+     * TODO: Surely this can be promoted to backup_plugin easily and make it to
+     * work for ANY plugin, not only qtypes (but we don't need it for now)
+     */
+    public static function get_components_and_fileareas($filter = null) {
+        $components = array();
+        // Get all the plugins of this type
+        $qtypes = get_plugin_list('qtype');
+        foreach ($qtypes as $name => $path) {
+            // Apply filter if specified
+            if (!is_null($filter) && $filter != $name) {
+                continue;
+            }
+            // Calculate the componentname
+            $componentname = 'qtype_' . $name;
+            // Get the plugin fileareas (all them MUST belong to the same component)
+            $classname = 'backup_qtype_' . $name . '_plugin';
+            if (class_exists($classname)) {
+                $elements = call_user_func(array($classname, 'get_qtype_fileareas'));
+                if ($elements) {
+                    // If there are elements, add them to $components
+                    $components[$componentname] = $elements;
+                }
+            }
+        }
+        return $components;
+    }
+
+    /**
+     * Returns one array with filearea => mappingname elements for the qtype
+     *
+     * Used by {@link get_components_and_fileareas} to know about all the qtype
+     * files to be processed both in backup and restore.
+     */
+    public static function get_qtype_fileareas() {
+        // By default, return empty array, only qtypes having own fileareas will override this
+        return array();
+    }
 }
index e0f6c9f..d03ae24 100644 (file)
@@ -160,7 +160,7 @@ abstract class backup_activity_structure_step extends backup_structure_step {
 
 /**
  * Abstract structure step, to be used by all the activities using core questions stuff
- * (namelu quiz module), supporting question plugins, states and sessions
+ * (namely quiz module), supporting question plugins, states and sessions
  */
 abstract class backup_questions_activity_structure_step extends backup_activity_structure_step {
 
@@ -1562,10 +1562,20 @@ class backup_annotate_all_question_files extends backup_execution_step {
                                         JOIN {backup_ids_temp} bi ON bi.itemid = qc.id
                                        WHERE bi.backupid = ?
                                          AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid()));
+        // To know about qtype specific components/fileareas
+        $components = backup_qtype_plugin::get_components_and_fileareas();
+        // Let's loop
         foreach($rs as $record) {
             // We don't need to specify filearea nor itemid as far as by
             // component and context it's enough to annotate the whole bank files
+            // This backups "questiontext", "generalfeedback" and "answerfeedback" fileareas (all them
+            // belonging to the "question" component
             backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', null, null);
+            // Again, it is enough to pick files only by context and component
+            // Do it for qtype specific components
+            foreach ($components as $component => $fileareas) {
+                backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, $component, null, null);
+            }
         }
         $rs->close();
     }
@@ -1620,6 +1630,7 @@ class backup_questions_structure_step extends backup_structure_step {
         $question->set_source_table('question', array('category' => backup::VAR_PARENTID));
 
         // don't need to annotate ids nor files
+        // (already done by {@link backup_annotate_all_question_files}
 
         return $qcategories;
     }
index 9febd72..cd82ab8 100644 (file)
@@ -35,6 +35,14 @@ class restore_final_task extends restore_task {
      */
     public function build() {
 
+        // Move all the CONTEXT_MODULE question qcats to their
+        // final (newly created) module context
+        $this->add_step(new restore_move_module_questions_categories('move_module_question_categories'));
+
+        // Create all the question files now that every question is in place
+        // and every category has its final contextid associated
+        $this->add_step(new restore_create_question_files('create_question_files'));
+
         // Review all the block_position records in backup_ids in order
         // match them now that all the contexts are created populating DB
         // as needed. Only if we are restoring blocks.
index 3f09689..46ea999 100644 (file)
@@ -31,6 +31,10 @@ require_once($CFG->dirroot . '/backup/moodle2/restore_activity_task.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_final_task.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_block_task.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_default_block_task.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/restore_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/restore_qtype_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_subplugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_settingslib.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_stepslib.php');
diff --git a/backup/moodle2/restore_plugin.class.php b/backup/moodle2/restore_plugin.class.php
new file mode 100644 (file)
index 0000000..bb0898d
--- /dev/null
@@ -0,0 +1,172 @@
+<?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 plugins support for moodle2 restore
+ *
+ * TODO: Finish phpdocs
+ * TODO: Add support for declaring decode_contents (not decode_rules)
+ */
+abstract class restore_plugin {
+
+    protected $plugintype;
+    protected $pluginname;
+    protected $connectionpoint;
+    protected $step;
+    protected $task;
+
+    public function __construct($plugintype, $pluginname, $step) {
+        $this->plugintype = $plugintype;
+        $this->pluginname = $pluginname;
+        $this->step          = $step;
+        $this->task          = $step->get_task();
+        $this->connectionpoint = '';
+    }
+
+    public function define_plugin_structure($connectionpoint) {
+        if (!$connectionpoint instanceof restore_path_element) {
+            throw new restore_step_exception('restore_path_element_required', $connectionpoint);
+        }
+
+        $paths = array();
+        $this->connectionpoint = $connectionpoint;
+        $methodname = 'define_' . basename($this->connectionpoint->get_path()) . '_plugin_structure';
+
+        if (method_exists($this, $methodname)) {
+            if ($bluginpaths = $this->$methodname()) {
+                foreach ($bluginpaths as $path) {
+                    $path->set_processing_object($this);
+                    $paths[] = $path;
+                }
+            }
+        }
+        return $paths;
+    }
+
+    /**
+     * after_execute dispatcher for any restore_plugin class
+     *
+     * This method will dispatch execution to the corresponding
+     * after_execute_xxx() method when available, with xxx
+     * being the connection point of the instance, so plugin
+     * classes with multiple connection points will support
+     * multiple after_execute methods, one for each connection point
+     */
+    public function launch_after_execute_methods() {
+        // Check if the after_execute method exists and launch it
+        $afterexecute = 'after_execute_' . basename($this->connectionpoint->get_path());
+        if (method_exists($this, $afterexecute)) {
+            $this->$afterexecute();
+        }
+    }
+
+// Protected API starts here
+
+// restore_step/structure_step/task wrappers
+
+    protected function get_restoreid() {
+        if (is_null($this->task)) {
+            throw new restore_step_exception('not_specified_restore_task');
+        }
+        return $this->task->get_restoreid();
+    }
+
+    /**
+     * To send ids pairs to backup_ids_table and to store them into paths
+     *
+     * This method will send the given itemname and old/new ids to the
+     * backup_ids_temp table, and, at the same time, will save the new id
+     * into the corresponding restore_path_element for easier access
+     * by children. Also will inject the known old context id for the task
+     * in case it's going to be used for restoring files later
+     */
+    protected function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null, $parentid = null) {
+        $this->step->set_mapping($itemname, $oldid, $newid, $restorefiles, $filesctxid, $parentid);
+    }
+
+    /**
+     * Returns the latest (parent) old id mapped by one pathelement
+     */
+    protected function get_old_parentid($itemname) {
+        return $this->step->get_old_parentid($itemname);
+    }
+
+    /**
+     * Returns the latest (parent) new id mapped by one pathelement
+     */
+    protected function get_new_parentid($itemname) {
+        return $this->step->get_new_parentid($itemname);
+    }
+
+    /**
+     * Return the new id of a mapping for the given itemname
+     *
+     */
+    protected function get_mappingid($itemname, $oldid) {
+        return $this->step->get_mappingid($itemname, $oldid);
+    }
+
+    /**
+     * Return the complete mapping from the given itemname, itemid
+     */
+    protected function get_mapping($itemname, $oldid) {
+        return $this->step->get_mapping($itemname, $oldid);
+    }
+
+    /**
+     * Add all the existing file, given their component and filearea and one backup_ids itemname to match with
+     */
+    protected function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) {
+        $this->step->add_related_files($component, $filearea, $mappingitemname, $filesctxid, $olditemid);
+    }
+
+    /**
+     * Apply course startdate offset based in original course startdate and course_offset_startdate setting
+     * Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple
+     * executions in the same request
+     */
+    protected function apply_date_offset($value) {
+        return $this->step->apply_date_offset($value);
+    }
+
+    /**
+     * Simple helper function that returns the name for the restore_path_element
+     * It's not mandatory to use it but recommended ;-)
+     */
+    protected function get_namefor($name = '') {
+        $name = $name !== '' ? '_' . $name : '';
+        return $this->plugintype . '_' . $this->pluginname . $name;
+    }
+
+    /**
+     * Simple helper function that returns the base (prefix) of the path for the restore_path_element
+     * Useful if we used get_recommended_name() in backup. It's not mandatory to use it but recommended ;-)
+     */
+    protected function get_pathfor($path = '') {
+        $path = trim($path, '/') !== '' ? '/' . trim($path, '/') : '';
+        return $this->connectionpoint->get_path() . '/' .
+               'plugin_' . $this->plugintype . '_' .
+               $this->pluginname . '_' . basename($this->connectionpoint->get_path()) . $path;
+    }
+}
diff --git a/backup/moodle2/restore_qtype_plugin.class.php b/backup/moodle2/restore_qtype_plugin.class.php
new file mode 100644 (file)
index 0000000..91cebff
--- /dev/null
@@ -0,0 +1,302 @@
+<?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 extending standard restore_plugin in order to implement some
+ * helper methods related with the questions (qtype plugin)
+ *
+ * TODO: Finish phpdocs
+ */
+abstract class restore_qtype_plugin extends restore_plugin {
+
+    /**
+     * Add to $paths the restore_path_elements needed
+     * to handle question_answers for a given question
+     * Used by various qtypes (calculated, essay, multianswer,
+     * multichoice, numerical, shortanswer, truefalse)
+     */
+    protected function add_question_question_answers(&$paths) {
+        // Check $paths is one array
+        if (!is_array($paths)) {
+            throw new restore_step_exception('paths_must_be_array', $paths);
+        }
+
+        $elename = 'question_answer';
+        $elepath = $this->get_pathfor('/answers/answer'); // we used get_recommended_name() so this works
+        $paths[] = new restore_path_element($elename, $elepath);
+    }
+
+    /**
+     * Add to $paths the restore_path_elements needed
+     * to handle question_numerical_units for a given question
+     * Used by various qtypes (calculated, numerical)
+     */
+    protected function add_question_numerical_units(&$paths) {
+        // Check $paths is one array
+        if (!is_array($paths)) {
+            throw new restore_step_exception('paths_must_be_array', $paths);
+        }
+
+        $elename = 'question_numerical_unit';
+        $elepath = $this->get_pathfor('/numerical_units/numerical_unit'); // we used get_recommended_name() so this works
+        $paths[] = new restore_path_element($elename, $elepath);
+    }
+
+    /**
+     * Add to $paths the restore_path_elements needed
+     * to handle question_numerical_options for a given question
+     * Used by various qtypes (calculated, numerical)
+     */
+    protected function add_question_numerical_options(&$paths) {
+        // Check $paths is one array
+        if (!is_array($paths)) {
+            throw new restore_step_exception('paths_must_be_array', $paths);
+        }
+
+        $elename = 'question_numerical_option';
+        $elepath = $this->get_pathfor('/numerical_options/numerical_option'); // we used get_recommended_name() so this works
+        $paths[] = new restore_path_element($elename, $elepath);
+    }
+
+    /**
+     * Add to $paths the restore_path_elements needed
+     * to handle question_datasets (defs and items) for a given question
+     * Used by various qtypes (calculated, numerical)
+     */
+    protected function add_question_datasets(&$paths) {
+        // Check $paths is one array
+        if (!is_array($paths)) {
+            throw new restore_step_exception('paths_must_be_array', $paths);
+        }
+
+        $elename = 'question_dataset_definition';
+        $elepath = $this->get_pathfor('/dataset_definitions/dataset_definition'); // we used get_recommended_name() so this works
+        $paths[] = new restore_path_element($elename, $elepath);
+
+        $elename = 'question_dataset_item';
+        $elepath = $this->get_pathfor('/dataset_definitions/dataset_definition/dataset_items/dataset_item');
+        $paths[] = new restore_path_element($elename, $elepath);
+    }
+
+    /**
+     * Processes the answer element (question answers). Common for various qtypes.
+     * It handles both creation (if the question is being created) and mapping
+     * (if the question already existed and is being reused)
+     */
+    public function process_question_answer($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Detect if the question is created or mapped
+        $oldquestionid   = $this->get_old_parentid('question');
+        $newquestionid   = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question has been created by restore, we need to create its question_answers too
+        if ($questioncreated) {
+            // Adjust some columns
+            $data->question = $newquestionid;
+            $data->answer = $data->answertext;
+            // Insert record
+            $newitemid = $DB->insert_record('question_answers', $data);
+
+        // The question existed, we need to map the existing question_answers
+        } else {
+            // Look in question_answers by answertext matching
+            $newitemid = $DB->get_field('question_answers', 'id', array('question' => $newquestionid, 'answer' => $data->answertext));
+            // If we haven't found the newitemid, something has gone really wrong, question in DB
+            // is missing answers, exception
+            if (!$newitemid) {
+                $info = new stdClass();
+                $info->filequestionid = $oldquestionid;
+                $info->dbquestionid   = $newquestionid;
+                $info->answer         = $data->answertext;
+                throw restore_step_exception('error_question_answers_missing_in_db', $info);
+            }
+        }
+        // Create mapping (we'll use this intensively when restoring question_states. And also answerfeedback files)
+        $this->set_mapping('question_answer', $oldid, $newitemid);
+    }
+
+    /**
+     * Processes the numerical_unit element (question numerical units). Common for various qtypes.
+     * It handles both creation (if the question is being created) and mapping
+     * (if the question already existed and is being reused)
+     */
+    public function process_question_numerical_unit($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Detect if the question is created or mapped
+        $oldquestionid   = $this->get_old_parentid('question');
+        $newquestionid   = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question has been created by restore, we need to create its question_numerical_units too
+        if ($questioncreated) {
+            // Adjust some columns
+            $data->question = $newquestionid;
+            // Insert record
+            $newitemid = $DB->insert_record('question_numerical_units', $data);
+        }
+    }
+
+    /**
+     * Processes the numerical_option element (question numerical options). Common for various qtypes.
+     * It handles both creation (if the question is being created) and mapping
+     * (if the question already existed and is being reused)
+     */
+    public function process_question_numerical_option($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Detect if the question is created or mapped
+        $oldquestionid   = $this->get_old_parentid('question');
+        $newquestionid   = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question has been created by restore, we need to create its question_numerical_options too
+        if ($questioncreated) {
+            // Adjust some columns
+            $data->question = $newquestionid;
+            // Insert record
+            $newitemid = $DB->insert_record('question_numerical_options', $data);
+            // Create mapping (not needed, no files nor childs nor states here)
+            //$this->set_mapping('question_numerical_option', $oldid, $newitemid);
+        }
+    }
+
+    /**
+     * Processes the dataset_definition element (question dataset definitions). Common for various qtypes.
+     * It handles both creation (if the question is being created) and mapping
+     * (if the question already existed and is being reused)
+     */
+    public function process_question_dataset_definition($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Detect if the question is created or mapped
+        $oldquestionid   = $this->get_old_parentid('question');
+        $newquestionid   = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question is mapped, nothing to do
+        if (!$questioncreated) {
+            return;
+        }
+
+        // Arrived here, let's see if the question_dataset_definition already exists in category or no
+        // (by category, name, type and enough items). Only for "shared" definitions (category != 0).
+        // If exists, reuse it, else, create it as "not shared" (category = 0)
+        $data->category = $this->get_mappingid('question_category', $data->category);
+        // If category is shared, look for definitions
+        $founddefid = null;
+        if ($data->category) {
+            $candidatedefs = $DB->get_records_sql("SELECT id, itemcount
+                                                     FROM {question_dataset_definitions}
+                                                    WHERE category = ?
+                                                      AND name = ?
+                                                      AND type = ?", array($data->category, $data->name, $data->type));
+            foreach ($candidatedefs as $candidatedef) {
+                if ($candidatedef->itemcount >= $data->itemcount) { // Check it has enough items
+                    $founddefid = $candidatedef->id;
+                    break; // end loop, shared definition match found
+                }
+            }
+            // If there were candidates but none fulfilled the itemcount condition, create definition as not shared
+            if ($candidatedefs && !$founddefid) {
+                $data->category = 0;
+            }
+        }
+        // If haven't found any shared definition match, let's create it
+        if (!$founddefid) {
+            $newitemid = $DB->insert_record('question_dataset_definitions', $data);
+            // Set mapping, so dataset items will know if they must be created
+            $this->set_mapping('question_dataset_definition', $oldid, $newitemid);
+
+        // If we have found one shared definition match, use it
+        } else {
+            $newitemid = $founddefid;
+            // Set mapping to 0, so dataset items will know they don't need to be created
+            $this->set_mapping('question_dataset_definition', $oldid, 0);
+        }
+
+        // Arrived here, we have one $newitemid (create or reused). Create the question_datasets record
+        $questiondataset = new stdClass();
+        $questiondataset->question = $newquestionid;
+        $questiondataset->datasetdefinition = $newitemid;
+        $DB->insert_record('question_datasets', $questiondataset);
+    }
+
+    /**
+     * Processes the dataset_item element (question dataset items). Common for various qtypes.
+     * It handles both creation (if the question is being created) and mapping
+     * (if the question already existed and is being reused)
+     */
+    public function process_question_dataset_item($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Detect if the question is created or mapped
+        $oldquestionid   = $this->get_old_parentid('question');
+        $newquestionid   = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question is mapped, nothing to do
+        if (!$questioncreated) {
+            return;
+        }
+
+        // Detect if the question_dataset_definition is being created
+        $newdefinitionid = $this->get_new_parentid('question_dataset_definition');
+
+        // If the definition is reused, nothing to do
+        if (!$newdefinitionid) {
+            return;
+        }
+
+        // let's create the question_dataset_items
+        $data->definition = $newdefinitionid;
+        $data->itemnumber = $data->number;
+        $DB->insert_record('question_dataset_items', $data);
+    }
+
+    /**
+     * Decode one question_states for this qtype (default impl)
+     */
+    public function recode_state_answer($state) {
+        // By default, return answer unmodified, qtypes needing recode will override this
+        return $state->answer;
+    }
+}
index 239d20b..04b7f4b 100644 (file)
@@ -40,6 +40,9 @@ class restore_root_task extends restore_task {
         // If we haven't preloaded information, load all the included inforef records to temp_ids table
         $this->add_step(new restore_load_included_inforef_records('load_inforef_records'));
 
+        // Load all the needed files to temp_ids table
+        $this->add_step(new restore_load_included_files('load_file_records', 'files.xml'));
+
         // If we haven't preloaded information, load all the needed roles to temp_ids_table
         $this->add_step(new restore_load_and_map_roles('load_and_map_roles'));
 
@@ -47,13 +50,10 @@ class restore_root_task extends restore_task {
         $this->add_step(new restore_load_included_users('load_user_records'));
 
         // If we haven't preloaded information and are restoring user info, process all those needed users
-        // creating/mapping them as needed. Any problem here will cause exception as far as prechecks have
+        // marking for create/map them as needed. Any problem here will cause exception as far as prechecks have
         // performed the same process so, it's not possible to have errors here
         $this->add_step(new restore_process_included_users('process_user_records'));
 
-        // Load all the needed files to temp_ids table
-        $this->add_step(new restore_load_included_files('load_file_records', 'files.xml'));
-
         // Unconditionally, create all the needed users calculated in the previous step
         $this->add_step(new restore_create_included_users('create_users'));
 
@@ -66,6 +66,18 @@ class restore_root_task extends restore_task {
         // Unconditionally, load create all the needed outcomes
         $this->add_step(new restore_outcomes_structure_step('create_scales', 'outcomes.xml'));
 
+        // If we haven't preloaded information, load all the needed categories and questions (reduced) to temp_ids_table
+        $this->add_step(new restore_load_categories_and_questions('load_categories_and_questions'));
+
+        // If we haven't preloaded information, process all the loaded categories and questions
+        // marking them for creation/mapping as needed. Any problem here will cause exception
+        // because this same process has been executed and reported by restore prechecks, so
+        // it is not possible to have errors here.
+        $this->add_step(new restore_process_categories_and_questions('process_categories_and_questions'));
+
+        // Unconditionally, create and map all the categories and questions
+        $this->add_step(new restore_create_categories_and_questions('create_categories_and_questions', 'questions.xml'));
+
         // At the end, mark it as built
         $this->built = true;
     }
index 8b5bf18..9b799d2 100644 (file)
@@ -488,10 +488,13 @@ class restore_load_included_files extends restore_structure_step {
 
         // load it if needed:
         //   - it it is one of the annotated inforef files (course/section/activity/block)
-        //   - it is one "user", "group", "grouping" or "grade" component file (that aren't sent to inforef ever)
+        //   - it is one "user", "group", "grouping", "grade", "question" or "qtype_xxxx" component file (that aren't sent to inforef ever)
+        // TODO: qtype_xxx should be replaced by proper backup_qtype_plugin::get_components_and_fileareas() use,
+        //       but then we'll need to change it to load plugins itself (because this is executed too early in restore)
         $isfileref   = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'fileref', $data->id);
         $iscomponent = ($data->component == 'user' || $data->component == 'group' ||
-                        $data->component == 'grouping' || $data->component == 'grade');
+                        $data->component == 'grouping' || $data->component == 'grade' ||
+                        $data->component == 'question' || substr($data->component, 0, 5) == 'qtype');
         if ($isfileref || $iscomponent) {
             restore_dbops::set_backup_files_record($this->get_restoreid(), $data);
         }
@@ -833,6 +836,43 @@ class restore_outcomes_structure_step extends restore_structure_step {
     }
 }
 
+/**
+ * Execution step that, *conditionally* (if there isn't preloaded information
+ * will load all the question categories and questions (header info only)
+ * to backup_temp_ids. They will be stored with "question_category" and
+ * "question" itemnames and with their original contextid and question category
+ * id as paremitemids
+ */
+class restore_load_categories_and_questions extends restore_execution_step {
+
+    protected function define_execution() {
+
+        if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
+            return;
+        }
+        $file = $this->get_basepath() . '/questions.xml';
+        restore_dbops::load_categories_and_questions_to_tempids($this->get_restoreid(), $file);
+    }
+}
+
+/**
+ * Execution step that, *conditionally* (if there isn't preloaded information)
+ * will process all the needed categories and questions
+ * in order to decide and perform any action with them (create / map / error)
+ * Note: Any error will cause exception, as far as this is the same processing
+ * than the one into restore prechecks (that should have stopped process earlier)
+ */
+class restore_process_categories_and_questions extends restore_execution_step {
+
+    protected function define_execution() {
+
+        if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do
+            return;
+        }
+        restore_dbops::process_categories_and_questions($this->get_restoreid(), $this->task->get_courseid(), $this->task->get_userid(), $this->task->is_samesite());
+    }
+}
+
 /**
  * Structure step that will read the section.xml creating/updating sections
  * as needed, rebuilding course cache and other friends
@@ -1989,3 +2029,366 @@ abstract class restore_activity_structure_step extends restore_structure_step {
         $this->set_mapping($modulename, $oldid, $newitemid, true);
     }
 }
+
+/**
+ * Structure step in charge of creating/mapping all the qcats and qs
+ * by parsing the questions.xml file and checking it against the
+ * results calculated by {@link restore_process_categories_and_questions}
+ * and stored in backup_ids_temp
+ */
+class restore_create_categories_and_questions extends restore_structure_step {
+
+    protected function define_structure() {
+
+        $category = new restore_path_element('question_category', '/question_categories/question_category');
+        $question = new restore_path_element('question', '/question_categories/question_category/questions/question');
+
+        // Apply for 'qtype' plugins optional paths at question level
+        $this->add_plugin_structure('qtype', $question);
+
+        return array($category, $question);
+    }
+
+    protected function process_question_category($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Check we have one mapping for this category
+        if (!$mapping = $this->get_mapping('question_category', $oldid)) {
+            return; // No mapping = this category doesn't need to be created/mapped
+        }
+
+        // Check we have to create the category (newitemid = 0)
+        if ($mapping->newitemid) {
+            return; // newitemid != 0, this category is going to be mapped. Nothing to do
+        }
+
+        // Arrived here, newitemid = 0, we need to create the category
+        // we'll do it at parentitemid context, but for CONTEXT_MODULE
+        // categories, that will be created at CONTEXT_COURSE and moved
+        // to module context later when the activity is created
+        if ($mapping->info->contextlevel == CONTEXT_MODULE) {
+            $mapping->parentitemid = $this->get_mappingid('context', $this->task->get_old_contextid());
+        }
+        $data->contextid = $mapping->parentitemid;
+
+        // Let's create the question_category and save mapping
+        $newitemid = $DB->insert_record('question_categories', $data);
+        $this->set_mapping('question_category', $oldid, $newitemid);
+        // Also annotate them as question_category_created, we need
+        // that later when remapping parents
+        $this->set_mapping('question_category_created', $oldid, $newitemid, false, null, $data->contextid);
+    }
+
+    protected function process_question($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Check we have one mapping for this question
+        if (!$questionmapping = $this->get_mapping('question', $oldid)) {
+            return; // No mapping = this question doesn't need to be created/mapped
+        }
+
+        // Get the mapped category (cannot use get_new_parentid() because not
+        // all the categories have been created, so it is not always available
+        // Instead we get the mapping for the question->parentitemid because
+        // we have loaded qcatids there for all parsed questions
+        $data->category = $this->get_mappingid('question_category', $questionmapping->parentitemid);
+
+        $data->timecreated  = $this->apply_date_offset($data->timecreated);
+        $data->timemodified = $this->apply_date_offset($data->timemodified);
+
+        $userid = $this->get_mappingid('user', $data->createdby);
+        $data->createdby = $userid ? $userid : $this->task->get_userid();
+
+        $userid = $this->get_mappingid('user', $data->modifiedby);
+        $data->modifiedby = $userid ? $userid : $this->task->get_userid();
+
+        // With newitemid = 0, let's create the question
+        if (!$questionmapping->newitemid) {
+            $newitemid = $DB->insert_record('question', $data);
+            $this->set_mapping('question', $oldid, $newitemid);
+            // Also annotate them as question_created, we need
+            // that later when remapping parents (keeping the old categoryid as parentid)
+            $this->set_mapping('question_created', $oldid, $newitemid, false, null, $questionmapping->parentitemid);
+        } else {
+            // By performing this set_mapping() we make get_old/new_parentid() to work for all the
+            // children elements of the 'question' one (so qtype plugins will know the question they belong to)
+            $this->set_mapping('question', $oldid, $questionmapping->newitemid);
+        }
+
+        // Note, we don't restore any question files yet
+        // as far as the CONTEXT_MODULE categories still
+        // haven't their contexts to be restored to
+        // The {@link restore_create_question_files}, executed in the final step
+        // step will be in charge of restoring all the question files
+    }
+
+    protected function after_execute() {
+        global $DB;
+
+        // First of all, recode all the created question_categories->parent fields
+        $qcats = $DB->get_records('backup_ids_temp', array(
+                     'backupid' => $this->get_restoreid(),
+                     'itemname' => 'question_category_created'));
+        foreach ($qcats as $qcat) {
+            $newparent = 0;
+            $dbcat = $DB->get_record('question_categories', array('id' => $qcat->newitemid));
+            // Get new parent (mapped or created, so we look in quesiton_category mappings)
+            if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
+                                 'backupid' => $this->get_restoreid(),
+                                 'itemname' => 'question_category',
+                                 'itemid'   => $dbcat->parent))) {
+                // contextids must match always, as far as we always include complete qbanks, just check it
+                $newparentctxid = $DB->get_field('question_categories', 'contextid', array('id' => $newparent));
+                if ($dbcat->contextid == $newparentctxid) {
+                    $DB->set_field('question_categories', 'parent', $newparent, array('id' => $dbcat->id));
+                } else {
+                    $newparent = 0; // No ctx match for both cats, no parent relationship
+                }
+            }
+            // Here with $newparent empty, problem with contexts or remapping, set it to top cat
+            if (!$newparent) {
+                $DB->set_field('question_categories', 'parent', 0, array('id' => $dbcat->id));
+            }
+        }
+
+        // Now, recode all the created question->parent fields
+        $qs = $DB->get_records('backup_ids_temp', array(
+                  'backupid' => $this->get_restoreid(),
+                  'itemname' => 'question_created'));
+        foreach ($qs as $q) {
+            $newparent = 0;
+            $dbq = $DB->get_record('question', array('id' => $q->newitemid));
+            // Get new parent (mapped or created, so we look in question mappings)
+            if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
+                                 'backupid' => $this->get_restoreid(),
+                                 'itemname' => 'question',
+                                 'itemid'   => $dbq->parent))) {
+                $DB->set_field('question', 'parent', $newparent, array('id' => $dbq->id));
+            }
+        }
+
+        // Note, we don't restore any question files yet
+        // as far as the CONTEXT_MODULE categories still
+        // haven't their contexts to be restored to
+        // The {@link restore_create_question_files}, executed in the final step
+        // step will be in charge of restoring all the question files
+    }
+}
+
+/**
+ * Execution step that will move all the CONTEXT_MODULE question categories
+ * created at early stages of restore in course context (because modules weren't
+ * created yet) to their target module (matching by old-new-contextid mapping)
+ */
+class restore_move_module_questions_categories extends restore_execution_step {
+
+    protected function define_execution() {
+        global $DB;
+
+        $contexts = restore_dbops::restore_get_question_banks($this->get_restoreid(), CONTEXT_MODULE);
+        foreach ($contexts as $contextid => $contextlevel) {
+            // Only if context mapping exists (i.e. the module has been restored)
+            if ($newcontext = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $contextid)) {
+                // Update all the qcats having their parentitemid set to the original contextid
+                $modulecats = $DB->get_records_sql("SELECT itemid, newitemid
+                                                      FROM {backup_ids_temp}
+                                                     WHERE backupid = ?
+                                                       AND itemname = 'question_category'
+                                                       AND parentitemid = ?", array($this->get_restoreid(), $contextid));
+                foreach ($modulecats as $modulecat) {
+                    $DB->set_field('question_categories', 'contextid', $newcontext->newitemid, array('id' => $modulecat->newitemid));
+                    // And set new contextid also in question_category mapping (will be
+                    // used by {@link restore_create_question_files} later
+                    restore_dbops::set_backup_ids_record($this->get_restoreid(), 'question_category', $modulecat->itemid, $modulecat->newitemid, $newcontext->newitemid);
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Execution step that will create all the question/answers/qtype-specific files for the restored
+ * questions. It must be executed after {@link restore_move_module_questions_categories}
+ * because only then each question is in its final category and only then the
+ * context can be determined
+ *
+ * TODO: Improve this. Instead of looping over each question, it can be reduced to
+ *       be done by contexts (this will save a huge ammount of queries)
+ */
+class restore_create_question_files extends restore_execution_step {
+
+    protected function define_execution() {
+        global $DB;
+
+        // Let's process only created questions
+        $questionsrs = $DB->get_recordset_sql("SELECT bi.itemid, bi.newitemid, bi.parentitemid, q.qtype
+                                               FROM {backup_ids_temp} bi
+                                               JOIN {question} q ON q.id = bi.newitemid
+                                              WHERE bi.backupid = ?
+                                                AND bi.itemname = 'question_created'", array($this->get_restoreid()));
+        foreach ($questionsrs as $question) {
+            // Get question_category mapping, it contains the target context for the question
+            if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'question_category', $question->parentitemid)) {
+                // Something went really wrong, cannot find the question_category for the question
+                debugging('Error fetching target context for question', DEBUG_DEVELOPER);
+                continue;
+            }
+            // Calculate source and target contexts
+            $oldctxid = $qcatmapping->info->contextid;
+            $newctxid = $qcatmapping->parentitemid;
+
+            // Add common question files (question and question_answer ones)
+            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
+                                              $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true);
+            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
+                                              $oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true);
+            restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
+                                              $oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true);
+            // Add qtype dependent files
+            $components = backup_qtype_plugin::get_components_and_fileareas($question->qtype);
+            foreach ($components as $component => $fileareas) {
+                foreach ($fileareas as $filearea => $mapping) {
+                    // Use itemid only if mapping is question_created
+                    $itemid = ($mapping == 'question_created') ? $question->itemid : null;
+                    restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
+                                                      $oldctxid, $this->task->get_userid(), $mapping, $itemid, $newctxid, true);
+                }
+            }
+        }
+        $questionsrs->close();
+    }
+}
+
+/**
+ * Abstract structure step, to be used by all the activities using core questions stuff
+ * (like the quiz module), to support qtype plugins, states and sessions
+ */
+abstract class restore_questions_activity_structure_step extends restore_activity_structure_step {
+
+    /**
+     * Attach below $element (usually attempts) the needed restore_path_elements
+     * to restore question_states
+     */
+    protected function add_question_attempts_states($element, &$paths) {
+        // Check $element is restore_path_element
+        if (! $element instanceof restore_path_element) {
+            throw new restore_step_exception('element_must_be_restore_path_element', $element);
+        }
+        // Check $paths is one array
+        if (!is_array($paths)) {
+            throw new restore_step_exception('paths_must_be_array', $paths);
+        }
+        $paths[] = new restore_path_element('question_state', $element->get_path() . '/states/state');
+    }
+
+    /**
+     * Attach below $element (usually attempts) the needed restore_path_elements
+     * to restore question_sessions
+     */
+    protected function add_question_attempts_sessions($element, &$paths) {
+        // Check $element is restore_path_element
+        if (! $element instanceof restore_path_element) {
+            throw new restore_step_exception('element_must_be_restore_path_element', $element);
+        }
+        // Check $paths is one array
+        if (!is_array($paths)) {
+            throw new restore_step_exception('paths_must_be_array', $paths);
+        }
+        $paths[] = new restore_path_element('question_session', $element->get_path() . '/sessions/session');
+    }
+
+    /**
+     * Process question_states
+     */
+    protected function process_question_state($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Get complete question mapping, we'll need info
+        $question = $this->get_mapping('question', $data->question);
+
+        // In the quiz_attempt mapping we are storing uniqueid
+        // and not id, so this gets the correct question_attempt to point to
+        $data->attempt  = $this->get_new_parentid('quiz_attempt');
+        $data->question = $question->newitemid;
+        $data->answer   = $this->restore_recode_answer($data, $question->info->qtype); // Delegate recoding of answer
+        $data->timestamp= $this->apply_date_offset($data->timestamp);
+
+        // Everything ready, insert and create mapping (needed by question_sessions)
+        $newitemid = $DB->insert_record('question_states', $data);
+        $this->set_mapping('question_state', $oldid, $newitemid);
+    }
+
+    /**
+     * Process question_sessions
+     */
+    protected function process_question_session($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // In the quiz_attempt mapping we are storing uniqueid
+        // and not id, so this gets the correct question_attempt to point to
+        $data->attemptid  = $this->get_new_parentid('quiz_attempt');
+        $data->questionid = $this->get_mappingid('question', $data->questionid);
+        $data->newest     = $this->get_mappingid('question_state', $data->newest);
+        $data->newgraded  = $this->get_mappingid('question_state', $data->newgraded);
+
+        // Everything ready, insert (no mapping needed)
+        $newitemid = $DB->insert_record('question_sessions', $data);
+
+        // Note: question_sessions haven't files associated. On purpose manualcomment is lacking
+        // support for them, so we don't need to handle them here.
+    }
+
+    /**
+     * Given a list of question->ids, separated by commas, returns the
+     * recoded list, with all the restore question mappings applied.
+     * Note: Used by quiz->questions and quiz_attempts->layout
+     * Note: 0 = page break (unconverted)
+     */
+    protected function questions_recode_layout($layout) {
+        // Extracts question id from sequence
+        if ($questionids = explode(',', $layout)) {
+            foreach ($questionids as $id => $questionid) {
+                if ($questionid) { // If it is zero then this is a pagebreak, don't translate
+                    $newquestionid = $this->get_mappingid('question', $questionid);
+                    $questionids[$id] = $newquestionid;
+                }
+            }
+        }
+        return implode(',', $questionids);
+    }
+
+    /**
+     * Given one question_states record, return the answer
+     * recoded pointing to all the restored stuff
+     */
+    public function restore_recode_answer($state, $qtype) {
+        // Build one static cache to store {@link restore_qtype_plugin}
+        // while we are needing them, just to save zillions of instantiations
+        // or using static stuff that will break our nice API
+        static $qtypeplugins = array();
+
+        // If we haven't the corresponding restore_qtype_plugin for current qtype
+        // instantiate it and add to cache
+        if (!isset($qtypeplugins[$qtype])) {
+            $classname = 'restore_qtype_' . $qtype . '_plugin';
+            if (class_exists($classname)) {
+                $qtypeplugins[$qtype] = new $classname('qtype', $qtype, $this);
+            } else {
+                $qtypeplugins[$qtype] = false;
+            }
+        }
+        return !empty($qtypeplugins[$qtype]) ? $qtypeplugins[$qtype]->recode_state_answer($state) : $state->answer;
+    }
+}
index 5c95b1f..0f732dc 100644 (file)
@@ -64,6 +64,23 @@ abstract class restore_subplugin {
         return $paths;
     }
 
+    /**
+     * after_execute dispatcher for any restore_subplugin class
+     *
+     * This method will dispatch execution to the corresponding
+     * after_execute_xxx() method when available, with xxx
+     * being the connection point of the instance, so subplugin
+     * classes with multiple connection points will support
+     * multiple after_execute methods, one for each connection point
+     */
+    public function launch_after_execute_methods() {
+        // Check if the after_execute method exists and launch it
+        $afterexecute = 'after_execute_' . basename($this->connectionpoint->get_path());
+        if (method_exists($this, $afterexecute)) {
+            $this->$afterexecute();
+        }
+    }
+
 // Protected API starts here
 
 // restore_step/structure_step/task wrappers
index fd5781e..afd7c82 100644 (file)
@@ -1,28 +1,4 @@
 <?php
-    //Functions used in restore
-
-    require_once($CFG->libdir.'/gradelib.php');
-
-/**
- * Group backup/restore constants, 0.
- */
-define('RESTORE_GROUPS_NONE', 0);
-
-/**
- * Group backup/restore constants, 1.
- */
-define('RESTORE_GROUPS_ONLY', 1);
-
-/**
- * Group backup/restore constants, 2.
- */
-define('RESTORE_GROUPINGS_ONLY', 2);
-
-/**
- * Group backup/restore constants, course/all.
- */
-define('RESTORE_GROUPS_GROUPINGS', 3);
-
     //This function iterates over all modules in backup file, searching for a
     //MODNAME_refresh_events() to execute. Perhaps it should ve moved to central Moodle...
     function restore_refresh_events($restore) {
@@ -84,811 +60,6 @@ define('RESTORE_GROUPS_GROUPINGS', 3);
         return true;
     }
 
-
-    /**
-     * This function creates all the gradebook data from xml
-     */
-    function restore_create_gradebook($restore,$xml_file) {
-        global $CFG, $DB;
-
-        $status = true;
-        //Check it exists
-        if (!file_exists($xml_file)) {
-            return false;
-        }
-
-        // Get info from xml
-        // info will contain the number of record to process
-        $info = restore_read_xml_gradebook($restore, $xml_file);
-
-        // If we have info, then process
-        if (empty($info)) {
-            return $status;
-        }
-
-        if (empty($CFG->disablegradehistory) and isset($info->gradebook_histories) and $info->gradebook_histories == "true") {
-            $restore_histories = true;
-        } else {
-            $restore_histories = false;
-        }
-
-        // make sure top course category exists
-        $course_category = grade_category::fetch_course_category($restore->course_id);
-        $course_category->load_grade_item();
-
-        // we need to know if all grade items that were backed up are being restored
-        // if that is not the case, we do not restore grade categories nor gradeitems of category type or course type
-        // i.e. the aggregated grades of that category
-
-        $restoreall = true;  // set to false if any grade_item is not selected/restored or already exist
-        $importing  = !empty($SESSION->restore->importing);
-
-        if ($importing) {
-            $restoreall = false;
-
-        } else {
-            $prev_grade_items = grade_item::fetch_all(array('courseid'=>$restore->course_id));
-            $prev_grade_cats  = grade_category::fetch_all(array('courseid'=>$restore->course_id));
-
-             // if any categories already present, skip restore of categories from backup - course item or category already exist
-            if (count($prev_grade_items) > 1 or count($prev_grade_cats) > 1) {
-                $restoreall = false;
-            }
-            unset($prev_grade_items);
-            unset($prev_grade_cats);
-
-            if ($restoreall) {
-                if ($recs = $DB->get_records("backup_ids", array('table_name'=>'grade_items', 'backup_code'=>$restore->backup_unique_code), "", "old_id")) {
-                    foreach ($recs as $rec) {
-                        if ($data = backup_getid($restore->backup_unique_code,'grade_items',$rec->old_id)) {
-
-                            $info = $data->info;
-                            // do not restore if this grade_item is a mod, and
-                            $itemtype = backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#']);
-
-                            if ($itemtype == 'mod') {
-                                $olditeminstance = backup_todb($info['GRADE_ITEM']['#']['ITEMINSTANCE']['0']['#']);
-                                $itemmodule      = backup_todb($info['GRADE_ITEM']['#']['ITEMMODULE']['0']['#']);
-
-                                if (empty($restore->mods[$itemmodule]->granular)) {
-                                    continue;
-                                } else if (!empty($restore->mods[$itemmodule]->instances[$olditeminstance]->restore)) {
-                                    continue;
-                                }
-                                // at least one activity should not be restored - do not restore categories and manual items at all
-                                $restoreall = false;
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Start ul
-        if (!defined('RESTORE_SILENTLY')) {
-            echo '<ul>';
-        }
-
-        // array of restored categories - speedup ;-)
-        $cached_categories = array();
-        $outcomes          = array();
-
-    /// Process letters
-        $context = get_context_instance(CONTEXT_COURSE, $restore->course_id);
-        // respect current grade letters if defined
-        if ($status and $restoreall and !$DB->record_exists('grade_letters', array('contextid'=>$context->id))) {
-            if (!defined('RESTORE_SILENTLY')) {
-                echo '<li>'.get_string('gradeletters','grades').'</li>';
-            }
-            // Fetch recordset_size records in each iteration
-            $recs = $DB->get_records("backup_ids", array('table_name'=>'grade_letters', 'backup_code'=>$restore->backup_unique_code),
-                                        "",
-                                        "old_id");
-            if ($recs) {
-                foreach ($recs as $rec) {
-                    // Get the full record from backup_ids
-                    $data = backup_getid($restore->backup_unique_code,'grade_letters',$rec->old_id);
-                    if ($data) {
-                        $info = $data->info;
-                        $dbrec = new stdClass();
-                        $dbrec->contextid     = $context->id;
-                        $dbrec->lowerboundary = backup_todb($info['GRADE_LETTER']['#']['LOWERBOUNDARY']['0']['#']);
-                        $dbrec->letter        = backup_todb($info['GRADE_LETTER']['#']['LETTER']['0']['#']);
-                        $DB->insert_record('grade_letters', $dbrec);
-                    }
-                }
-            }
-        }
-
-    /// Process grade items and grades
-        if ($status) {
-            if (!defined('RESTORE_SILENTLY')) {
-                echo '<li>'.get_string('gradeitems','grades').'</li>';
-            }
-            $counter = 0;
-
-            //Fetch recordset_size records in each iteration
-            $recs = $DB->get_records("backup_ids", array('table_name'=>'grade_items', 'backup_code'=>$restore->backup_unique_code),
-                                        "id", // restore in the backup order
-                                        "old_id");
-
-            if ($recs) {
-                foreach ($recs as $rec) {
-                    //Get the full record from backup_ids
-                    $data = backup_getid($restore->backup_unique_code,'grade_items',$rec->old_id);
-                    if ($data) {
-                        $info = $data->info;
-
-                        // first find out if category or normal item
-                        $itemtype =  backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#'], false);
-                        if ($itemtype == 'course' or $itemtype == 'category') {
-                            if (!$restoreall or $importing) {
-                                continue;
-                            }
-
-                            $oldcat = backup_todb($info['GRADE_ITEM']['#']['ITEMINSTANCE']['0']['#'], false);
-                            if (!$cdata = backup_getid($restore->backup_unique_code,'grade_categories',$oldcat)) {
-                                continue;
-                            }
-                            $cinfo = $cdata->info;
-                            unset($cdata);
-                            if ($itemtype == 'course') {
-
-                                $course_category->fullname            = backup_todb($cinfo['GRADE_CATEGORY']['#']['FULLNAME']['0']['#'], false);
-                                $course_category->aggregation         = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATION']['0']['#'], false);
-                                $course_category->keephigh            = backup_todb($cinfo['GRADE_CATEGORY']['#']['KEEPHIGH']['0']['#'], false);
-                                $course_category->droplow             = backup_todb($cinfo['GRADE_CATEGORY']['#']['DROPLOW']['0']['#'], false);
-                                $course_category->aggregateonlygraded = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEONLYGRADED']['0']['#'], false);
-                                $course_category->aggregateoutcomes   = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEOUTCOMES']['0']['#'], false);
-                                $course_category->aggregatesubcats    = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATESUBCATS']['0']['#'], false);
-                                $course_category->timecreated         = backup_todb($cinfo['GRADE_CATEGORY']['#']['TIMECREATED']['0']['#'], false);
-                                $course_category->update('restore');
-
-                                $status = backup_putid($restore->backup_unique_code,'grade_categories',$oldcat,$course_category->id) && $status;
-                                $cached_categories[$oldcat] = $course_category;
-                                $grade_item = $course_category->get_grade_item();
-
-                            } else {
-                                $oldparent = backup_todb($cinfo['GRADE_CATEGORY']['#']['PARENT']['0']['#'], false);
-                                if (empty($cached_categories[$oldparent])) {
-                                    debugging('parent not found '.$oldparent);
-                                    continue; // parent not found, sorry
-                                }
-                                $grade_category = new grade_category();
-                                $grade_category->courseid            = $restore->course_id;
-                                $grade_category->parent              = $cached_categories[$oldparent]->id;
-                                $grade_category->fullname            = backup_todb($cinfo['GRADE_CATEGORY']['#']['FULLNAME']['0']['#'], false);
-                                $grade_category->aggregation         = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATION']['0']['#'], false);
-                                $grade_category->keephigh            = backup_todb($cinfo['GRADE_CATEGORY']['#']['KEEPHIGH']['0']['#'], false);
-                                $grade_category->droplow             = backup_todb($cinfo['GRADE_CATEGORY']['#']['DROPLOW']['0']['#'], false);
-                                $grade_category->aggregateonlygraded = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEONLYGRADED']['0']['#'], false);
-                                $grade_category->aggregateoutcomes   = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATEOUTCOMES']['0']['#'], false);
-                                $grade_category->aggregatesubcats    = backup_todb($cinfo['GRADE_CATEGORY']['#']['AGGREGATESUBCATS']['0']['#'], false);
-                                $grade_category->timecreated         = backup_todb($cinfo['GRADE_CATEGORY']['#']['TIMECREATED']['0']['#'], false);
-                                $grade_category->insert('restore');
-
-                                $status = backup_putid($restore->backup_unique_code,'grade_categories',$oldcat,$grade_category->id) && $status;
-                                $cached_categories[$oldcat] = $grade_category;
-                                $grade_item = $grade_category->get_grade_item(); // creates grade_item too
-                            }
-                            unset($cinfo);
-
-                            $idnumber = backup_todb($info['GRADE_ITEM']['#']['IDNUMBER']['0']['#'], false);
-                            if (grade_verify_idnumber($idnumber, $restore->course_id)) {
-                                $grade_item->idnumber    = $idnumber;
-                            }
-
-                            $grade_item->itemname        = backup_todb($info['GRADE_ITEM']['#']['ITEMNAME']['0']['#'], false);
-                            $grade_item->iteminfo        = backup_todb($info['GRADE_ITEM']['#']['ITEMINFO']['0']['#'], false);
-                            $grade_item->gradetype       = backup_todb($info['GRADE_ITEM']['#']['GRADETYPE']['0']['#'], false);
-                            $grade_item->calculation     = backup_todb($info['GRADE_ITEM']['#']['CALCULATION']['0']['#'], false);
-                            $grade_item->grademax        = backup_todb($info['GRADE_ITEM']['#']['GRADEMAX']['0']['#'], false);
-                            $grade_item->grademin        = backup_todb($info['GRADE_ITEM']['#']['GRADEMIN']['0']['#'], false);
-                            $grade_item->gradepass       = backup_todb($info['GRADE_ITEM']['#']['GRADEPASS']['0']['#'], false);
-                            $grade_item->multfactor      = backup_todb($info['GRADE_ITEM']['#']['MULTFACTOR']['0']['#'], false);
-                            $grade_item->plusfactor      = backup_todb($info['GRADE_ITEM']['#']['PLUSFACTOR']['0']['#'], false);
-                            $grade_item->aggregationcoef = backup_todb($info['GRADE_ITEM']['#']['AGGREGATIONCOEF']['0']['#'], false);
-                            $grade_item->display         = backup_todb($info['GRADE_ITEM']['#']['DISPLAY']['0']['#'], false);
-                            $grade_item->decimals        = backup_todb($info['GRADE_ITEM']['#']['DECIMALS']['0']['#'], false);
-                            $grade_item->hidden          = backup_todb($info['GRADE_ITEM']['#']['HIDDEN']['0']['#'], false);
-                            $grade_item->locked          = backup_todb($info['GRADE_ITEM']['#']['LOCKED']['0']['#'], false);
-                            $grade_item->locktime        = backup_todb($info['GRADE_ITEM']['#']['LOCKTIME']['0']['#'], false);
-                            $grade_item->timecreated     = backup_todb($info['GRADE_ITEM']['#']['TIMECREATED']['0']['#'], false);
-
-                            if (backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false)) {
-                                $scale = backup_getid($restore->backup_unique_code,"scale",backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false));
-                                $grade_item->scaleid     = $scale->new_id;
-                            }
-
-                            if  (backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#'], false)) {
-                                $outcome = backup_getid($restore->backup_unique_code,"grade_outcomes",backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#'], false));
-                                $grade_item->outcomeid   = $outcome->new_id;
-                            }
-
-                            $grade_item->update('restore');
-                            $status = backup_putid($restore->backup_unique_code,"grade_items", $rec->old_id, $grade_item->id) && $status;
-
-                        } else {
-                            if ($itemtype != 'mod' and (!$restoreall or $importing)) {
-                                // not extra gradebook stuff if restoring individual activities or something already there
-                                continue;
-                            }
-
-                            $dbrec = new stdClass();
-
-                            $dbrec->courseid      = $restore->course_id;
-                            $dbrec->itemtype      = backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#'], false);
-                            $dbrec->itemmodule    = backup_todb($info['GRADE_ITEM']['#']['ITEMMODULE']['0']['#'], false);
-
-                            if ($itemtype == 'mod') {
-                                // iteminstance should point to new mod
-                                $olditeminstance = backup_todb($info['GRADE_ITEM']['#']['ITEMINSTANCE']['0']['#'], false);
-                                $mod = backup_getid($restore->backup_unique_code,$dbrec->itemmodule, $olditeminstance);
-                                $dbrec->iteminstance = $mod->new_id;
-                                if (!$cm = get_coursemodule_from_instance($dbrec->itemmodule, $mod->new_id)) {
-                                    // item not restored - no item
-                                    continue;
-                                }
-                                // keep in sync with activity idnumber
-                                $dbrec->idnumber = $cm->idnumber;
-
-                            } else {
-                                $idnumber = backup_todb($info['GRADE_ITEM']['#']['IDNUMBER']['0']['#'], false);
-
-                                if (grade_verify_idnumber($idnumber, $restore->course_id)) {
-                                    //make sure the new idnumber is unique
-                                    $dbrec->idnumber  = $idnumber;
-                                }
-                            }
-
-                            $dbrec->itemname        = backup_todb($info['GRADE_ITEM']['#']['ITEMNAME']['0']['#'], false);
-                            $dbrec->itemtype        = backup_todb($info['GRADE_ITEM']['#']['ITEMTYPE']['0']['#'], false);
-                            $dbrec->itemmodule      = backup_todb($info['GRADE_ITEM']['#']['ITEMMODULE']['0']['#'], false);
-                            $dbrec->itemnumber      = backup_todb($info['GRADE_ITEM']['#']['ITEMNUMBER']['0']['#'], false);
-                            $dbrec->iteminfo        = backup_todb($info['GRADE_ITEM']['#']['ITEMINFO']['0']['#'], false);
-                            $dbrec->gradetype       = backup_todb($info['GRADE_ITEM']['#']['GRADETYPE']['0']['#'], false);
-                            $dbrec->calculation     = backup_todb($info['GRADE_ITEM']['#']['CALCULATION']['0']['#'], false);
-                            $dbrec->grademax        = backup_todb($info['GRADE_ITEM']['#']['GRADEMAX']['0']['#'], false);
-                            $dbrec->grademin        = backup_todb($info['GRADE_ITEM']['#']['GRADEMIN']['0']['#'], false);
-                            $dbrec->gradepass       = backup_todb($info['GRADE_ITEM']['#']['GRADEPASS']['0']['#'], false);
-                            $dbrec->multfactor      = backup_todb($info['GRADE_ITEM']['#']['MULTFACTOR']['0']['#'], false);
-                            $dbrec->plusfactor      = backup_todb($info['GRADE_ITEM']['#']['PLUSFACTOR']['0']['#'], false);
-                            $dbrec->aggregationcoef = backup_todb($info['GRADE_ITEM']['#']['AGGREGATIONCOEF']['0']['#'], false);
-                            $dbrec->display         = backup_todb($info['GRADE_ITEM']['#']['DISPLAY']['0']['#'], false);
-                            $dbrec->decimals        = backup_todb($info['GRADE_ITEM']['#']['DECIMALS']['0']['#'], false);
-                            $dbrec->hidden          = backup_todb($info['GRADE_ITEM']['#']['HIDDEN']['0']['#'], false);
-                            $dbrec->locked          = backup_todb($info['GRADE_ITEM']['#']['LOCKED']['0']['#'], false);
-                            $dbrec->locktime        = backup_todb($info['GRADE_ITEM']['#']['LOCKTIME']['0']['#'], false);
-                            $dbrec->timecreated     = backup_todb($info['GRADE_ITEM']['#']['TIMECREATED']['0']['#'], false);
-
-                            if (backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false)) {
-                                $scale = backup_getid($restore->backup_unique_code,"scale",backup_todb($info['GRADE_ITEM']['#']['SCALEID']['0']['#'], false));
-                                $dbrec->scaleid = $scale->new_id;
-                            }
-
-                            if  (backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#'])) {
-                                $oldoutcome = backup_todb($info['GRADE_ITEM']['#']['OUTCOMEID']['0']['#']);
-                                if (empty($outcomes[$oldoutcome])) {
-                                    continue; // error!
-                                }
-                                if (empty($outcomes[$oldoutcome]->id)) {
-                                    $outcomes[$oldoutcome]->insert('restore');
-                                    $outcomes[$oldoutcome]->use_in($restore->course_id);
-                                    backup_putid($restore->backup_unique_code, "grade_outcomes", $oldoutcome, $outcomes[$oldoutcome]->id);
-                                }
-                                $dbrec->outcomeid = $outcomes[$oldoutcome]->id;
-                            }
-
-                            $grade_item = new grade_item($dbrec, false);
-                            $grade_item->insert('restore');
-                            if ($restoreall) {
-                                // set original parent if restored
-                                $oldcat = $info['GRADE_ITEM']['#']['CATEGORYID']['0']['#'];
-                                if (!empty($cached_categories[$oldcat])) {
-                                    $grade_item->set_parent($cached_categories[$oldcat]->id);
-                                }
-                            }
-                            $status = backup_putid($restore->backup_unique_code,"grade_items", $rec->old_id, $grade_item->id) && $status;
-                        }
-
-                        // no need to restore grades if user data is not selected or importing activities
-                        if ($importing
-                          or ($grade_item->itemtype == 'mod' and !restore_userdata_selected($restore,  $grade_item->itemmodule, $olditeminstance))) {
-                            // module instance not selected when restored using granular
-                            // skip this item
-                            continue;
-                        }
-
-                        /// now, restore grade_grades
-                        if (!empty($info['GRADE_ITEM']['#']['GRADE_GRADES']['0']['#']['GRADE'])) {
-                            //Iterate over items
-                            foreach ($info['GRADE_ITEM']['#']['GRADE_GRADES']['0']['#']['GRADE'] as $g_info) {
-
-                                $grade = new grade_grade();
-                                $grade->itemid         = $grade_item->id;
-
-                                $olduser = backup_todb($g_info['#']['USERID']['0']['#'], false);
-                                $user = backup_getid($restore->backup_unique_code,"user",$olduser);
-                                $grade->userid         = $user->new_id;
-
-                                $grade->rawgrade       = backup_todb($g_info['#']['RAWGRADE']['0']['#'], false);
-                                $grade->rawgrademax    = backup_todb($g_info['#']['RAWGRADEMAX']['0']['#'], false);
-                                $grade->rawgrademin    = backup_todb($g_info['#']['RAWGRADEMIN']['0']['#'], false);
-                                // need to find scaleid
-                                if (backup_todb($g_info['#']['RAWSCALEID']['0']['#'])) {
-                                    $scale = backup_getid($restore->backup_unique_code,"scale",backup_todb($g_info['#']['RAWSCALEID']['0']['#'], false));
-                                    $grade->rawscaleid = $scale->new_id;
-                                }
-
-                                if (backup_todb($g_info['#']['USERMODIFIED']['0']['#'])) {
-                                    if ($modifier = backup_getid($restore->backup_unique_code,"user", backup_todb($g_info['#']['USERMODIFIED']['0']['#'], false))) {
-                                        $grade->usermodified = $modifier->new_id;
-                                    }
-                                }
-
-                                $grade->finalgrade        = backup_todb($g_info['#']['FINALGRADE']['0']['#'], false);
-                                $grade->hidden            = backup_todb($g_info['#']['HIDDEN']['0']['#'], false);
-                                $grade->locked            = backup_todb($g_info['#']['LOCKED']['0']['#'], false);
-                                $grade->locktime          = backup_todb($g_info['#']['LOCKTIME']['0']['#'], false);
-                                $grade->exported          = backup_todb($g_info['#']['EXPORTED']['0']['#'], false);
-                                $grade->overridden        = backup_todb($g_info['#']['OVERRIDDEN']['0']['#'], false);
-                                $grade->excluded          = backup_todb($g_info['#']['EXCLUDED']['0']['#'], false);
-                                $grade->feedback          = backup_todb($g_info['#']['FEEDBACK']['0']['#'], false);
-                                $grade->feedbackformat    = backup_todb($g_info['#']['FEEDBACKFORMAT']['0']['#'], false);
-                                $grade->information       = backup_todb($g_info['#']['INFORMATION']['0']['#'], false);
-                                $grade->informationformat = backup_todb($g_info['#']['INFORMATIONFORMAT']['0']['#'], false);
-                                $grade->timecreated       = backup_todb($g_info['#']['TIMECREATED']['0']['#'], false);
-                                $grade->timemodified      = backup_todb($g_info['#']['TIMEMODIFIED']['0']['#'], false);
-
-                                $grade->insert('restore');
-                                backup_putid($restore->backup_unique_code,"grade_grades", backup_todb($g_info['#']['ID']['0']['#']), $grade->id);
-
-                                $counter++;
-                                if ($counter % 20 == 0) {
-                                    if (!defined('RESTORE_SILENTLY')) {
-                                        echo ".";
-                                        if ($counter % 400 == 0) {
-                                            echo "<br />";
-                                        }
-                                    }
-                                    backup_flush(300);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-    /// add outcomes that are not used when doing full restore
-        if ($status and $restoreall) {
-            foreach ($outcomes as $oldoutcome=>$grade_outcome) {
-                if (empty($grade_outcome->id)) {
-                    $grade_outcome->insert('restore');
-                    $grade_outcome->use_in($restore->course_id);
-                    backup_putid($restore->backup_unique_code, "grade_outcomes", $oldoutcome, $grade_outcome->id);
-                }
-            }
-        }
-
-
-        if ($status and !$importing and $restore_histories) {
-            /// following code is very inefficient
-
-            $gchcount = $DB->count_records('backup_ids', array('backup_code'=>$restore->backup_unique_code, 'table_name'=>'grade_categories_history'));
-            $gghcount = $DB->count_records('backup_ids', array('backup_code'=>$restore->backup_unique_code, 'table_name'=>'grade_grades_history'));
-            $gihcount = $DB->count_records('backup_ids', array('backup_code'=>$restore->backup_unique_code, 'table_name'=>'grade_items_history'));
-            $gohcount = $DB->count_records('backup_ids', array('backup_code'=>$restore->backup_unique_code, 'table_name'=>'grade_outcomes_history'));
-
-            // Number of records to get in every chunk
-            $recordset_size = 2;
-
-            // process histories
-            if ($gchcount && $status) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo '<li>'.get_string('gradecategoryhistory','grades').'</li>';
-                }
-                $counter = 0;
-                while ($counter < $gchcount) {
-                    //Fetch recordset_size records in each iteration
-                    $recs = $DB->get_records("backup_ids",array('table_name'=>'grade_categories_history', 'backup_code'=>$restore->backup_unique_code),
-                                                "old_id",
-                                                "old_id",
-                                                $counter,
-                                                $recordset_size);
-                    if ($recs) {
-                        foreach ($recs as $rec) {
-                            //Get the full record from backup_ids
-                            $data = backup_getid($restore->backup_unique_code,'grade_categories_history',$rec->old_id);
-                            if ($data) {
-                                //Now get completed xmlized object
-                                $info = $data->info;
-                                //traverse_xmlize($info);                            //Debug
-                                //print_object ($GLOBALS['traverse_array']);         //Debug
-                                //$GLOBALS['traverse_array']="";                     //Debug
-
-                                $oldobj = backup_getid($restore->backup_unique_code,"grade_categories", backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['OLDID']['0']['#']));
-                                if (empty($oldobj->new_id)) {
-                                    // if the old object is not being restored, can't restoring its history
-                                    $counter++;
-                                    continue;
-                                }
-                                $dbrec->oldid = $oldobj->new_id;
-                                $dbrec->action = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['ACTION']['0']['#']);
-                                $dbrec->source = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['SOURCE']['0']['#']);
-                                $dbrec->timemodified = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['TIMEMODIFIED']['0']['#']);
-
-                                // loggeduser might not be restored, e.g. admin
-                                if ($oldobj = backup_getid($restore->backup_unique_code,"user", backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['LOGGEDUSER']['0']['#']))) {
-                                    $dbrec->loggeduser = $oldobj->new_id;
-                                }
-
-                                // this item might not have a parent at all, do not skip it if no parent is specified
-                                if (backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['PARENT']['0']['#'])) {
-                                    $oldobj = backup_getid($restore->backup_unique_code,"grade_categories", backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['PARENT']['0']['#']));
-                                    if (empty($oldobj->new_id)) {
-                                        // if the parent category not restored
-                                        $counter++;
-                                        continue;
-                                    }
-                                }
-                                $dbrec->parent = $oldobj->new_id;
-                                $dbrec->depth = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['DEPTH']['0']['#']);
-                                // path needs to be rebuilt
-                                if ($path = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['PATH']['0']['#'])) {
-                                // to preserve the path and make it work, we need to replace the categories one by one
-                                // we first get the list of categories in current path
-                                    if ($paths = explode("/", $path)) {
-                                        $newpath = '';
-                                        foreach ($paths as $catid) {
-                                            if ($catid) {
-                                                // find the new corresponding path
-                                                $oldpath = backup_getid($restore->backup_unique_code,"grade_categories", $catid);
-                                                $newpath .= "/$oldpath->new_id";
-                                            }
-                                        }
-                                        $dbrec->path = $newpath;
-                                    }
-                                }
-                                $dbrec->fullname = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['FULLNAME']['0']['#']);
-                                $dbrec->aggregation = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGRETGATION']['0']['#']);
-                                $dbrec->keephigh = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['KEEPHIGH']['0']['#']);
-                                $dbrec->droplow = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['DROPLOW']['0']['#']);
-
-                                $dbrec->aggregateonlygraded = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGREGATEONLYGRADED']['0']['#']);
-                                $dbrec->aggregateoutcomes = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGREGATEOUTCOMES']['0']['#']);
-                                $dbrec->aggregatesubcats = backup_todb($info['GRADE_CATEGORIES_HISTORY']['#']['AGGREGATESUBCATS']['0']['#']);
-
-                                $dbrec->courseid = $restore->course_id;
-                                $DB->insert_record('grade_categories_history', $dbrec);
-                                unset($dbrec);
-
-                            }
-                            //Increment counters
-                            $counter++;
-                            //Do some output
-                            if ($counter % 1 == 0) {
-                                if (!defined('RESTORE_SILENTLY')) {
-                                    echo ".";
-                                    if ($counter % 20 == 0) {
-                                        echo "<br />";
-                                    }
-                                }
-                                backup_flush(300);
-                            }
-                        }
-                    }
-                }
-            }
-
-            // process histories
-            if ($gghcount && $status) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo '<li>'.get_string('gradegradeshistory','grades').'</li>';
-                }
-                $counter = 0;
-                while ($counter < $gghcount) {
-                    //Fetch recordset_size records in each iteration
-                    $recs = $DB->get_records("backup_ids", array('table_name'=>'grade_grades_history', 'backup_code'=>$restore->backup_unique_code),
-                                                "old_id",
-                                                "old_id",
-                                                $counter,
-                                                $recordset_size);
-                    if ($recs) {
-                        foreach ($recs as $rec) {
-                            //Get the full record from backup_ids
-                            $data = backup_getid($restore->backup_unique_code,'grade_grades_history',$rec->old_id);
-                            if ($data) {
-                                //Now get completed xmlized object
-                                $info = $data->info;
-                                //traverse_xmlize($info);                            //Debug
-                                //print_object ($GLOBALS['traverse_array']);         //Debug
-                                //$GLOBALS['traverse_array']="";                     //Debug
-
-                                $oldobj = backup_getid($restore->backup_unique_code,"grade_grades", backup_todb($info['GRADE_GRADES_HISTORY']['#']['OLDID']['0']['#']));
-                                if (empty($oldobj->new_id)) {
-                                    // if the old object is not being restored, can't restoring its history
-                                    $counter++;
-                                    continue;
-                                }
-                                $dbrec->oldid = $oldobj->new_id;
-                                $dbrec->action = backup_todb($info['GRADE_GRADES_HISTORY']['#']['ACTION']['0']['#']);
-                                $dbrec->source = backup_todb($info['GRADE_GRADES_HISTORY']['#']['SOURCE']['0']['#']);
-                                $dbrec->timemodified = backup_todb($info['GRADE_GRADES_HISTORY']['#']['TIMEMODIFIED']['0']['#']);
-                                if ($oldobj = backup_getid($restore->backup_unique_code,"user", backup_todb($info['GRADE_GRADES_HISTORY']['#']['LOGGEDUSER']['0']['#']))) {
-                                    $dbrec->loggeduser = $oldobj->new_id;
-                                }
-
-                                $oldobj = backup_getid($restore->backup_unique_code,"grade_items", backup_todb($info['GRADE_GRADES_HISTORY']['#']['ITEMID']['0']['#']));
-                                $dbrec->itemid = $oldobj->new_id;
-                                if (empty($dbrec->itemid)) {
-                                    $counter++;
-                                    continue; // grade item not being restored
-                                }
-                                $oldobj = backup_getid($restore->backup_unique_code,"user", backup_todb($info['GRADE_GRADES_HISTORY']['#']['USERID']['0']['#']));
-                                $dbrec->userid = $oldobj->new_id;
-                                $dbrec->rawgrade = backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWGRADE']['0']['#']);
-                                $dbrec->rawgrademax = backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWGRADEMAX']['0']['#']);
-                                $dbrec->rawgrademin = backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWGRADEMIN']['0']['#']);
-                                if ($oldobj = backup_getid($restore->backup_unique_code,"user", backup_todb($info['GRADE_GRADES_HISTORY']['#']['USERMODIFIED']['0']['#']))) {
-                                    $dbrec->usermodified = $oldobj->new_id;
-                                }
-
-                                if (backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWSCALEID']['0']['#'])) {
-                                    $scale = backup_getid($restore->backup_unique_code,"scale",backup_todb($info['GRADE_GRADES_HISTORY']['#']['RAWSCALEID']['0']['#']));
-                                    $dbrec->rawscaleid = $scale->new_id;
-                                }
-
-                                $dbrec->finalgrade = backup_todb($info['GRADE_GRADES_HISTORY']['#']['FINALGRADE']['0']['#']);
-                                $dbrec->hidden = backup_todb($info['GRADE_GRADES_HISTORY']['#']['HIDDEN']['0']['#']);
-                                $dbrec->locked = backup_todb($info['GRADE_GRADES_HISTORY']['#']['LOCKED']['0']['#']);
-                                $dbrec->locktime = backup_todb($info['GRADE_GRADES_HISTORY']['#']['LOCKTIME']['0']['#']);
-                                $dbrec->exported = backup_todb($info['GRADE_GRADES_HISTORY']['#']['EXPORTED']['0']['#']);
-                                $dbrec->overridden = backup_todb($info['GRADE_GRADES_HISTORY']['#']['OVERRIDDEN']['0']['#']);
-                                $dbrec->excluded = backup_todb($info['GRADE_GRADES_HISTORY']['#']['EXCLUDED']['0']['#']);
-                                $dbrec->feedback = backup_todb($info['GRADE_TEXT_HISTORY']['#']['FEEDBACK']['0']['#']);
-                                $dbrec->feedbackformat = backup_todb($info['GRADE_TEXT_HISTORY']['#']['FEEDBACKFORMAT']['0']['#']);
-                                $dbrec->information = backup_todb($info['GRADE_TEXT_HISTORY']['#']['INFORMATION']['0']['#']);
-                                $dbrec->informationformat = backup_todb($info['GRADE_TEXT_HISTORY']['#']['INFORMATIONFORMAT']['0']['#']);
-
-                                $DB->insert_record('grade_grades_history', $dbrec);
-                                unset($dbrec);
-
-                            }
-                            //Increment counters
-                            $counter++;
-                            //Do some output
-                            if ($counter % 1 == 0) {
-                                if (!defined('RESTORE_SILENTLY')) {
-                                    echo ".";
-                                    if ($counter % 20 == 0) {
-                                        echo "<br />";
-                                    }
-                                }
-                                backup_flush(300);
-                            }
-                        }
-                    }
-                }
-            }
-
-            // process histories
-
-            if ($gihcount && $status) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo '<li>'.get_string('gradeitemshistory','grades').'</li>';
-                }
-                $counter = 0;
-                while ($counter < $gihcount) {
-                    //Fetch recordset_size records in each iteration
-                    $recs = $DB->get_records("backup_ids", array('table_name'=>'grade_items_history', 'backup_code'=>$restore->backup_unique_code),
-                                                "old_id",
-                                                "old_id",
-                                                $counter,
-                                                $recordset_size);
-                    if ($recs) {
-                        foreach ($recs as $rec) {
-                            //Get the full record from backup_ids
-                            $data = backup_getid($restore->backup_unique_code,'grade_items_history',$rec->old_id);
-                            if ($data) {
-                                //Now get completed xmlized object
-                                $info = $data->info;
-                                //traverse_xmlize($info);                            //Debug
-                                //print_object ($GLOBALS['traverse_array']);         //Debug
-                                //$GLOBALS['traverse_array']="";                     //Debug
-
-
-                                $oldobj = backup_getid($restore->backup_unique_code,"grade_items", backup_todb($info['GRADE_ITEM_HISTORY']['#']['OLDID']['0']['#']));
-                                if (empty($oldobj->new_id)) {
-                                    // if the old object is not being restored, can't restoring its history
-                                    $counter++;
-                                    continue;
-                                }
-                                $dbrec->oldid = $oldobj->new_id;
-                                $dbrec->action = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ACTION']['0']['#']);
-                                $dbrec->source = backup_todb($info['GRADE_ITEM_HISTORY']['#']['SOURCE']['0']['#']);
-                                $dbrec->timemodified = backup_todb($info['GRADE_ITEM_HISTORY']['#']['TIMEMODIFIED']['0']['#']);
-                                if ($oldobj = backup_getid($restore->backup_unique_code,"user", backup_todb($info['GRADE_ITEM_HISTORY']['#']['LOGGEDUSER']['0']['#']))) {
-                                    $dbrec->loggeduser = $oldobj->new_id;
-                                }
-                                $dbrec->courseid = $restore->course_id;
-                                $oldobj = backup_getid($restore->backup_unique_code,'grade_categories',backup_todb($info['GRADE_ITEM_HISTORY']['#']['CATEGORYID']['0']['#']));
-                                $oldobj->categoryid = $category->new_id;
-                                if (empty($oldobj->categoryid)) {
-                                    $counter++;
-                                    continue; // category not restored
-                                }
-
-                                $dbrec->itemname= backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMNAME']['0']['#']);
-                                $dbrec->itemtype = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMTYPE']['0']['#']);
-                                $dbrec->itemmodule = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMMODULE']['0']['#']);
-
-                                // code from grade_items restore
-                                $iteminstance = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMINSTANCE']['0']['#']);
-                                // do not restore if this grade_item is a mod, and
-                                if ($dbrec->itemtype == 'mod') {
-
-                                    if (!restore_userdata_selected($restore,  $dbrec->itemmodule, $iteminstance)) {
-                                        // module instance not selected when restored using granular
-                                        // skip this item
-                                        $counter++;
-                                        continue;
-                                    }
-
-                                    // iteminstance should point to new mod
-
-                                    $mod = backup_getid($restore->backup_unique_code,$dbrec->itemmodule, $iteminstance);
-                                    $dbrec->iteminstance = $mod->new_id;
-
-                                } else if ($dbrec->itemtype == 'category') {
-                                    // the item instance should point to the new grade category
-
-                                    // only proceed if we are restoring all grade items
-                                    if ($restoreall) {
-                                        $category = backup_getid($restore->backup_unique_code,'grade_categories', $iteminstance);
-                                        $dbrec->iteminstance = $category->new_id;
-                                    } else {
-                                        // otherwise we can safely ignore this grade item and subsequent
-                                        // grade_raws, grade_finals etc
-                                        continue;
-                                    }
-                                } elseif ($dbrec->itemtype == 'course') { // We don't restore course type to avoid duplicate course items
-                                    if ($restoreall) {
-                                        // TODO any special code needed here to restore course item without duplicating it?
-                                        // find the course category with depth 1, and course id = current course id
-                                        // this would have been already restored
-
-                                        $cat = $DB->get_record('grade_categories', array('depth'=>1, 'courseid'=>$restore->course_id));
-                                        $dbrec->iteminstance = $cat->id;
-
-                                    } else {
-                                        $counter++;
-                                        continue;
-                                    }
-                                }
-
-                                $dbrec->itemnumber = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMNUMBER']['0']['#']);
-                                $dbrec->iteminfo = backup_todb($info['GRADE_ITEM_HISTORY']['#']['ITEMINFO']['0']['#']);
-                                $dbrec->idnumber = backup_todb($info['GRADE_ITEM_HISTORY']['#']['IDNUMBER']['0']['#']);
-                                $dbrec->calculation = backup_todb($info['GRADE_ITEM_HISTORY']['#']['CALCULATION']['0']['#']);
-                                $dbrec->gradetype = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADETYPE']['0']['#']);
-                                $dbrec->grademax = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADEMAX']['0']['#']);
-                                $dbrec->grademin = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADEMIN']['0']['#']);
-                                if ($oldobj = backup_getid($restore->backup_unique_code,"scale", backup_todb($info['GRADE_ITEM_HISTORY']['#']['SCALEID']['0']['#']))) {
-                                    // scaleid is optional
-                                    $dbrec->scaleid = $oldobj->new_id;
-                                }
-                                if ($oldobj = backup_getid($restore->backup_unique_code,"grade_outcomes", backup_todb($info['GRADE_ITEM_HISTORY']['#']['OUTCOMEID']['0']['#']))) {
-                                    // outcome is optional
-                                    $dbrec->outcomeid = $oldobj->new_id;
-                                }
-                                $dbrec->gradepass = backup_todb($info['GRADE_ITEM_HISTORY']['#']['GRADEPASS']['0']['#']);
-                                $dbrec->multfactor = backup_todb($info['GRADE_ITEM_HISTORY']['#']['MULTFACTOR']['0']['#']);
-                                $dbrec->plusfactor = backup_todb($info['GRADE_ITEM_HISTORY']['#']['PLUSFACTOR']['0']['#']);
-                                $dbrec->aggregationcoef = backup_todb($info['GRADE_ITEM_HISTORY']['#']['AGGREGATIONCOEF']['0']['#']);
-                                $dbrec->sortorder = backup_todb($info['GRADE_ITEM_HISTORY']['#']['SORTORDER']['0']['#']);
-                                $dbrec->display = backup_todb($info['GRADE_ITEM_HISTORY']['#']['DISPLAY']['0']['#']);
-                                $dbrec->decimals = backup_todb($info['GRADE_ITEM_HISTORY']['#']['DECIMALS']['0']['#']);
-                                $dbrec->hidden = backup_todb($info['GRADE_ITEM_HISTORY']['#']['HIDDEN']['0']['#']);
-                                $dbrec->locked = backup_todb($info['GRADE_ITEM_HISTORY']['#']['LOCKED']['0']['#']);
-                                $dbrec->locktime = backup_todb($info['GRADE_ITEM_HISTORY']['#']['LOCKTIME']['0']['#']);
-                                $dbrec->needsupdate = backup_todb($info['GRADE_ITEM_HISTORY']['#']['NEEDSUPDATE']['0']['#']);
-
-                                $DB->insert_record('grade_items_history', $dbrec);
-                                unset($dbrec);
-
-                            }
-                            //Increment counters
-                            $counter++;
-                            //Do some output
-                            if ($counter % 1 == 0) {
-                                if (!defined('RESTORE_SILENTLY')) {
-                                    echo ".";
-                                    if ($counter % 20 == 0) {
-                                        echo "<br />";
-                                    }
-                                }
-                                backup_flush(300);
-                            }
-                        }
-                    }
-                }
-            }
-
-            // process histories
-            if ($gohcount && $status) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo '<li>'.get_string('gradeoutcomeshistory','grades').'</li>';
-                }
-                $counter = 0;
-                while ($counter < $gohcount) {
-                    //Fetch recordset_size records in each iteration
-                    $recs = $DB->get_records("backup_ids", array('table_name'=>'grade_outcomes_history', 'backup_code'=>$restore->backup_unique_code),
-                                                "old_id",
-                                                "old_id",
-                                                $counter,
-                                                $recordset_size);
-                    if ($recs) {
-                        foreach ($recs as $rec) {
-                            //Get the full record from backup_ids
-                            $data = backup_getid($restore->backup_unique_code,'grade_outcomes_history',$rec->old_id);
-                            if ($data) {
-                                //Now get completed xmlized object
-                                $info = $data->info;
-                                //traverse_xmlize($info);                            //Debug
-                                //print_object ($GLOBALS['traverse_array']);         //Debug
-                                //$GLOBALS['traverse_array']="";                     //Debug
-
-                                $oldobj = backup_getid($restore->backup_unique_code,"grade_outcomes", backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['OLDID']['0']['#']));
-                                if (empty($oldobj->new_id)) {
-                                    // if the old object is not being restored, can't restoring its history
-                                    $counter++;
-                                    continue;
-                                }
-                                $dbrec->oldid = $oldobj->new_id;
-                                $dbrec->action = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['ACTION']['0']['#']);
-                                $dbrec->source = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['SOURCE']['0']['#']);
-                                $dbrec->timemodified = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['TIMEMODIFIED']['0']['#']);
-                                if ($oldobj = backup_getid($restore->backup_unique_code,"user", backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['LOGGEDUSER']['0']['#']))) {
-                                    $dbrec->loggeduser = $oldobj->new_id;
-                                }
-                                $dbrec->courseid = $restore->course_id;
-                                $dbrec->shortname = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['SHORTNAME']['0']['#']);
-                                $dbrec->fullname= backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['FULLNAME']['0']['#']);
-                                $oldobj = backup_getid($restore->backup_unique_code,"scale", backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['SCALEID']['0']['#']));
-                                $dbrec->scaleid = $oldobj->new_id;
-                                $dbrec->description = backup_todb($info['GRADE_OUTCOME_HISTORY']['#']['DESCRIPTION']['0']['#']);
-
-                                $DB->insert_record('grade_outcomes_history', $dbrec);
-                                unset($dbrec);
-
-                            }
-                            //Increment counters
-                            $counter++;
-                            //Do some output
-                            if ($counter % 1 == 0) {
-                                if (!defined('RESTORE_SILENTLY')) {
-                                    echo ".";
-                                    if ($counter % 20 == 0) {
-                                        echo "<br />";
-                                    }
-                                }
-                                backup_flush(300);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        if (!defined('RESTORE_SILENTLY')) {
-        //End ul
-            echo '</ul>';
-        }
-        return $status;
-    }
-
     //This function creates all the structures messages and contacts
     function restore_create_messages($restore,$xml_file) {
         global $CFG, $DB;
index 1e9ac82..98fd2ce 100644 (file)
@@ -233,17 +233,421 @@ abstract class restore_dbops {
         $xmlparser->process();
     }
 
+    /**
+     * Load the needed questions.xml file to backup_ids table for future reference
+     */
+    public static function load_categories_and_questions_to_tempids($restoreid, $questionsfile) {
+
+        if (!file_exists($questionsfile)) { // Shouldn't happen ever, but...
+            throw new backup_helper_exception('missing_questions_xml_file', $questionsfile);
+        }
+        // Let's parse, custom processor will do its work, sending info to DB
+        $xmlparser = new progressive_parser();
+        $xmlparser->set_file($questionsfile);
+        $xmlprocessor = new restore_questions_parser_processor($restoreid);
+        $xmlparser->set_processor($xmlprocessor);
+        $xmlparser->process();
+    }
+
+    /**
+     * Check all the included categories and questions, deciding the action to perform
+     * for each one (mapping / creation) and returning one array of problems in case
+     * something is wrong.
+     *
+     * There are some basic rules that the method below will always try to enforce:
+     *
+     * Rule1: Targets will be, always, calculated for *whole* question banks (a.k.a. contexid source),
+     *     so, given 2 question categories belonging to the same bank, their target bank will be
+     *     always the same. If not, we can be incurring into "fragmentation", leading to random/cloze
+     *     problems (qtypes having "child" questions).
+     *
+     * Rule2: The 'moodle/question:managecategory' and 'moodle/question:add' capabilities will be
+     *     checked before creating any category/question respectively and, if the cap is not allowed
+     *     into upper contexts (system, coursecat)) but in lower ones (course), the *whole* question bank
+     *     will be created there.
+     *
+     * Rule3: Coursecat question banks not existing in the target site will be created as course
+     *     (lower ctx) question banks, never as "guessed" coursecat question banks base on depth or so.
+     *
+     * Rule4: System question banks will be created at system context if user has perms to do so. Else they
+     *     will created as course (lower ctx) question banks (similary to rule3). In other words, course ctx
+     *     if always a fallback for system and coursecat question banks.
+     *
+     * Also, there are some notes to clarify the scope of this method:
+     *
+     * Note1: This method won't create any question category nor question at all. It simply will calculate
+     *     which actions (create/map) must be performed for each element and where, validating that all those
+     *     actions are doable by the user executing the restore operation. Any problem found will be
+     *     returned in the problems array, causing the restore process to stop with error.
+     *
+     * Note2: To decide if one question bank (all its question categories and questions) is going to be remapped,
+     *     then all the categories and questions must exist in the same target bank. If able to do so, missing
+     *     qcats and qs will be created (rule2). But if, at the end, something is missing, the whole question bank
+     *     will be recreated at course ctx (rule1), no matter if that duplicates some categories/questions.
+     *
+     * Note3: We'll be using the newitemid column in the temp_ids table to store the action to be performed
+     *     with each question category and question. newitemid = 0 means the qcat/q needs to be created and
+     *     any other value means the qcat/q is mapped. Also, for qcats, parentitemid will contain the target
+     *     context where the categories have to be created (but for module contexts where we'll keep the old
+     *     one until the activity is created)
+     *
+     * Note4: All these "actions" will be "executed" later by {@link restore_create_categories_and_questions}
+     */
+    public static function precheck_categories_and_questions($restoreid, $courseid, $userid, $samesite) {
+
+        $problems = array();
+
+        // TODO: Check all qs, looking their qtypes are restorable
+
+        // Precheck all qcats and qs looking for target contexts / warnings / errors
+        list($syserr, $syswarn) = self::prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, CONTEXT_SYSTEM);
+        list($caterr, $catwarn) = self::prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, CONTEXT_COURSECAT);
+        list($couerr, $couwarn) = self::prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, CONTEXT_COURSE);
+        list($moderr, $modwarn) = self::prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, CONTEXT_MODULE);
+
+        // Acummulate and handle errors and warnings
+        $errors   = array_merge($syserr, $caterr, $couerr, $moderr);
+        $warnings = array_merge($syswarn, $catwarn, $couwarn, $modwarn);
+        if (!empty($errors)) {
+            $problems['errors'] = $errors;
+        }
+        if (!empty($warnings)) {
+            $problems['warnings'] = $warnings;
+        }
+        return $problems;
+    }
+
+    /**
+     * This function will process all the question banks present in restore
+     * at some contextlevel (from CONTEXT_SYSTEM to CONTEXT_MODULE), finding
+     * the target contexts where each bank will be restored and returning
+     * warnings/errors as needed.
+     *
+     * Some contextlevels (system, coursecat), will delegate process to
+     * course level if any problem is found (lack of permissions, non-matching
+     * target context...). Other contextlevels (course, module) will
+     * cause return error if some problem is found.
+     *
+     * At the end, if no errors were found, all the categories in backup_temp_ids
+     * will be pointing (parentitemid) to the target context where they must be
+     * created later in the restore process.
+     *
+     * Note: at the time these prechecks are executed, activities haven't been
+     * created yet so, for CONTEXT_MODULE banks, we keep the old contextid
+     * in the parentitemid field. Once the activity (and its context) has been
+     * created, we'll update that context in the required qcats
+     *
+     * Caller {@link precheck_categories_and_questions} will, simply, execute
+     * this function for all the contextlevels, acting as a simple controller
+     * of warnings and errors.
+     *
+     * The function returns 2 arrays, one containing errors and another containing
+     * warnings. Both empty if no errors/warnings are found.
+     */
+    public static function prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, $contextlevel) {
+        global $CFG, $DB;
+
+        // To return any errors and warnings found
+        $errors   = array();
+        $warnings = array();
+
+        // Specify which fallbacks must be performed
+        $fallbacks = array(
+            CONTEXT_SYSTEM => CONTEXT_COURSE,
+            CONTEXT_COURSECAT => CONTEXT_COURSE);
+
+        // For any contextlevel, follow this process logic:
+        //
+        // 0) Iterate over each context (qbank)
+        // 1) Iterate over each qcat in the context, matching by stamp for the found target context
+        //     2a) No match, check if user can create qcat and q
+        //         3a) User can, mark the qcat and all dependent qs to be created in that target context
+        //         3b) User cannot, check if we are in some contextlevel with fallback
+        //             4a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
+        //             4b) No fallback, error. End qcat loop.
+        //     2b) Match, mark qcat to be mapped and iterate over each q, matching by stamp and version
+        //         5a) No match, check if user can add q
+        //             6a) User can, mark the q to be created
+        //             6b) User cannot, check if we are in some contextlevel with fallback
+        //                 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
+        //                 7b) No fallback, error. End qcat loop
+        //         5b) Match, mark q to be mapped
+
+        // Get all the contexts (question banks) in restore for the given contextlevel
+        $contexts = self::restore_get_question_banks($restoreid, $contextlevel);
+
+        // 0) Iterate over each context (qbank)
+        foreach ($contexts as $contextid => $contextlevel) {
+            // Init some perms
+            $canmanagecategory = false;
+            $canadd            = false;
+            // get categories in context (bank)
+            $categories = self::restore_get_question_categories($restoreid, $contextid);
+            // cache permissions if $targetcontext is found
+            if ($targetcontext = self::restore_find_best_target_context($categories, $courseid, $contextlevel)) {
+                $canmanagecategory = has_capability('moodle/question:managecategory', $targetcontext, $userid);
+                $canadd            = has_capability('moodle/question:add', $targetcontext, $userid);
+            }
+            // 1) Iterate over each qcat in the context, matching by stamp for the found target context
+            foreach ($categories as $category) {
+                $matchcat = false;
+                if ($targetcontext) {
+                    $matchcat = $DB->get_record('question_categories', array(
+                                    'contextid' => $targetcontext->id,
+                                    'stamp' => $category->stamp));
+                }
+                // 2a) No match, check if user can create qcat and q
+                if (!$matchcat) {
+                    // 3a) User can, mark the qcat and all dependent qs to be created in that target context
+                    if ($canmanagecategory && $canadd) {
+                        // Set parentitemid to targetcontext, BUT for CONTEXT_MODULE categories, where
+                        // we keep the source contextid unmodified (for easier matching later when the
+                        // activities are created)
+                        $parentitemid = $targetcontext->id;
+                        if ($contextlevel == CONTEXT_MODULE) {
+                            $parentitemid = null; // null means "not modify" a.k.a. leave original contextid
+                        }
+                        self::set_backup_ids_record($restoreid, 'question_category', $category->id, 0, $parentitemid);
+                        // Nothing else to mark, newitemid = 0 means create
+
+                    // 3b) User cannot, check if we are in some contextlevel with fallback
+                    } else {
+                        // 4a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
+                        if (array_key_exists($contextlevel, $fallbacks)) {
+                            foreach ($categories as $movedcat) {
+                                $movedcat->contextlevel = $fallbacks[$contextlevel];
+                                self::set_backup_ids_record($restoreid, 'question_category', $movedcat->id, 0, $contextid, $movedcat);
+                                // Warn about the performed fallback
+                                $warnings[] = get_string('qcategory2coursefallback', 'backup', $movedcat);
+                            }
+
+                        // 4b) No fallback, error. End qcat loop.
+                        } else {
+                            $errors[] = get_string('qcategorycannotberestored', 'backup', $category);
+                        }
+                        break; // out from qcat loop (both 4a and 4b), we have decided about ALL categories in context (bank)
+                    }
+
+                // 2b) Match, mark qcat to be mapped and iterate over each q, matching by stamp and version
+                } else {
+                    self::set_backup_ids_record($restoreid, 'question_category', $category->id, $matchcat->id, $targetcontext->id);
+                    $questions = self::restore_get_questions($restoreid, $category->id);
+                    foreach ($questions as $question) {
+                        $matchq = $DB->get_record('question', array(
+                                      'category' => $matchcat->id,
+                                      'stamp' => $question->stamp,
+                                      'version' => $question->version));
+                        // 5a) No match, check if user can add q
+                        if (!$matchq) {
+                            // 6a) User can, mark the q to be created
+                            if ($canadd) {
+                                // Nothing to mark, newitemid means create
+
+                             // 6b) User cannot, check if we are in some contextlevel with fallback
+                            } else {
+                                // 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loo
+                                if (array_key_exists($contextlevel, $fallbacks)) {
+                                    foreach ($categories as $movedcat) {
+                                        $movedcat->contextlevel = $fallbacks[$contextlevel];
+                                        self::set_backup_ids_record($restoreid, 'question_category', $movedcat->id, 0, $contextid, $movedcat);
+                                        // Warn about the performed fallback
+                                        $warnings[] = get_string('question2coursefallback', 'backup', $movedcat);
+                                    }
+
+                                // 7b) No fallback, error. End qcat loop
+                                } else {
+                                    $errors[] = get_string('questioncannotberestored', 'backup', $question);
+                                }
+                                break 2; // out from qcat loop (both 7a and 7b), we have decided about ALL categories in context (bank)
+                            }
+
+                        // 5b) Match, mark q to be mapped
+                        } else {
+                            self::set_backup_ids_record($restoreid, 'question', $question->id, $matchq->id);
+                        }
+                    }
+                }
+            }
+        }
+
+        return array($errors, $warnings);
+    }
+
+    /**
+     * Return one array of contextid => contextlevel pairs
+     * of question banks to be checked for one given restore operation
+     * ordered from CONTEXT_SYSTEM downto CONTEXT_MODULE
+     * If contextlevel is specified, then only banks corresponding to
+     * that level are returned
+     */
+    public static function restore_get_question_banks($restoreid, $contextlevel = null) {
+        global $DB;
+
+        $results = array();
+        $qcats = $DB->get_records_sql("SELECT itemid, parentitemid AS contextid
+                                         FROM {backup_ids_temp}
+                                       WHERE backupid = ?
+                                         AND itemname = 'question_category'", array($restoreid));
+        foreach ($qcats as $qcat) {
+            // If this qcat context haven't been acummulated yet, do that
+            if (!isset($results[$qcat->contextid])) {
+                $temprec = self::get_backup_ids_record($restoreid, 'question_category', $qcat->itemid);
+                // Filter by contextlevel if necessary
+                if (is_null($contextlevel) || $contextlevel == $temprec->info->contextlevel) {
+                    $results[$qcat->contextid] = $temprec->info->contextlevel;
+                }
+            }
+        }
+        // Sort by value (contextlevel from CONTEXT_SYSTEM downto CONTEXT_MODULE)
+        asort($results);
+        return $results;
+    }
+
+    /**
+     * Return one array of question_category records for
+     * a given restore operation and one restore context (question bank)
+     */
+    public static function restore_get_question_categories($restoreid, $contextid) {
+        global $DB;
+
+        $results = array();
+        $qcats = $DB->get_records_sql("SELECT itemid
+                                         FROM {backup_ids_temp}
+                                        WHERE backupid = ?
+                                          AND itemname = 'question_category'
+                                          AND parentitemid = ?", array($restoreid, $contextid));
+        foreach ($qcats as $qcat) {
+            $temprec = self::get_backup_ids_record($restoreid, 'question_category', $qcat->itemid);
+            $results[$qcat->itemid] = $temprec->info;
+        }
+        return $results;
+    }
+
+    /**
+     * Calculates the best context found to restore one collection of qcats,
+     * al them belonging to the same context (question bank), returning the
+     * target context found (object) or false
+     */
+    public static function restore_find_best_target_context($categories, $courseid, $contextlevel) {
+        global $DB;
+
+        $targetcontext = false;
+
+        // Depending of $contextlevel, we perform different actions
+        switch ($contextlevel) {
+             // For system is easy, the best context is the system context
+             case CONTEXT_SYSTEM:
+                 $targetcontext = get_context_instance(CONTEXT_SYSTEM);
+                 break;
+
+             // For coursecat, we are going to look for stamps in all the
+             // course categories between CONTEXT_SYSTEM and CONTEXT_COURSE
+             // (i.e. in all the course categories in the path)
+             //
+             // And only will return one "best" target context if all the
+             // matches belong to ONE and ONLY ONE context. If multiple
+             // matches are found, that means that there is some annoying
+             // qbank "fragmentation" in the categories, so we'll fallback
+             // to create the qbank at course level
+             case CONTEXT_COURSECAT:
+                 // Build the array of stamps we are going to match
+                 $stamps = array();
+                 foreach ($categories as $category) {
+                     $stamps[] = $category->stamp;
+                 }
+                 $contexts = array();
+                 // Build the array of contexts we are going to look
+                 $systemctx = get_context_instance(CONTEXT_SYSTEM);
+                 $coursectx = get_context_instance(CONTEXT_COURSE, $courseid);
+                 $parentctxs= get_parent_contexts($coursectx);
+                 foreach ($parentctxs as $parentctx) {
+                     // Exclude system context
+                     if ($parentctx == $systemctx->id) {
+                         continue;
+                     }
+                     $contexts[] = $parentctx;
+                 }
+                 if (!empty($stamps) && !empty($contexts)) {
+                     // Prepare the query
+                     list($stamp_sql, $stamp_params) = $DB->get_in_or_equal($stamps);
+                     list($context_sql, $context_params) = $DB->get_in_or_equal($contexts);
+                     $sql = "SELECT contextid
+                               FROM {question_categories}
+                              WHERE stamp $stamp_sql
+                                AND contextid $context_sql";
+                     $params = array_merge($stamp_params, $context_params);
+                     $matchingcontexts = $DB->get_records_sql($sql, $params);
+                     // Only if ONE and ONLY ONE context is found, use it as valid target
+                     if (count($matchingcontexts) == 1) {
+                         $targetcontext = get_context_instance_by_id(reset($matchingcontexts)->contextid);
+                     }
+                 }
+                 break;
+
+             // For course is easy, the best context is the course context
+             case CONTEXT_COURSE:
+                 $targetcontext = get_context_instance(CONTEXT_COURSE, $courseid);
+                 break;
+
+             // For module is easy, there is not best context, as far as the
+             // activity hasn't been created yet. So we return context course
+             // for them, so permission checks and friends will work. Note this
+             // case is handled by {@link prechek_precheck_qbanks_by_level}
+             // in an special way
+             case CONTEXT_MODULE:
+                 $targetcontext = get_context_instance(CONTEXT_COURSE, $courseid);
+                 break;
+        }
+        return $targetcontext;
+    }
+
+    /**
+     * Return one array of question records for
+     * a given restore operation and one question category
+     */
+    public static function restore_get_questions($restoreid, $qcatid) {
+        global $DB;
+
+        $results = array();
+        $qs = $DB->get_records_sql("SELECT itemid
+                                      FROM {backup_ids_temp}
+                                     WHERE backupid = ?
+                                       AND itemname = 'question'
+                                       AND parentitemid = ?", array($restoreid, $qcatid));
+        foreach ($qs as $q) {
+            $temprec = self::get_backup_ids_record($restoreid, 'question', $q->itemid);
+            $results[$q->itemid] = $temprec->info;
+        }
+        return $results;
+    }
+
     /**
      * Given one component/filearea/context and
      * optionally one source itemname to match itemids
      * put the corresponding files in the pool
      */
-    public static function send_files_to_pool($basepath, $restoreid, $component, $filearea, $oldcontextid, $dfltuserid, $itemname = null, $olditemid = null) {
+    public static function send_files_to_pool($basepath, $restoreid, $component, $filearea, $oldcontextid, $dfltuserid, $itemname = null, $olditemid = null, $forcenewcontextid = null, $skipparentitemidctxmatch = false) {
         global $DB;
 
-        // Get new context, must exist or this will fail
-        if (!$newcontextid = self::get_backup_ids_record($restoreid, 'context', $oldcontextid)->newitemid) {
-            throw new restore_dbops_exception('unknown_context_mapping', $oldcontextid);
+        if ($forcenewcontextid) {
+            // Some components can have "forced" new contexts (example: questions can end belonging to non-standard context mappings,
+            // with questions originally at system/coursecat context in source being restored to course context in target). So we need
+            // to be able to force the new contextid
+            $newcontextid = $forcenewcontextid;
+        } else {
+            // Get new context, must exist or this will fail
+            if (!$newcontextid = self::get_backup_ids_record($restoreid, 'context', $oldcontextid)->newitemid) {
+                throw new restore_dbops_exception('unknown_context_mapping', $oldcontextid);
+            }
+        }
+
+        // Sometimes it's possible to have not the oldcontextids stored into backup_ids_temp->parentitemid
+        // columns (because we have used them to store other information). This happens usually with
+        // all the question related backup_ids_temp records. In that case, it's safe to ignore that
+        // matching as far as we are always restoring for well known oldcontexts and olditemids
+        $parentitemctxmatchsql = ' AND i.parentitemid = f.contextid ';
+        if ($skipparentitemidctxmatch) {
+            $parentitemctxmatchsql = '';
         }
 
         // Important: remember how files have been loaded to backup_files_temp
@@ -262,16 +666,16 @@ abstract class restore_dbops {
 
         // itemname not null, going to join with backup_ids to perform the old-new mapping of itemids
         } else {
-            $sql = 'SELECT f.contextid, f.component, f.filearea, f.itemid, i.newitemid, f.info
+            $sql = "SELECT f.contextid, f.component, f.filearea, f.itemid, i.newitemid, f.info
                       FROM {backup_files_temp} f
                       JOIN {backup_ids_temp} i ON i.backupid = f.backupid
-                                              AND i.parentitemid = f.contextid
+                                              $parentitemctxmatchsql
                                               AND i.itemid = f.itemid
                      WHERE f.backupid = ?
                        AND f.contextid = ?
                        AND f.component = ?
                        AND f.filearea = ?
-                       AND i.itemname = ?';
+                       AND i.itemname = ?";
             $params = array($restoreid, $oldcontextid, $component, $filearea, $itemname);
             if ($olditemid !== null) { // Just process ONE olditemid intead of the whole itemname
                 $sql .= ' AND i.itemid = ?';
@@ -760,11 +1164,11 @@ abstract class restore_dbops {
     }
 
     /**
-     * Process the needed users in order to create / map them
+     * Process the needed users in order to decide
+     * which action to perform with them (create/map)
      *
      * Just wrap over precheck_included_users(), returning
-     * exception if any problem is found or performing the
-     * required user creations if needed
+     * exception if any problem is found
      */
     public static function process_included_users($restoreid, $courseid, $userid, $samesite) {
         global $DB;
@@ -779,6 +1183,27 @@ abstract class restore_dbops {
         }
     }
 
+    /**
+     * Process the needed question categories and questions
+     * to check all them, deciding about the action to perform
+     * (create/map) and target.
+     *
+     * Just wrap over precheck_categories_and_questions(), returning
+     * exception if any problem is found
+     */
+    public static function process_categories_and_questions($restoreid, $courseid, $userid, $samesite) {
+        global $DB;
+
+        // Just let precheck_included_users() to do all the hard work
+        $problems = self::precheck_categories_and_questions($restoreid, $courseid, $userid, $samesite);
+
+        // With problems of type error, throw exception, shouldn't happen if prechecks were originally
+        // executed, so be radical here.
+        if (array_key_exists('errors', $problems)) {
+            throw new restore_dbops_exception('restore_problems_processing_questions', null, implode(', ', $problems));
+        }
+    }
+
     public static function set_backup_files_record($restoreid, $filerec) {
         global $DB;
 
index 6361cd2..0433516 100644 (file)
@@ -120,6 +120,14 @@ abstract class restore_prechecks_helper {
             $warnings = array_key_exists('warnings', $problems) ? array_merge($warnings, $problems['warnings']) : $warnings;
         }
 
+        // Check we are able to restore and the categories and questions
+        $file = $controller->get_plan()->get_basepath() . '/questions.xml';
+        restore_dbops::load_categories_and_questions_to_tempids($restoreid, $file);
+        if ($problems = restore_dbops::precheck_categories_and_questions($restoreid, $courseid, $userid, $samesite)) {
+            $errors = array_key_exists('errors', $problems) ? array_merge($errors, $problems['errors']) : $errors;
+            $warnings = array_key_exists('warnings', $problems) ? array_merge($warnings, $problems['warnings']) : $warnings;
+        }
+
         // Prepare results and return
         $results = array();
         if (!empty($errors)) {
diff --git a/backup/util/helper/restore_questions_parser_processor.class.php b/backup/util/helper/restore_questions_parser_processor.class.php
new file mode 100644 (file)
index 0000000..c15a9f2
--- /dev/null
@@ -0,0 +1,87 @@
+<?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-helper
+ * @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/util/xml/parser/processors/grouped_parser_processor.class.php');
+
+/**
+ * helper implementation of grouped_parser_processor that will
+ * load all the categories and questions (header info only) from then questions.xml file
+ * to the backup_ids table storing the whole structure there for later processing.
+ * Note: only "needed" categories are loaded (must have question_categoryref record in backup_ids)
+ * Note: parentitemid will contain the category->contextid for categories
+ * Note: parentitemid will contain the category->id for questions
+ *
+ * TODO: Complete phpdocs
+ */
+class restore_questions_parser_processor extends grouped_parser_processor {
+
+    protected $restoreid;
+    protected $lastcatid;
+
+    public function __construct($restoreid) {
+        $this->restoreid = $restoreid;
+        $this->lastcatid = 0;
+        parent::__construct(array());
+        // Set the paths we are interested on
+        $this->add_path('/question_categories/question_category');
+        $this->add_path('/question_categories/question_category/questions/question');
+    }
+
+    protected function dispatch_chunk($data) {
+        // Prepare question_category record
+        if ($data['path'] == '/question_categories/question_category') {
+            $info     = (object)$data['tags'];
+            $itemname = 'question_category';
+            $itemid   = $info->id;
+            $parentitemid = $info->contextid;
+            $this->lastcatid = $itemid;
+
+        // Prepare question record
+        } else if ($data['path'] == '/question_categories/question_category/questions/question') {
+            $info = (object)$data['tags'];
+            $itemname = 'question';
+            $itemid   = $info->id;
+            $parentitemid = $this->lastcatid;
+
+        // Not question_category nor question, impossible. Throw exception.
+        } else {
+            throw new progressive_parser_exception('restore_questions_parser_processor_unexpected_path', $data['path']);
+        }
+
+        // Only load it if needed (exist same question_categoryref itemid in table)
+        if (restore_dbops::get_backup_ids_record($this->restoreid, 'question_categoryref', $this->lastcatid)) {
+            restore_dbops::set_backup_ids_record($this->restoreid, $itemname, $itemid, 0, $parentitemid, $info);
+        }
+    }
+
+    /**
+     * Provide NULL decoding
+     */
+    public function process_cdata($cdata) {
+        if ($cdata === '$@NULL@$') {
+            return null;
+        }
+        return $cdata;
+    }
+}
index 69dc7c6..4452a14 100644 (file)
@@ -40,6 +40,7 @@ require_once($CFG->dirroot . '/backup/util/helper/restore_moodlexml_parser_proce
 require_once($CFG->dirroot . '/backup/util/helper/restore_inforef_parser_processor.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_users_parser_processor.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_roles_parser_processor.class.php');
+require_once($CFG->dirroot . '/backup/util/helper/restore_questions_parser_processor.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_structure_parser_processor.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_decode_rule.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/restore_decode_content.class.php');
index adf99e6..970e775 100644 (file)
@@ -101,10 +101,10 @@ abstract class backup_structure_step extends backup_step {
 // Protected API starts here
 
     /**
-     * Add plugin structure to any element in the activity backup tree
+     * Add plugin structure to any element in the structure backup tree
      *
      * @param string $plugintype type of plugin as defined by get_plugin_types()
-     * @param backup_nested_element $element element in the activity backup tree that
+     * @param backup_nested_element $element element in the structure backup tree that
      *                                       we are going to add plugin information to
      * @param bool $multiple to define if multiple plugins can produce information
      *                       for each instance of $element (true) or no (false)
index 26efb6a..aef63fa 100644 (file)
@@ -98,8 +98,8 @@ abstract class restore_structure_step extends restore_step {
         // And process it, dispatch to target methods in step will start automatically
         $xmlparser->process();
 
-        // Have finished, call to the after_execute method
-        $this->after_execute();
+        // Have finished, launch the after_execute method of all the processing objects
+        $this->launch_after_execute_methods();
     }
 
     /**
@@ -238,8 +238,97 @@ abstract class restore_structure_step extends restore_step {
         return $value + $cache[$this->get_restoreid()];
     }
 
+    /**
+     * As far as restore structure steps are implementing restore_plugin stuff, they need to
+     * have the parent task available for wrapping purposes (get course/context....)
+     */
+    public function get_task() {
+        return $this->task;
+    }
+
 // Protected API starts here
 
+    /**
+     * Add plugin structure to any element in the structure restore tree
+     *
+     * @param string $plugintype type of plugin as defined by get_plugin_types()
+     * @param restore_path_element $element element in the structure restore tree that
+     *                                       we are going to add plugin information to
+     */
+    protected function add_plugin_structure($plugintype, $element) {
+
+        global $CFG;
+
+        // Check the requested plugintype is a valid one
+        if (!array_key_exists($plugintype, get_plugin_types($plugintype))) {
+             throw new restore_step_exception('incorrect_plugin_type', $plugintype);
+        }
+
+        // Get all the restore path elements, looking across all the plugin dirs
+        $pluginsdirs = get_plugin_list($plugintype);
+        foreach ($pluginsdirs as $name => $pluginsdir) {
+            // We need to add also backup plugin classes on restore, they may contain
+            // some stuff used both in backup & restore
+            $backupclassname = 'backup_' . $plugintype . '_' . $name . '_plugin';
+            $backupfile = $pluginsdir . '/backup/moodle2/' . $backupclassname . '.class.php';
+            if (file_exists($backupfile)) {
+                require_once($backupfile);
+            }
+            // Now add restore plugin classes and prepare stuff
+            $restoreclassname = 'restore_' . $plugintype . '_' . $name . '_plugin';
+            $restorefile = $pluginsdir . '/backup/moodle2/' . $restoreclassname . '.class.php';
+            if (file_exists($restorefile)) {
+                require_once($restorefile);
+                $restoreplugin = new $restoreclassname($plugintype, $name, $this);
+                // Add plugin paths to the step
+                $this->prepare_pathelements($restoreplugin->define_plugin_structure($element));
+            }
+        }
+    }
+
+    /**
+     * Launch all the after_execute methods present in all the processing objects
+     *
+     * This method will launch all the after_execute methods that can be defined
+     * both in restore_plugin and restore_structure_step classes
+     *
+     * For restore_plugin classes the name of the method to be executed will be
+     * "after_execute_" + connection point (as far as can be multiple connection
+     * points in the same class)
+     *
+     * For restore_structure_step classes is will be, simply, "after_execute". Note
+     * that this is executed *after* the plugin ones
+     */
+    protected function launch_after_execute_methods() {
+        $alreadylaunched = array(); // To avoid multiple executions
+        foreach ($this->pathelements as $key => $pathelement) {
+            // Get the processing object
+            $pobject = $pathelement->get_processing_object();
+            // Skip null processors (child of grouped ones for sure)
+            if (is_null($pobject)) {
+                continue;
+            }
+            // Skip restore structure step processors (this)
+            if ($pobject instanceof restore_structure_step) {
+                continue;
+            }
+            // Skip already launched processing objects
+            if (in_array($pobject, $alreadylaunched, true)) {
+                continue;
+            }
+            // Add processing object to array of launched ones
+            $alreadylaunched[] = $pobject;
+            // If the processing object has support for
+            // launching after_execute methods, use it
+            if (method_exists($pobject, 'launch_after_execute_methods')) {
+                $pobject->launch_after_execute_methods();
+            }
+        }
+        // Finally execute own (restore_structure_step) after_execute method
+        $this->after_execute();
+
+    }
+
     /**
      * This method will be executed after the whole structure step have been processed
      *
index 55330e0..b22cccd 100644 (file)
@@ -134,6 +134,10 @@ $string['moodleversion'] = 'Moodle version';
 $string['nomatchingcourses'] = 'There are no courses to display';
 $string['originalwwwroot'] = 'URL of backup';
 $string['previousstage'] = 'Previous';
+$string['qcategory2coursefallback'] = 'The questions category "{$a->name}", originally at system/course category context in backup file, will be created at course context by restore';
+$string['qcategorycannotberestored'] = 'The questions category "{$a->name}" cannot be created by restore';
+$string['question2coursefallback'] = 'The questions category "{$a->name}", originally at system/course category context in backup file, will be created at course context by restore';
+$string['questionegorycannotberestored'] = 'The questions "{$a->name}" cannot be created by restore';
 $string['restoreactivity'] = 'Restore activity';
 $string['restorecourse'] = 'Restore course';
 $string['restorecoursesettings'] = 'Course settings';
index 36bcf97..9eb48c2 100644 (file)
@@ -76,6 +76,11 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
             'userid', 'discussionid', 'postid', 'firstread',
             'lastread'));
 
+        $trackedprefs = new backup_nested_element('trackedprefs');
+
+        $track = new backup_nested_element('track', array('id'), array(
+            'userid'));
+
         // Build the tree
 
         $forum->add_child($discussions);
@@ -87,6 +92,9 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
         $forum->add_child($readposts);
         $readposts->add_child($read);
 
+        $forum->add_child($trackedprefs);
+        $trackedprefs->add_child($track);
+
         $discussion->add_child($posts);
         $posts->add_child($post);
 
@@ -115,6 +123,8 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
 
             $read->set_source_table('forum_read', array('forumid' => backup::VAR_PARENTID));
 
+            $track->set_source_table('forum_track_prefs', array('forumid' => backup::VAR_PARENTID));
+
             $rating->set_source_table('rating', array('contextid' => backup::VAR_CONTEXTID,
                                                       'itemid'    => backup::VAR_PARENTID));
             $rating->set_source_alias('rating', 'value');
@@ -136,6 +146,8 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
 
         $read->annotate_ids('user', 'userid');
 
+        $track->annotate_ids('user', 'userid');
+
         // Define file annotations
 
         $forum->annotate_files('mod_forum', 'intro', null); // This file area hasn't itemid
index 3f78f15..17e8820 100644 (file)
@@ -43,6 +43,7 @@ class restore_forum_activity_structure_step extends restore_activity_structure_s
             $paths[] = new restore_path_element('forum_rating', '/activity/forum/discussions/discussion/posts/post/ratings/rating');
             $paths[] = new restore_path_element('forum_subscription', '/activity/forum/subscriptions/subscription');
             $paths[] = new restore_path_element('forum_read', '/activity/forum/readposts/read');
+            $paths[] = new restore_path_element('forum_track', '/activity/forum/trackedprefs/track');
         }
 
         // Return the paths wrapped into standard activity structure
@@ -154,6 +155,18 @@ class restore_forum_activity_structure_step extends restore_activity_structure_s
         $newitemid = $DB->insert_record('forum_read', $data);
     }
 
+    protected function process_forum_track($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->forumid = $this->get_new_parentid('forum');
+        $data->userid = $this->get_mappingid('user', $data->userid);
+
+        $newitemid = $DB->insert_record('forum_track_prefs', $data);
+    }
+
     protected function after_execute() {
         global $DB;
 
index 4cc027a..7a05c5a 100644 (file)
@@ -44,7 +44,8 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
             'review', 'questionsperpage', 'shufflequestions', 'shuffleanswers',
             'questions', 'sumgrades', 'grade', 'timecreated',
             'timemodified', 'timelimit', 'password', 'subnet',
-            'popup', 'delay1', 'delay2', 'showuserpicture'));
+            'popup', 'delay1', 'delay2', 'showuserpicture',
+            'showblocks'));
 
         $qinstances = new backup_nested_element('question_instances');
 
@@ -115,7 +116,6 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
         if ($userinfo) {
             $grade->set_source_table('quiz_grades', array('quiz' => backup::VAR_PARENTID));
             $attempt->set_source_table('quiz_attempts', array('quiz' => backup::VAR_PARENTID));
-            // TODO: states and sessions go here
         }
 
         // Define source alias
@@ -128,7 +128,6 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
         $override->annotate_ids('group', 'groupid');
         $grade->annotate_ids('user', 'userid');
         $attempt->annotate_ids('user', 'userid');
-        // TODO: attempts, answers... anotations go here
 
         // Define file annotations
         $quiz->annotate_files('mod_quiz', 'intro', null); // This file area hasn't itemid
diff --git a/mod/quiz/backup/moodle2/restore_quiz_activity_task.class.php b/mod/quiz/backup/moodle2/restore_quiz_activity_task.class.php
new file mode 100644 (file)
index 0000000..bf79c7f
--- /dev/null
@@ -0,0 +1,77 @@
+<?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
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/quiz/backup/moodle2/restore_quiz_stepslib.php'); // Because it exists (must)
+
+/**
+ * quiz restore task that provides all the settings and steps to perform one
+ * complete restore of the activity
+ */
+class restore_quiz_activity_task extends restore_activity_task {
+
+    /**
+     * Define (add) particular settings this activity can have
+     */
+    protected function define_my_settings() {
+        // No particular settings for this activity
+    }
+
+    /**
+     * Define (add) particular steps this activity can have
+     */
+    protected function define_my_steps() {
+        // quiz only has one structure step
+        $this->add_step(new restore_quiz_activity_structure_step('quiz_structure', 'quiz.xml'));
+    }
+
+    /**
+     * Define the contents in the activity that must be
+     * processed by the link decoder
+     */
+    static public function define_decode_contents() {
+        $contents = array();
+
+        $contents[] = new restore_decode_content('quiz', array('intro'), 'quiz');
+        $contents[] = new restore_decode_content('quiz_feedback', array('feedbacktext'), 'quiz_feedback');
+
+        return $contents;
+    }
+
+    /**
+     * Define the decoding rules for links belonging
+     * to the activity to be executed by the link decoder
+     */
+    static public function define_decode_rules() {
+        $rules = array();
+
+        $rules[] = new restore_decode_rule('QUIZVIEWBYID', '/mod/quiz/view.php?id=$1', 'course_module');
+        $rules[] = new restore_decode_rule('QUIZVIEWBYQ', '/mod/quiz/view.php?q=$1', 'quiz');
+        $rules[] = new restore_decode_rule('QUIZINDEX', '/mod/quiz/index.php?id=$1', 'course');
+
+        return $rules;
+
+    }
+}
diff --git a/mod/quiz/backup/moodle2/restore_quiz_stepslib.php b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php
new file mode 100644 (file)
index 0000000..41e535d
--- /dev/null
@@ -0,0 +1,176 @@
+<?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 restore steps that will be used by the restore_quiz_activity_task
+ */
+
+/**
+ * Structure step to restore one quiz activity
+ */
+class restore_quiz_activity_structure_step extends restore_questions_activity_structure_step {
+
+    protected function define_structure() {
+
+        $paths = array();
+        $userinfo = $this->get_setting_value('userinfo');
+
+        $paths[] = new restore_path_element('quiz', '/activity/quiz');
+        $paths[] = new restore_path_element('quiz_question_instance', '/activity/quiz/question_instances/question_instance');
+        $paths[] = new restore_path_element('quiz_feedback', '/activity/quiz/feedbacks/feedback');
+        $paths[] = new restore_path_element('quiz_override', '/activity/quiz/overrides/override');
+        if ($userinfo) {
+            $paths[] = new restore_path_element('quiz_grade', '/activity/quiz/grades/grade');
+            $quizattempt = new restore_path_element('quiz_attempt', '/activity/quiz/attempts/attempt');
+            $paths[] = $quizattempt;
+            // Add states and sessions
+            $this->add_question_attempts_states($quizattempt, $paths);
+            $this->add_question_attempts_sessions($quizattempt, $paths);
+        }
+
+        // Return the paths wrapped into standard activity structure
+        return $this->prepare_activity_structure($paths);
+    }
+
+    protected function process_quiz($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+        $data->course = $this->get_courseid();
+
+        $data->timeopen = $this->apply_date_offset($data->timeopen);
+        $data->timeclose = $this->apply_date_offset($data->timeclose);
+        $data->timecreated = $this->apply_date_offset($data->timecreated);
+        $data->timemodified = $this->apply_date_offset($data->timemodified);
+
+        $data->questions = $this->questions_recode_layout($data->questions);
+
+        // insert the quiz record
+        $newitemid = $DB->insert_record('quiz', $data);
+        // immediately after inserting "activity" record, call this
+        $this->apply_activity_instance($newitemid);
+    }
+
+    protected function process_quiz_question_instance($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->quiz = $this->get_new_parentid('quiz');
+
+        $data->question = $this->get_mappingid('question', $data->question);
+
+        $DB->insert_record('quiz_question_instances', $data);
+    }
+
+    protected function process_quiz_feedback($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->quizid = $this->get_new_parentid('quiz');
+
+        $newitemid = $DB->insert_record('quiz_feedback', $data);
+        $this->set_mapping('quiz_feedback', $oldid, $newitemid, true); // Has related files
+    }
+
+    protected function process_quiz_override($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        // Based on userinfo, we'll restore user overides or no
+        $userinfo = $this->get_setting_value('userinfo');
+
+        // Skip user overrides if we are not restoring userinfo
+        if (!$userinfo && !is_null($data->userid)) {
+            return;
+        }
+
+        $data->quiz = $this->get_new_parentid('quiz');
+
+        $data->userid = $this->get_mappingid('user', $data->userid);
+        $data->groupid = $this->get_mappingid('group', $data->groupid);
+
+        $data->timeopen = $this->apply_date_offset($data->timeopen);
+        $data->timeclose = $this->apply_date_offset($data->timeclose);
+
+        $DB->insert_record('quiz_overrides', $data);
+    }
+
+    protected function process_quiz_grade($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $data->quiz = $this->get_new_parentid('quiz');
+
+        $data->userid = $this->get_mappingid('user', $data->userid);
+        $data->grade = $data->gradeval;
+
+        $data->timemodified = $this->apply_date_offset($data->timemodified);
+
+        $DB->insert_record('quiz_grades', $data);
+    }
+
+    protected function process_quiz_attempt($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+        $olduniqueid = $data->uniqueid;
+
+        $data->quiz = $this->get_new_parentid('quiz');
+        $data->attempt = $data->attemptnum;
+
+        $data->uniqueid = question_new_attempt_uniqueid('quiz');
+
+        $data->userid = $this->get_mappingid('user', $data->userid);
+
+        $data->timestart = $this->apply_date_offset($data->timestart);
+        $data->timefinish = $this->apply_date_offset($data->timefinish);
+        $data->timemodified = $this->apply_date_offset($data->timemodified);
+
+        $data->layout = $this->questions_recode_layout($data->layout);
+
+        $newitemid = $DB->insert_record('quiz_attempts', $data);
+
+        // Save quiz_attempt->uniqueid as quiz_attempt mapping, both question_states and
+        // question_sessions have Fk to it and not to quiz_attempts->id at all. In fact
+        // quiz_attempt->id isn't use by anybody
+        $this->set_mapping('quiz_attempt', $olduniqueid, $data->uniqueid, false);
+    }
+
+    protected function after_execute() {
+        // Add quiz related files, no need to match by itemname (just internally handled context)
+        $this->add_related_files('mod_quiz', 'intro', null);
+        // Add feedback related files, matching by itemname = 'quiz_feedback'
+        $this->add_related_files('mod_quiz', 'feedback', 'quiz_feedback');
+    }
+}
index b64053e..ad24dc0 100644 (file)
@@ -1,627 +1,4 @@
 <?php
-    //This php script contains all the stuff to restore quiz mods
-
-// Todo:
-
-    // whereever it says "/// We have to recode the .... field" we should put in a check
-    // to see if the recoding was successful and throw an appropriate error otherwise
-
-//This is the "graphical" structure of the quiz mod:
-    //To see, put your terminal to 160cc
-
-    //
-    //                           quiz
-    //                        (CL,pk->id)
-    //                            |
-    //           -----------------------------------------------
-    //           |                    |                        |
-    //           |               quiz_grades                   |
-    //           |           (UL,pk->id,fk->quiz)              |
-    //           |                                             |
-    //      quiz_attempts                          quiz_question_instances
-    //  (UL,pk->id,fk->quiz)                    (CL,pk->id,fk->quiz,question)
-    //
-    // Meaning: pk->primary key field of the table
-    //          fk->foreign key to link with parent
-    //          nt->nested field (recursive data)
-    //          SL->site level info
-    //          CL->course level info
-    //          UL->user level info
-    //          files->table may have files
-    //
-    //-----------------------------------------------------------
-
-    // When we restore a quiz we also need to restore the questions and possibly
-    // the data about student interaction with the questions. The functions to do
-    // that are included with the following library
-    include_once("$CFG->dirroot/question/restorelib.php");
-
-    function quiz_restore_mods($mod,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Hook to call Moodle < 1.5 Quiz Restore
-        if ($restore->backup_version < 2005043000) {
-            include_once("restorelibpre15.php");
-            return quiz_restore_pre15_mods($mod,$restore);
-        }
-
-        //Get record from backup_ids
-        $data = backup_getid($restore->backup_unique_code,$mod->modtype,$mod->id);
-
-        if ($data) {
-            //Now get completed xmlized object
-            $info = $data->info;
-            //if necessary, write to restorelog and adjust date/time fields
-            if ($restore->course_startdateoffset) {
-                restore_log_date_changes('Quiz', $restore, $info['MOD']['#'], array('TIMEOPEN', 'TIMECLOSE'));
-            }
-            //traverse_xmlize($info);                                                                     //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the QUIZ record structure
-            $quiz = new stdClass;
-            $quiz->course = $restore->course_id;
-            $quiz->name = backup_todb($info['MOD']['#']['NAME']['0']['#']);
-            $quiz->intro = backup_todb($info['MOD']['#']['INTRO']['0']['#']);
-            $quiz->timeopen = backup_todb($info['MOD']['#']['TIMEOPEN']['0']['#']);
-            $quiz->timeclose = backup_todb($info['MOD']['#']['TIMECLOSE']['0']['#']);
-            $quiz->optionflags = backup_todb($info['MOD']['#']['OPTIONFLAGS']['0']['#']);
-            $quiz->penaltyscheme = backup_todb($info['MOD']['#']['PENALTYSCHEME']['0']['#']);
-            $quiz->attempts = backup_todb($info['MOD']['#']['ATTEMPTS_NUMBER']['0']['#']);
-            $quiz->attemptonlast = backup_todb($info['MOD']['#']['ATTEMPTONLAST']['0']['#']);
-            $quiz->grademethod = backup_todb($info['MOD']['#']['GRADEMETHOD']['0']['#']);
-            $quiz->decimalpoints = backup_todb($info['MOD']['#']['DECIMALPOINTS']['0']['#']);
-            $quiz->review = backup_todb($info['MOD']['#']['REVIEW']['0']['#']);
-            $quiz->questionsperpage = backup_todb($info['MOD']['#']['QUESTIONSPERPAGE']['0']['#']);
-            $quiz->shufflequestions = backup_todb($info['MOD']['#']['SHUFFLEQUESTIONS']['0']['#']);
-            $quiz->shuffleanswers = backup_todb($info['MOD']['#']['SHUFFLEANSWERS']['0']['#']);
-            $quiz->questions = backup_todb($info['MOD']['#']['QUESTIONS']['0']['#']);
-            $quiz->sumgrades = backup_todb($info['MOD']['#']['SUMGRADES']['0']['#']);
-            $quiz->grade = backup_todb($info['MOD']['#']['GRADE']['0']['#']);
-            $quiz->timecreated = backup_todb($info['MOD']['#']['TIMECREATED']['0']['#']);
-            $quiz->timemodified = backup_todb($info['MOD']['#']['TIMEMODIFIED']['0']['#']);
-            if (isset($info['MOD']['#']['TIMELIMITSECS']['0']['#'])) {
-                $quiz->timelimit = backup_todb($info['MOD']['#']['TIMELIMITSECS']['0']['#']);
-            } else {
-                $quiz->timelimit = backup_todb($info['MOD']['#']['TIMELIMIT']['0']['#']) * 60;
-            }
-            $quiz->password = backup_todb($info['MOD']['#']['PASSWORD']['0']['#']);
-            $quiz->subnet = backup_todb($info['MOD']['#']['SUBNET']['0']['#']);
-            $quiz->popup = backup_todb($info['MOD']['#']['POPUP']['0']['#']);
-            $quiz->delay1 = isset($info['MOD']['#']['DELAY1']['0']['#'])?backup_todb($info['MOD']['#']['DELAY1']['0']['#']):'';
-            $quiz->delay2 = isset($info['MOD']['#']['DELAY2']['0']['#'])?backup_todb($info['MOD']['#']['DELAY2']['0']['#']):'';
-            //We have to recode the questions field (a list of questions id and pagebreaks)
-            $quiz->questions = quiz_recode_layout($quiz->questions, $restore);
-
-            //The structure is equal to the db, so insert the quiz
-            $newid = $DB->insert_record ("quiz",$quiz);
-
-            //Do some output
-            if (!defined('RESTORE_SILENTLY')) {
-                echo "<li>".get_string("modulename","quiz")." \"".format_string($quiz->name,true)."\"</li>";
-            }
-            backup_flush(300);
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,$mod->modtype,
-                             $mod->id, $newid);
-                //We have to restore the question_instances now (course level table)
-                $status = quiz_question_instances_restore_mods($newid,$info,$restore);
-                //We have to restore the feedback now (course level table)
-                $status = quiz_feedback_restore_mods($newid, $info, $restore, $quiz);
-                //We have to restore the overrides now (course level table)
-                $status = quiz_overrides_restore_mods($newid, $info, $restore, $quiz);
-                //Now check if want to restore user data and do it.
-                if (restore_userdata_selected($restore,'quiz',$mod->id)) {
-                    //Restore quiz_attempts
-                    $status = quiz_attempts_restore_mods ($newid,$info,$restore);
-                    if ($status) {
-                        //Restore quiz_grades
-                        $status = quiz_grades_restore_mods ($newid,$info,$restore);
-                    }
-                }
-            } else {
-                $status = false;
-            }
-        } else {
-            $status = false;
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_question_instances
-    function quiz_question_instances_restore_mods($quiz_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the quiz_question_instances array
-        if (array_key_exists('QUESTION_INSTANCES', $info['MOD']['#'])) {
-            $instances = $info['MOD']['#']['QUESTION_INSTANCES']['0']['#']['QUESTION_INSTANCE'];
-        } else {
-            $instances = array();
-        }
-
-        //Iterate over question_instances
-        for($i = 0; $i < sizeof($instances); $i++) {
-            $gra_info = $instances[$i];
-            //traverse_xmlize($gra_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($gra_info['#']['ID']['0']['#']);
-
-            //Now, build the QUESTION_INSTANCES record structure
-            $instance = new stdClass;
-            $instance->quiz = $quiz_id;
-            $instance->question = backup_todb($gra_info['#']['QUESTION']['0']['#']);
-            $instance->grade = backup_todb($gra_info['#']['GRADE']['0']['#']);
-
-            //We have to recode the question field
-            $question = backup_getid($restore->backup_unique_code,"question",$instance->question);
-            if ($question) {
-                $instance->question = $question->new_id;
-            }
-
-            //The structure is equal to the db, so insert the quiz_question_instances
-            $newid = $DB->insert_record ("quiz_question_instances",$instance);
-
-            //Do some output
-            if (($i+1) % 10 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 200 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"quiz_question_instances",$oldid,
-                             $newid);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_question_instances
-    function quiz_feedback_restore_mods($quiz_id, $info, $restore, $quiz) {
-        global $DB;
-
-        $status = true;
-
-        //Get the quiz_feedback array
-        if (array_key_exists('FEEDBACKS', $info['MOD']['#'])) {
-            $feedbacks = $info['MOD']['#']['FEEDBACKS']['0']['#']['FEEDBACK'];
-
-            //Iterate over the feedbacks
-            foreach ($feedbacks as $feedback_info) {
-                //traverse_xmlize($feedback_info);                                                            //Debug
-                //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-                //$GLOBALS['traverse_array']="";                                                              //Debug
-
-                //We'll need this later!!
-                $oldid = backup_todb($feedback_info['#']['ID']['0']['#']);
-
-                //Now, build the quiz_feedback record structure
-                $feedback = new stdClass();
-                $feedback->quizid = $quiz_id;
-                $feedback->feedbacktext = backup_todb($feedback_info['#']['FEEDBACKTEXT']['0']['#']);
-                $feedback->mingrade = backup_todb($feedback_info['#']['MINGRADE']['0']['#']);
-                $feedback->maxgrade = backup_todb($feedback_info['#']['MAXGRADE']['0']['#']);
-
-                //The structure is equal to the db, so insert the quiz_question_instances
-                $newid = $DB->insert_record('quiz_feedback', $feedback);
-
-                if ($newid) {
-                    //We have the newid, update backup_ids
-                    backup_putid($restore->backup_unique_code, 'quiz_feedback', $oldid, $newid);
-                } else {
-                    $status = false;
-                }
-            }
-        } else {
-            $feedback = new stdClass();
-            $feedback->quizid = $quiz_id;
-            $feedback->feedbacktext = '';
-            $feedback->mingrade = 0;
-            $feedback->maxgrade = $quiz->grade + 1;
-            $DB->insert_record('quiz_feedback', $feedback);
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_overrides
-    function quiz_overrides_restore_mods($quiz_id, $info, $restore, $quiz) {
-        global $DB;
-
-        $douserdata = restore_userdata_selected($restore,'quiz',$quiz_id);
-
-        $status = true;
-
-        //Get the quiz_feedback array
-        if (array_key_exists('OVERRIDES', $info['MOD']['#'])) {
-            $overrides = $info['MOD']['#']['OVERRIDES']['0']['#']['OVERRIDE'];
-
-            //Iterate over the feedbacks
-            foreach ($overrides as $override_info) {
-                //traverse_xmlize($override_info);                                                            //Debug
-                //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-                //$GLOBALS['traverse_array']="";                                                              //Debug
-
-                //We'll need this later!!
-                $oldid = backup_todb($override_info['#']['ID']['0']['#']);
-
-                //Now, build the quiz_overrides record structure
-                $override = new stdClass();
-                $override->quiz = $quiz_id;
-                $override->groupid = backup_todb($override_info['#']['GROUPID']['0']['#']);
-                $override->userid = backup_todb($override_info['#']['USERID']['0']['#']);
-                $override->timeopen = backup_todb($override_info['#']['TIMEOPEN']['0']['#']);
-                $override->timeclose = backup_todb($override_info['#']['TIMECLOSE']['0']['#']);
-                $override->timelimit = backup_todb($override_info['#']['TIMELIMIT']['0']['#']);
-                $override->attempts = backup_todb($override_info['#']['ATTEMPTS']['0']['#']);
-                $override->password = backup_todb($override_info['#']['PASSWORD']['0']['#']);
-
-                // Only restore user overrides if we are restoring user data
-                if ($douserdata || $override->userid == 0) {
-
-                    //We have to recode the userid field
-                    if ($override->userid) {
-                        if (!$user = backup_getid($restore->backup_unique_code,"user",$override->userid)) {
-                            debugging("override can not be restored, user id $override->userid not present in backup");
-                            // do not not block the restore
-                            continue;
-                        }
-                        $override->userid = $user->new_id;
-
-                    }
-
-                    //We have to recode the groupid field
-                    if ($override->groupid) {
-                        if (!$group = backup_getid($restore->backup_unique_code,"groups",$override->groupid)) {
-                            debugging("override can not be restored, group id $override->groupid not present in backup");
-                            // do not not block the restore
-                            continue;
-                        }
-                        $override->groupid = $group->new_id;
-                    }
-
-                    //The structure is equal to the db, so insert the quiz_question_instances
-                    $newid = $DB->insert_record('quiz_overrides', $override);
-
-                    if ($newid) {
-                        //We have the newid, update backup_ids
-                        backup_putid($restore->backup_unique_code, 'quiz_overrides', $oldid, $newid);
-                    } else {
-                        $status = false;
-                    }
-                }
-            }
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_attempts
-    function quiz_attempts_restore_mods($quiz_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the quiz_attempts array
-        if (array_key_exists('ATTEMPTS', $info['MOD']['#'])) {
-            $attempts = $info['MOD']['#']['ATTEMPTS']['0']['#']['ATTEMPT'];
-        } else {
-            $attempts = array();
-        }
-
-        //Iterate over attempts
-        for($i = 0; $i < sizeof($attempts); $i++) {
-            $att_info = $attempts[$i];
-            //traverse_xmlize($att_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($att_info['#']['ID']['0']['#']);
-            $olduserid = backup_todb($att_info['#']['USERID']['0']['#']);
-
-            //Now, build the ATTEMPTS record structure
-            $attempt = new stdClass;
-            $attempt->quiz = $quiz_id;
-            $attempt->userid = backup_todb($att_info['#']['USERID']['0']['#']);
-            $attempt->attempt = backup_todb($att_info['#']['ATTEMPTNUM']['0']['#']);
-            $attempt->sumgrades = backup_todb($att_info['#']['SUMGRADES']['0']['#']);
-            $attempt->timestart = backup_todb($att_info['#']['TIMESTART']['0']['#']);
-            $attempt->timefinish = backup_todb($att_info['#']['TIMEFINISH']['0']['#']);
-            $attempt->timemodified = backup_todb($att_info['#']['TIMEMODIFIED']['0']['#']);
-            $attempt->layout = backup_todb($att_info['#']['LAYOUT']['0']['#']);
-            $attempt->preview = backup_todb($att_info['#']['PREVIEW']['0']['#']);
-
-            //We have to recode the userid field
-            $user = backup_getid($restore->backup_unique_code,"user",$attempt->userid);
-            if ($user) {
-                $attempt->userid = $user->new_id;
-            }
-
-            //Set the uniqueid field
-            $attempt->uniqueid = question_new_attempt_uniqueid();
-
-            //We have to recode the layout field (a list of questions id and pagebreaks)
-            $attempt->layout = quiz_recode_layout($attempt->layout, $restore);
-
-            //The structure is equal to the db, so insert the quiz_attempts
-            $newid = $DB->insert_record ("quiz_attempts",$attempt);
-
-            //Do some output
-            if (($i+1) % 10 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 200 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"quiz_attempts",$oldid,
-                             $newid);
-                //Now process question_states
-                // This function is defined in question/restorelib.php
-                $status = question_states_restore_mods($attempt->uniqueid,$att_info,$restore);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_grades
-    function quiz_grades_restore_mods($quiz_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the quiz_grades array
-        if (array_key_exists('GRADES', $info['MOD']['#'])) {
-            $grades = $info['MOD']['#']['GRADES']['0']['#']['GRADE'];
-        } else {
-            $grades = array();
-        }
-
-        //Iterate over grades
-        for($i = 0; $i < sizeof($grades); $i++) {
-            $gra_info = $grades[$i];
-            //traverse_xmlize($gra_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($gra_info['#']['ID']['0']['#']);
-            $olduserid = backup_todb($gra_info['#']['USERID']['0']['#']);
-
-            //Now, build the GRADES record structure
-            $grade = new stdClass;
-            $grade->quiz = $quiz_id;
-            $grade->userid = backup_todb($gra_info['#']['USERID']['0']['#']);
-            $grade->grade = backup_todb($gra_info['#']['GRADEVAL']['0']['#']);
-            $grade->timemodified = backup_todb($gra_info['#']['TIMEMODIFIED']['0']['#']);
-
-            //We have to recode the userid field
-            $user = backup_getid($restore->backup_unique_code,"user",$grade->userid);
-            if ($user) {
-                $grade->userid = $user->new_id;
-            }
-
-            //The structure is equal to the db, so insert the quiz_grades
-            $newid = $DB->insert_record ("quiz_grades",$grade);
-
-            //Do some output
-            if (($i+1) % 10 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 200 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"quiz_grades",$oldid,
-                             $newid);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //Return a content decoded to support interactivities linking. Every module
-    //should have its own. They are called automatically from
-    //quiz_decode_content_links_caller() function in each module
-    //in the restore process
-    function quiz_decode_content_links ($content,$restore) {
-        global $CFG;
-
-        $result = $content;
-
-        //Link to the list of quizs
-
-        $searchstring='/\$@(QUIZINDEX)\*([0-9]+)@\$/';
-        //We look for it
-        preg_match_all($searchstring,$content,$foundset);
-        //If found, then we are going to look for its new id (in backup tables)
-        if ($foundset[0]) {
-            //print_object($foundset);                                     //Debug
-            //Iterate over foundset[2]. They are the old_ids
-            foreach($foundset[2] as $old_id) {
-                //We get the needed variables here (course id)
-                $rec = backup_getid($restore->backup_unique_code,"course",$old_id);
-                //Personalize the searchstring
-                $searchstring='/\$@(QUIZINDEX)\*('.$old_id.')@\$/';
-                //If it is a link to this course, update the link to its new location
-                if($rec->new_id) {
-                    //Now replace it
-                    $result= preg_replace($searchstring,$CFG->wwwroot.'/mod/quiz/index.php?id='.$rec->new_id,$result);
-                } else {
-                    //It's a foreign link so leave it as original
-                    $result= preg_replace($searchstring,$restore->original_wwwroot.'/mod/quiz/index.php?id='.$old_id,$result);
-                }
-            }
-        }
-
-        //Link to quiz view by moduleid
-        $searchstring='/\$@(QUIZVIEWBYID)\*([0-9]+)@\$/';
-        //We look for it
-        preg_match_all($searchstring,$result,$foundset);
-        //If found, then we are going to look for its new id (in backup tables)
-        if ($foundset[0]) {
-            //print_object($foundset);                                     //Debug
-            //Iterate over foundset[2]. They are the old_ids
-            foreach($foundset[2] as $old_id) {
-                //We get the needed variables here (course_modules id)
-                $rec = backup_getid($restore->backup_unique_code,"course_modules",$old_id);
-                //Personalize the searchstring
-                $searchstring='/\$@(QUIZVIEWBYID)\*('.$old_id.')@\$/';
-                //If it is a link to this course, update the link to its new location
-                if($rec->new_id) {
-                    //Now replace it
-                    $result= preg_replace($searchstring,$CFG->wwwroot.'/mod/quiz/view.php?id='.$rec->new_id,$result);
-                } else {
-                    //It's a foreign link so leave it as original
-                    $result= preg_replace($searchstring,$restore->original_wwwroot.'/mod/quiz/view.php?id='.$old_id,$result);
-                }
-            }
-        }
-
-        //Link to quiz view by quizid
-        $searchstring='/\$@(QUIZVIEWBYQ)\*([0-9]+)@\$/';
-        //We look for it
-        preg_match_all($searchstring,$result,$foundset);
-        //If found, then we are going to look for its new id (in backup tables)
-        if ($foundset[0]) {
-            //print_object($foundset);                                     //Debug
-            //Iterate over foundset[2]. They are the old_ids
-            foreach($foundset[2] as $old_id) {
-                //We get the needed variables here (course_modules id)
-                $rec = backup_getid($restore->backup_unique_code,'quiz',$old_id);
-                //Personalize the searchstring
-                $searchstring='/\$@(QUIZVIEWBYQ)\*('.$old_id.')@\$/';
-                //If it is a link to this course, update the link to its new location
-                if($rec->new_id) {
-                    //Now replace it
-                    $result= preg_replace($searchstring,$CFG->wwwroot.'/mod/quiz/view.php?q='.$rec->new_id,$result);
-                } else {
-                    //It's a foreign link so leave it as original
-                    $result= preg_replace($searchstring,$restore->original_wwwroot.'/mod/quiz/view.php?q='.$old_id,$result);
-                }
-            }
-        }
-
-        return $result;
-    }
-
-    //This function makes all the necessary calls to xxxx_decode_content_links()
-    //function in each module, passing them the desired contents to be decoded
-    //from backup format to destination site/course in order to mantain inter-activities
-    //working in the backup/restore process. It's called from restore_decode_content_links()
-    //function in restore process
-    function quiz_decode_content_links_caller($restore) {
-        global $CFG, $DB;
-        $status = true;
-
-        if ($quizs = $DB->get_records('quiz', array('course'=>$restore->course_id), '', "id,intro")) {
-                                               //Iterate over each quiz->intro
-            $i = 0;   //Counter to send some output to the browser to avoid timeouts
-            foreach ($quizs as $quiz) {
-                //Increment counter
-                $i++;
-                $content = $quiz->intro;
-                $result = restore_decode_content_links_worker($content,$restore);
-                if ($result != $content) {
-                    //Update record
-                    $quiz->intro = $result;
-                    $DB->update_record("quiz",$quiz);
-                    if (debugging()) {
-                        if (!defined('RESTORE_SILENTLY')) {
-                            echo '<br /><hr />'.s($content).'<br />changed to<br />'.s($result).'<hr /><br />';
-                        }
-                    }
-                }
-                //Do some output
-                if (($i+1) % 5 == 0) {
-                    if (!defined('RESTORE_SILENTLY')) {
-                        echo ".";
-                        if (($i+1) % 100 == 0) {
-                            echo "<br />";
-                        }
-                    }
-                    backup_flush(300);
-                }
-            }
-        }
-
-        return $status;
-    }
-
-    //This function converts texts in FORMAT_WIKI to FORMAT_MARKDOWN for
-    //some texts in the module
-    function quiz_restore_wiki2markdown ($restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Convert question->questiontext
-        if ($records = $DB->get_records_sql("SELECT q.id, q.questiontext, q.questiontextformat
-                                               FROM {question} q,
-                                                    {backup_ids} b
-                                              WHERE b.backup_code = ? AND
-                                                    b.table_name = 'question' AND
-                                                    q.id = b.new_id AND
-                                                    q.questiontextformat = ".FORMAT_WIKI, array($restore->backup_unique_code))) {
-            $i = 0;
-            foreach ($records as $record) {
-                //Rebuild wiki links
-                $record->questiontext = restore_decode_wiki_content($record->questiontext, $restore);
-                //Convert to Markdown
-                $wtm = new WikiToMarkdown();
-                $record->questiontext = $wtm->convert($record->questiontext, $restore->course_id);
-                $record->questiontextformat = FORMAT_MARKDOWN;
-                $DB->update_record('question', $record);
-                //Do some output
-                $i++;
-                if (($i+1) % 1 == 0) {
-                    if (!defined('RESTORE_SILENTLY')) {
-                        echo ".";
-                        if (($i+1) % 20 == 0) {
-                            echo "<br />";
-                        }
-                    }
-                    backup_flush(300);
-                }
-            }
-        }
-        return $status;
-    }
 
     //This function returns a log record with all the necessary transformations
     //done. It's used by restore_log_module() to restore modules log.
         }
         return $status;
     }
-
-    function quiz_recode_layout($layout, $restore) {
-        //Recodes the quiz layout (a list of questions id and pagebreaks)
-
-        //Extracts question id from sequence
-        if ($questionids = explode(',', $layout)) {
-            foreach ($questionids as $id => $questionid) {
-                if ($questionid) { // If it is zero then this is a pagebreak, don't translate
-                    $newq = backup_getid($restore->backup_unique_code,"question",$questionid);
-                    $questionids[$id] = $newq->new_id;
-                }
-            }
-        }
-        return implode(',', $questionids);
-    }
-
-
diff --git a/mod/quiz/restorelibpre15.php b/mod/quiz/restorelibpre15.php
deleted file mode 100644 (file)
index 78061fa..0000000
+++ /dev/null
@@ -1,1907 +0,0 @@
-<?php
-    //This php script contains all the stuff to backup/restore
-    //quiz mods
-
-    //To see, put your terminal to 160cc
-
-    //This is the "graphical" structure of the quiz mod:
-    //
-    //                           quiz                                                      question_categories
-    //                        (CL,pk->id)                                                   (CL,pk->id)
-    //                            |                                                              |
-    //           -----------------------------------------------                                 |
-    //           |                        |                    |                                 |.......................................
-    //           |                        |                    |                                 |                                      .
-    //           |                        |                    |                                 |                                      .
-    //      quiz_attempts        quiz_grades       quiz_question_grades                          |    ----question_datasets----    .
-    // (UL,pk->id, fk->quiz) (UL,pk->id,fk->quiz)  (CL,pk->id,fk->quiz)                          |    |  (CL,pk->id,fk->question,  |    .
-    //             |                                              |                              |    |   fk->dataset_definition)  |    .
-    //             |                                              |                              |    |                            |    .
-    //             |                                              |                              |    |                            |    .
-    //             |                                              |                              |    |                            |    .
-    //       quiz_responses                                       |                      question                       question_dataset_definitions
-    //  (UL,pk->id, fk->attempt)----------------------------------------------------(CL,pk->id,fk->category,files)            (CL,pk->id,fk->category)
-    //                                                                                           |                                      |
-    //                                                                                           |                                      |
-    //                                                                                           |                                      |
-    //                                                                                           |                               question_dataset_items
-    //                                                                                           |                            (CL,pk->id,fk->definition)
-    //                                                                                           |
-    //                                                                                           |
-    //                                                                                           |
-    //             --------------------------------------------------------------------------------------------------------------
-    //             |             |              |              |                       |                  |                     |
-    //             |             |              |              |                       |                  |                     |
-    //             |             |              |              |                 question_calculated          |                     |    question_randomsamatch
-    //      question_truefalse       |       question_multichoice      |             (CL,pl->id,fk->question)     |                     |--(CL,pl->id,fk->question)
-    // (CL,pl->id,fk->question)  |   (CL,pl->id,fk->question)  |                       .                  |                     |
-    //             .             |              .              |                       .                  |                     |
-    //             .      question_shortanswer      .       question_numerical                 .            question_multianswer.           |
-    //             .  (CL,pl->id,fk->question)  .  (CL,pl->id,fk->question)            .        (CL,pl->id,fk->question)        |         question_match
-    //             .             .              .              .                       .                  .                     |--(CL,pl->id,fk->question)
-    //             .             .              .              .                       .                  .                     |             .
-    //             .             .              .              .                       .                  .                     |             .
-    //             .             .              .              .                       .                  .                     |             .
-    //             .             .              .              .                       .                  .                     |       question_match_sub
-    //             .             .              .              .                       .                  .                     |--(CL,pl->id,fk->question)
-    //             ........................................................................................                     |
-    //                                                   .                                                                      |
-    //                                                   .                                                                      |
-    //                                                   .                                                                      |    question_numerical_units
-    //                                                question_answers                                                              |--(CL,pl->id,fk->question)
-    //                                         (CL,pk->id,fk->question)----------------------------------------------------------
-    //
-    // Meaning: pk->primary key field of the table
-    //          fk->foreign key to link with parent
-    //          nt->nested field (recursive data)
-    //          CL->course level info
-    //          UL->user level info
-    //          files->table may have files
-    //
-    //-----------------------------------------------------------
-
-    //This module is special, because we make the restore in two steps:
-    // 1.-We restore every category and their questions (complete structure). It includes this tables:
-    //     - question_categories
-    //     - question
-    //     - question_truefalse
-    //     - question_shortanswer
-    //     - question_multianswer
-    //     - question_multichoice
-    //     - question_numerical
-    //     - question_randomsamatch
-    //     - question_match
-    //     - question_match_sub
-    //     - question_calculated
-    //     - question_answers
-    //     - question_numerical_units
-    //     - question_datasets
-    //     - question_dataset_definitions
-    //     - question_dataset_items
-    //    All this backup info have its own section in moodle.xml (QUESTION_CATEGORIES) and it's generated
-    //    before every module backup standard invocation. And only if to restore quizzes has been selected !!
-    //    It's invoked with quiz_restore_question_categories. (course independent).
-
-    // 2.-Standard module restore (Invoked via quiz_restore_mods). It includes this tables:
-    //     - quiz
-    //     - quiz_question_grades
-    //     - quiz_attempts
-    //     - quiz_grades
-    //     - quiz_responses
-    //    This step is the standard mod backup. (course dependent).
-
-    //We are going to nedd quiz libs to be able to mimic the upgrade process
-    require_once("$CFG->dirroot/mod/quiz/locallib.php");
-
-    //STEP 1. Restore categories/questions and associated structures
-    //    (course independent)
-    function quiz_restore_pre15_question_categories($category,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get record from backup_ids
-        $data = backup_getid($restore->backup_unique_code,"question_categories",$category->id);
-
-        if ($data) {
-            //Now get completed xmlized object
-            $info = $data->info;
-            //traverse_xmlize($info);                                                                     //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_categories record structure
-            $quiz_cat->course = $restore->course_id;
-            $quiz_cat->name = backup_todb($info['QUESTION_CATEGORY']['#']['NAME']['0']['#']);
-            $quiz_cat->info = backup_todb($info['QUESTION_CATEGORY']['#']['INFO']['0']['#']);
-            $quiz_cat->publish = backup_todb($info['QUESTION_CATEGORY']['#']['PUBLISH']['0']['#']);
-            $quiz_cat->stamp = backup_todb($info['QUESTION_CATEGORY']['#']['STAMP']['0']['#']);
-            $quiz_cat->parent = backup_todb($info['QUESTION_CATEGORY']['#']['PARENT']['0']['#']);
-            $quiz_cat->sortorder = backup_todb($info['QUESTION_CATEGORY']['#']['SORTORDER']['0']['#']);
-
-            if ($catfound = restore_get_best_question_category($quiz_cat, $restore->course_id)) {
-                $newid = $catfound;
-            } else {
-                if (!$quiz_cat->stamp) {
-                    $quiz_cat->stamp = make_unique_id_code();
-                }
-                $newid = $DB->insert_record ("question_categories",$quiz_cat);
-            }
-
-            //Do some output
-            if ($newid) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo "<li>".get_string('category', 'quiz')." \"".$quiz_cat->name."\"<br />";
-                }
-            } else {
-                if (!defined('RESTORE_SILENTLY')) {
-                    //We must never arrive here !!
-                    echo "<li>".get_string('category', 'quiz')." \"".$quiz_cat->name."\" Error!<br />";
-                }
-                $status = false;
-            }
-            backup_flush(300);
-
-            //Here category has been created or selected, so save results in backup_ids and start with questions
-            if ($newid and $status) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"question_categories",
-                             $category->id, $newid);
-                //Now restore question
-                $status = quiz_restore_pre15_questions ($category->id, $newid,$info,$restore);
-            } else {
-                $status = false;
-            }
-            if (!defined('RESTORE_SILENTLY')) {
-                echo '</li>';
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_questions ($old_category_id,$new_category_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the questions array
-        $questions = $info['QUESTION_CATEGORY']['#']['QUESTIONS']['0']['#']['QUESTION'];
-
-        //Iterate over questions
-        for($i = 0; $i < sizeof($questions); $i++) {
-            $question = new stdClass();
-            $que_info = $questions[$i];
-            //traverse_xmlize($que_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($que_info['#']['ID']['0']['#']);
-
-            //Now, build the question record structure
-            $question->category = $new_category_id;
-            $question->parent = backup_todb($que_info['#']['PARENT']['0']['#']);
-            $question->name = backup_todb($que_info['#']['NAME']['0']['#']);
-            $question->questiontext = backup_todb($que_info['#']['QUESTIONTEXT']['0']['#']);
-            $question->questiontextformat = backup_todb($que_info['#']['QUESTIONTEXTFORMAT']['0']['#']);
-            $question->image = backup_todb($que_info['#']['IMAGE']['0']['#']);
-            $question->defaultgrade = backup_todb($que_info['#']['DEFAULTGRADE']['0']['#']);
-            if (isset($que_info['#']['PENALTY']['0']['#'])) { //Only if it's set, to apply DB default else.
-                $question->penalty = backup_todb($que_info['#']['PENALTY']['0']['#']);
-            }
-            $question->qtype = backup_todb($que_info['#']['QTYPE']['0']['#']);
-            if (isset($que_info['#']['LENGTH']['0']['#'])) { //Only if it's set, to apply DB default else.
-                $question->length = backup_todb($que_info['#']['LENGTH']['0']['#']);
-            }
-            $question->stamp = backup_todb($que_info['#']['STAMP']['0']['#']);
-            if (isset($que_info['#']['VERSION']['0']['#'])) { //Only if it's set, to apply DB default else.
-                $question->version = backup_todb($que_info['#']['VERSION']['0']['#']);
-            }
-            if (isset($que_info['#']['HIDDEN']['0']['#'])) { //Only if it's set, to apply DB default else.
-                $question->hidden = backup_todb($que_info['#']['HIDDEN']['0']['#']);
-            }
-
-            //Although only a few backups can have questions with parent, we try to recode it
-            //if it contains something
-            if ($question->parent and $parent = backup_getid($restore->backup_unique_code,"question",$question->parent)) {
-                $question->parent = $parent->new_id;
-            }
-
-            // If it is a random question then hide it
-            if ($question->qtype == RANDOM) {
-                $question->hidden = 1;
-            }
-
-            //If it is a description question, length = 0
-            if ($question->qtype == DESCRIPTION) {
-                $question->length = 0;
-            }
-
-            //Check if the question exists
-            //by category and stamp
-            $question_exists = $DB->get_record ("question", array("category"=>$question->category,
-                                                            "stamp"=>$question->stamp));
-            //If the stamp doesn't exists, check if question exists
-            //by category, name and questiontext and calculate stamp
-            //Mantains pre Beta 1.1 compatibility !!
-            if (!$question->stamp) {
-                $question->stamp = make_unique_id_code();
-                $question->version = 1;
-                $question_exists = $DB->get_record ("question", array("category"=>$question->category,
-                                                                "name"=>$question->name,
-                                                                "questiontext"=>$question->questiontext));
-            }
-
-            //If the question exists, only record its id
-            if ($question_exists) {
-                $newid = $question_exists->id;
-                $creatingnewquestion = false;
-            //Else, create a new question
-            } else {
-                //The structure is equal to the db, so insert the question
-                $newid = $DB->insert_record ("question",$question);
-                //If it is a random question, parent = id
-                if ($newid && $question->qtype == RANDOM) {
-                    $DB->set_field ('question', 'parent', $newid, array('id'=>$newid));
-                }
-                $creatingnewquestion = true;
-            }
-
-            //Do some output
-            if (($i+1) % 2 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 40 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-            //Save newid to backup tables
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"question",$oldid,
-                             $newid);
-            }
-            //If it's a new question in the DB, restore it
-            if ($creatingnewquestion) {
-                //Now, restore every question_answers in this question
-                $status = quiz_restore_pre15_answers($oldid,$newid,$que_info,$restore);
-                //Now, depending of the type of questions, invoke different functions
-                if ($question->qtype == "1") {
-                    $status = quiz_restore_pre15_shortanswer($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "2") {
-                    $status = quiz_restore_pre15_truefalse($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "3") {
-                    $status = quiz_restore_pre15_multichoice($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "4") {
-                    //Random question. Nothing to do.
-                } else if ($question->qtype == "5") {
-                    $status = quiz_restore_pre15_match($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "6") {
-                    $status = quiz_restore_pre15_randomsamatch($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "7") {
-                    //Description question. Nothing to do.
-                } else if ($question->qtype == "8") {
-                    $status = quiz_restore_pre15_numerical($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "9") {
-                    $status = quiz_restore_pre15_multianswer($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "10") {
-                    $status = quiz_restore_pre15_calculated($oldid,$newid,$que_info,$restore);
-                }
-            } else {
-                //We are NOT creating the question, but we need to know every question_answers
-                //map between the XML file and the database to be able to restore the responses
-                //in each attempt.
-                $status = quiz_restore_pre15_map_answers($oldid,$newid,$que_info,$restore);
-                //Now, depending of the type of questions, invoke different functions
-                //to create the necessary mappings in backup_ids, because we are not
-                //creating the question, but need some records in backup table
-                if ($question->qtype == "1") {
-                    //Shortanswer question. Nothing to remap
-                } else if ($question->qtype == "2") {
-                    //Truefalse question. Nothing to remap
-                } else if ($question->qtype == "3") {
-                    //Multichoice question. Nothing to remap
-                } else if ($question->qtype == "4") {
-                    //Random question. Nothing to remap
-                } else if ($question->qtype == "5") {
-                    $status = quiz_restore_pre15_map_match($oldid,$newid,$que_info,$restore);
-                } else if ($question->qtype == "6") {
-                    //Randomsamatch question. Nothing to remap
-                } else if ($question->qtype == "7") {
-                    //Description question. Nothing to remap
-                } else if ($question->qtype == "8") {
-                    //Numerical question. Nothing to remap
-                } else if ($question->qtype == "9") {
-                    //Multianswer question. Nothing to remap
-                } else if ($question->qtype == "10") {
-                    //Calculated question. Nothing to remap
-                }
-            }
-        }
-        return $status;
-    }
-
-    function quiz_restore_pre15_answers ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the answers array
-        if (isset($info['#']['ANSWERS']['0']['#']['ANSWER'])) {
-            $answers = $info['#']['ANSWERS']['0']['#']['ANSWER'];
-
-            //Iterate over answers
-            for($i = 0; $i < sizeof($answers); $i++) {
-                $ans_info = $answers[$i];
-                //traverse_xmlize($ans_info);                                                                 //Debug
-                //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-                //$GLOBALS['traverse_array']="";                                                              //Debug
-
-                //We'll need this later!!
-                $oldid = backup_todb($ans_info['#']['ID']['0']['#']);
-
-                //Now, build the question_answers record structure
-                $answer->question = $new_question_id;
-                $answer->answer = backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']);
-                $answer->fraction = backup_todb($ans_info['#']['FRACTION']['0']['#']);
-                $answer->feedback = backup_todb($ans_info['#']['FEEDBACK']['0']['#']);
-
-                //The structure is equal to the db, so insert the question_answers
-                $newid = $DB->insert_record ("question_answers",$answer);
-
-                //Do some output
-                if (($i+1) % 50 == 0) {
-                    if (!defined('RESTORE_SILENTLY')) {
-                        echo ".";
-                        if (($i+1) % 1000 == 0) {
-                            echo "<br />";
-                        }
-                    }
-                    backup_flush(300);
-                }
-
-                if ($newid) {
-                    //We have the newid, update backup_ids
-                    backup_putid($restore->backup_unique_code,"question_answers",$oldid,
-                                 $newid);
-                } else {
-                    $status = false;
-                }
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_map_answers ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        if (!isset($info['#']['ANSWERS'])) {    // No answers in this question (eg random)
-            return $status;
-        }
-
-        //Get the answers array
-        $answers = $info['#']['ANSWERS']['0']['#']['ANSWER'];
-
-        //Iterate over answers
-        for($i = 0; $i < sizeof($answers); $i++) {
-            $ans_info = $answers[$i];
-            //traverse_xmlize($ans_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($ans_info['#']['ID']['0']['#']);
-
-            //Now, build the question_answers record structure
-            $answer->question = $new_question_id;
-            $answer->answer = backup_todb($ans_info['#']['ANSWER_TEXT']['0']['#']);
-            $answer->fraction = backup_todb($ans_info['#']['FRACTION']['0']['#']);
-            $answer->feedback = backup_todb($ans_info['#']['FEEDBACK']['0']['#']);
-
-            //If we are in this method is because the question exists in DB, so its
-            //answers must exist too.
-            //Now, we are going to look for that answer in DB and to create the
-            //mappings in backup_ids to use them later where restoring responses (user level).
-
-            //Get the answer from DB (by question, answer and fraction)
-            $db_answer = $DB->get_record ("question_answers", array("question"=>$new_question_id,
-                                                    "answer"=>$answer->answer,
-                                                    "fraction"=>$answer->fraction));
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($db_answer) {
-                //We have the database answer, update backup_ids
-                backup_putid($restore->backup_unique_code,"question_answers",$oldid,
-                             $db_answer->id);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_shortanswer ($old_question_id,$new_question_id,$info,$restore,$restrictto = '') {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the shortanswers array
-        $shortanswers = $info['#']['SHORTANSWER'];
-
-        //Iterate over shortanswers
-        for($i = 0; $i < sizeof($shortanswers); $i++) {
-            $sho_info = $shortanswers[$i];
-            //traverse_xmlize($sho_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_shortanswer record structure
-            $shortanswer->question = $new_question_id;
-            $shortanswer->answers = backup_todb($sho_info['#']['ANSWERS']['0']['#']);
-            $shortanswer->usecase = backup_todb($sho_info['#']['USECASE']['0']['#']);
-
-            //We have to recode the answers field (a list of answers id)
-            //Extracts answer id from sequence
-            $answers_field = "";
-            $in_first = true;
-            $tok = strtok($shortanswer->answers,",");
-            while ($tok) {
-                //Get the answer from backup_ids
-                $answer = backup_getid($restore->backup_unique_code,"question_answers",$tok);
-                if ($answer) {
-                    if ($in_first) {
-                        $answers_field .= $answer->new_id;
-                        $in_first = false;
-                    } else {
-                        $answers_field .= ",".$answer->new_id;
-                    }
-                }
-                //check for next
-                $tok = strtok(",");
-            }
-            //We have the answers field recoded to its new ids
-            $shortanswer->answers = $answers_field;
-
-            //The structure is equal to the db, so insert the question_shortanswer
-            //Only if there aren't restrictions or there are restriction concordance
-            if (empty($restrictto) || (!empty($restrictto) && $shortanswer->answers == $restrictto)) {
-                $newid = $DB->insert_record ("question_shortanswer",$shortanswer);
-            }
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if (!$newid && !$restrictto) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_truefalse ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the truefalse array
-        $truefalses = $info['#']['TRUEFALSE'];
-
-        //Iterate over truefalse
-        for($i = 0; $i < sizeof($truefalses); $i++) {
-            $tru_info = $truefalses[$i];
-            //traverse_xmlize($tru_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_truefalse record structure
-            $truefalse->question = $new_question_id;
-            $truefalse->trueanswer = backup_todb($tru_info['#']['TRUEANSWER']['0']['#']);
-            $truefalse->falseanswer = backup_todb($tru_info['#']['FALSEANSWER']['0']['#']);
-
-            ////We have to recode the trueanswer field
-            $answer = backup_getid($restore->backup_unique_code,"question_answers",$truefalse->trueanswer);
-            if ($answer) {
-                $truefalse->trueanswer = $answer->new_id;
-            }
-
-            ////We have to recode the falseanswer field
-            $answer = backup_getid($restore->backup_unique_code,"question_answers",$truefalse->falseanswer);
-            if ($answer) {
-                $truefalse->falseanswer = $answer->new_id;
-            }
-
-            //The structure is equal to the db, so insert the question_truefalse
-            $newid = $DB->insert_record ("question_truefalse",$truefalse);
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if (!$newid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_multichoice ($old_question_id,$new_question_id,$info,$restore, $restrictto = '') {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the multichoices array
-        $multichoices = $info['#']['MULTICHOICE'];
-
-        //Iterate over multichoices
-        for($i = 0; $i < sizeof($multichoices); $i++) {
-            $mul_info = $multichoices[$i];
-            //traverse_xmlize($mul_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_multichoice record structure
-            $multichoice->question = $new_question_id;
-            $multichoice->layout = backup_todb($mul_info['#']['LAYOUT']['0']['#']);
-            $multichoice->answers = backup_todb($mul_info['#']['ANSWERS']['0']['#']);
-            $multichoice->single = backup_todb($mul_info['#']['SINGLE']['0']['#']);
-
-            //We have to recode the answers field (a list of answers id)
-            //Extracts answer id from sequence
-            $answers_field = "";
-            $in_first = true;
-            $tok = strtok($multichoice->answers,",");
-            while ($tok) {
-                //Get the answer from backup_ids
-                $answer = backup_getid($restore->backup_unique_code,"question_answers",$tok);
-                if ($answer) {
-                    if ($in_first) {
-                        $answers_field .= $answer->new_id;
-                        $in_first = false;
-                    } else {
-                        $answers_field .= ",".$answer->new_id;
-                    }
-                }
-                //check for next
-                $tok = strtok(",");
-            }
-            //We have the answers field recoded to its new ids
-            $multichoice->answers = $answers_field;
-
-            //The structure is equal to the db, so insert the question_shortanswer
-            //Only if there aren't restrictions or there are restriction concordance
-            if (empty($restrictto) || (!empty($restrictto) && $multichoice->answers == $restrictto)) {
-                $newid = $DB->insert_record ("question_multichoice",$multichoice);
-            }
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if (!$newid && !$restrictto) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_match ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the matchs array
-        $matchs = $info['#']['MATCHS']['0']['#']['MATCH'];
-
-        //We have to build the subquestions field (a list of match_sub id)
-        $subquestions_field = "";
-        $in_first = true;
-
-        //Iterate over matchs
-        for($i = 0; $i < sizeof($matchs); $i++) {
-            $mat_info = $matchs[$i];
-            //traverse_xmlize($mat_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($mat_info['#']['ID']['0']['#']);
-
-            //Now, build the question_match_SUB record structure
-            $match_sub->question = $new_question_id;
-            $match_sub->questiontext = backup_todb($mat_info['#']['QUESTIONTEXT']['0']['#']);
-            $match_sub->answertext = backup_todb($mat_info['#']['ANSWERTEXT']['0']['#']);
-
-            //The structure is equal to the db, so insert the question_match_sub
-            $newid = $DB->insert_record ("question_match_sub",$match_sub);
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"question_match_sub",$oldid,
-                             $newid);
-                //We have a new match_sub, append it to subquestions_field
-                if ($in_first) {
-                    $subquestions_field .= $newid;
-                    $in_first = false;
-                } else {
-                    $subquestions_field .= ",".$newid;
-                }
-            } else {
-                $status = false;
-            }
-        }
-
-        //We have created every match_sub, now create the match
-        $match->question = $new_question_id;
-        $match->subquestions = $subquestions_field;
-
-        //The structure is equal to the db, so insert the question_match_sub
-        $newid = $DB->insert_record ("question_match",$match);
-
-        if (!$newid) {
-            $status = false;
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_map_match ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the matchs array
-        $matchs = $info['#']['MATCHS']['0']['#']['MATCH'];
-
-        //We have to build the subquestions field (a list of match_sub id)
-        $subquestions_field = "";
-        $in_first = true;
-
-        //Iterate over matchs
-        for($i = 0; $i < sizeof($matchs); $i++) {
-            $mat_info = $matchs[$i];
-            //traverse_xmlize($mat_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($mat_info['#']['ID']['0']['#']);
-
-            //Now, build the question_match_SUB record structure
-            $match_sub->question = $new_question_id;
-            $match_sub->questiontext = backup_todb($mat_info['#']['QUESTIONTEXT']['0']['#']);
-            $match_sub->answertext = backup_todb($mat_info['#']['ANSWERTEXT']['0']['#']);
-
-            //If we are in this method is because the question exists in DB, so its
-            //match_sub must exist too.
-            //Now, we are going to look for that match_sub in DB and to create the
-            //mappings in backup_ids to use them later where restoring responses (user level).
-
-            //Get the match_sub from DB (by question, questiontext and answertext)
-            $db_match_sub = $DB->get_record ("question_match_sub", array("question"=>$new_question_id,
-                                                      "questiontext"=>$match_sub->questiontext,
-                                                      "answertext"=>$match_sub->answertext));
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            //We have the database match_sub, so update backup_ids
-            if ($db_match_sub) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"question_match_sub",$oldid,
-                             $db_match_sub->id);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_randomsamatch ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the randomsamatchs array
-        $randomsamatchs = $info['#']['RANDOMSAMATCH'];
-
-        //Iterate over randomsamatchs
-        for($i = 0; $i < sizeof($randomsamatchs); $i++) {
-            $ran_info = $randomsamatchs[$i];
-            //traverse_xmlize($ran_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_randomsamatch record structure
-            $randomsamatch->question = $new_question_id;
-            $randomsamatch->choose = backup_todb($ran_info['#']['CHOOSE']['0']['#']);
-
-            //The structure is equal to the db, so insert the question_randomsamatch
-            $newid = $DB->insert_record ("question_randomsamatch",$randomsamatch);
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if (!$newid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_numerical ($old_question_id,$new_question_id,$info,$restore, $restrictto = '') {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the numerical array
-        $numericals = $info['#']['NUMERICAL'];
-
-        //Iterate over numericals
-        for($i = 0; $i < sizeof($numericals); $i++) {
-            $num_info = $numericals[$i];
-            //traverse_xmlize($num_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_numerical record structure
-            $numerical->question = $new_question_id;
-            $numerical->answer = backup_todb($num_info['#']['ANSWER']['0']['#']);
-            $numerical->min = backup_todb($num_info['#']['MIN']['0']['#']);
-            $numerical->max = backup_todb($num_info['#']['MAX']['0']['#']);
-
-            ////We have to recode the answer field
-            $answer = backup_getid($restore->backup_unique_code,"question_answers",$numerical->answer);
-            if ($answer) {
-                $numerical->answer = $answer->new_id;
-            }
-
-            //Answer goes to answers in 1.5 (although it continues being only one!)
-            //Changed 12-05 (chating with Gustav and Julian this remains = pre15 = answer)
-            //$numerical->answers = $numerical->answer;
-
-            //We have to calculate the tolerance field of the numerical question
-            $numerical->tolerance = ($numerical->max - $numerical->min)/2;
-
-            //The structure is equal to the db, so insert the question_numerical
-            //Only if there aren't restrictions or there are restriction concordance
-            if (empty($restrictto) || (!empty($restrictto) && in_array($numerical->answer,explode(",",$restrictto)))) {
-                $newid = $DB->insert_record ("question_numerical",$numerical);
-            }
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            //Now restore numerical_units
-            if ($newid) {
-                $status = quiz_restore_pre15_numerical_units ($old_question_id,$new_question_id,$num_info,$restore);
-            }
-
-            if (!$newid && !$restrictto) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_calculated ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the calculated-s array
-        $calculateds = $info['#']['CALCULATED'];
-
-        //Iterate over calculateds
-        for($i = 0; $i < sizeof($calculateds); $i++) {
-            $cal_info = $calculateds[$i];
-            //traverse_xmlize($cal_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_calculated record structure
-            $calculated->question = $new_question_id;
-            $calculated->answer = backup_todb($cal_info['#']['ANSWER']['0']['#']);
-            $calculated->tolerance = backup_todb($cal_info['#']['TOLERANCE']['0']['#']);
-            $calculated->tolerancetype = backup_todb($cal_info['#']['TOLERANCETYPE']['0']['#']);
-            $calculated->correctanswerlength = backup_todb($cal_info['#']['CORRECTANSWERLENGTH']['0']['#']);
-            $calculated->correctanswerformat = backup_todb($cal_info['#']['CORRECTANSWERFORMAT']['0']['#']);
-
-            ////We have to recode the answer field
-            $answer = backup_getid($restore->backup_unique_code,"question_answers",$calculated->answer);
-            if ($answer) {
-                $calculated->answer = $answer->new_id;
-            }
-
-            //If we haven't correctanswerformat, it defaults to 2 (in DB)
-            if (empty($calculated->correctanswerformat)) {
-                $calculated->correctanswerformat = 2;
-            }
-
-            //The structure is equal to the db, so insert the question_calculated
-            $newid = $DB->insert_record ("question_calculated",$calculated);
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            //Now restore numerical_units
-            $status = quiz_restore_pre15_numerical_units ($old_question_id,$new_question_id,$cal_info,$restore);
-
-            //Now restore dataset_definitions
-            if ($status && $newid) {
-                $status = quiz_restore_pre15_dataset_definitions ($old_question_id,$new_question_id,$cal_info,$restore);
-            }
-
-            if (!$newid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_multianswer ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //We need some question fields here so we get the full record from DB
-        $parentquestion = $DB->get_record('question', array('id'=>$new_question_id));
-
-        //We need to store all the positions with their created questions
-        //to be able to calculate the sequence field
-        $createdquestions = array();
-
-        //Under 1.5, every multianswer record becomes a question itself
-        //with its parent set to the cloze question. And there is only
-        //ONE multianswer record with the sequence of questions used.
-
-        //Get the multianswers array
-        $multianswers_array = $info['#']['MULTIANSWERS']['0']['#']['MULTIANSWER'];
-        //Iterate over multianswers_array
-        for($i = 0; $i < sizeof($multianswers_array); $i++) {
-            $mul_info = $multianswers_array[$i];
-            //traverse_xmlize($mul_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We need this later
-            $oldid = backup_todb($mul_info['#']['ID']['0']['#']);
-
-            //Now, build the question_multianswer record structure
-            $multianswer->question = $new_question_id;
-            $multianswer->answers = backup_todb($mul_info['#']['ANSWERS']['0']['#']);
-            $multianswer->positionkey = backup_todb($mul_info['#']['POSITIONKEY']['0']['#']);
-            $multianswer->answertype = backup_todb($mul_info['#']['ANSWERTYPE']['0']['#']);
-            $multianswer->norm = backup_todb($mul_info['#']['NORM']['0']['#']);
-
-            //Saving multianswer and positionkey to use them later restoring states
-            backup_putid ($restore->backup_unique_code,'multianswer-pos',$oldid,$multianswer->positionkey);
-
-            //We have to recode all the answers to their new ids
-            $ansarr = explode(",", $multianswer->answers);
-            foreach ($ansarr as $key => $value) {
-                //Get the answer from backup_ids
-                $answer = backup_getid($restore->backup_unique_code,'question_answers',$value);
-                $ansarr[$key] = $answer->new_id;
-            }
-            $multianswer->answers = implode(",",$ansarr);
-
-            //Build the new question structure
-            $question = new stdClass();
-            $question->category           = $parentquestion->category;
-            $question->parent             = $parentquestion->id;
-            $question->name               = $parentquestion->name;
-            $question->questiontextformat = $parentquestion->questiontextformat;
-            $question->defaultgrade       = $multianswer->norm;
-            $question->penalty            = $parentquestion->penalty;
-            $question->qtype              = $multianswer->answertype;
-            $question->version            = $parentquestion->version;
-            $question->hidden             = $parentquestion->hidden;
-            $question->length             = 0;
-            $question->questiontext       = '';
-            $question->stamp              = make_unique_id_code();
-
-            //Save the new question to DB
-            $newid = $DB->insert_record('question', $question);
-
-            if ($newid) {
-                $createdquestions[$multianswer->positionkey] = $newid;
-            }
-
-            //Do some output
-            if (($i+1) % 50 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 1000 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            //Remap question_answers records from the original multianswer question
-            //to their newly created question
-            if ($newid) {
-                $answersdb = $DB->get_records_list('question_answers','id', explode(',',$multianswer->answers));
-                foreach ($answersdb as $answerdb) {
-                    $DB->set_field('question_answers','question',$newid,array('id' =>$answerdb->id));
-                }
-            }
-
-            //If we have created the question record, now, depending of the
-            //answertype, delegate the restore to every qtype function
-            if ($newid) {
-                if ($multianswer->answertype == "1") {
-                    $status = quiz_restore_pre15_shortanswer ($old_question_id,$newid,$mul_info,$restore,$multianswer->answers);
-                } else if ($multianswer->answertype == "3") {
-                    $status = quiz_restore_pre15_multichoice ($old_question_id,$newid,$mul_info,$restore,$multianswer->answers);
-                } else if ($multianswer->answertype == "8") {
-                    $status = quiz_restore_pre15_numerical ($old_question_id,$newid,$mul_info,$restore,$multianswer->answers);
-                }
-            } else {
-                $status = false;
-            }
-        }
-
-        //Everything is created, just going to create the multianswer record
-        if ($status) {
-            ksort($createdquestions);
-
-            $multianswerdb = new stdClass();
-            $multianswerdb->question = $parentquestion->id;
-            $multianswerdb->sequence = implode(",",$createdquestions);
-            $mid = $DB->insert_record('question_multianswer', $multianswerdb);
-
-            if (!$mid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_numerical_units ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the numerical array
-        $numerical_units = $info['#']['NUMERICAL_UNITS']['0']['#']['NUMERICAL_UNIT'];
-
-        //Iterate over numerical_units
-        for($i = 0; $i < sizeof($numerical_units); $i++) {
-            $nu_info = $numerical_units[$i];
-            //traverse_xmlize($nu_info);                                                                  //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_numerical_UNITS record structure
-            $numerical_unit->question = $new_question_id;
-            $numerical_unit->multiplier = backup_todb($nu_info['#']['MULTIPLIER']['0']['#']);
-            $numerical_unit->unit = backup_todb($nu_info['#']['UNIT']['0']['#']);
-
-            //The structure is equal to the db, so insert the question_numerical_units
-            $newid = $DB->insert_record ("question_numerical_units",$numerical_unit);
-
-            if (!$newid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_dataset_definitions ($old_question_id,$new_question_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the dataset_definitions array
-        $dataset_definitions = $info['#']['DATASET_DEFINITIONS']['0']['#']['DATASET_DEFINITION'];
-
-        //Iterate over dataset_definitions
-        for($i = 0; $i < sizeof($dataset_definitions); $i++) {
-            $dd_info = $dataset_definitions[$i];
-            //traverse_xmlize($dd_info);                                                                  //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_dataset_DEFINITION record structure
-            $dataset_definition->category = backup_todb($dd_info['#']['CATEGORY']['0']['#']);
-            $dataset_definition->name = backup_todb($dd_info['#']['NAME']['0']['#']);
-            $dataset_definition->type = backup_todb($dd_info['#']['TYPE']['0']['#']);
-            $dataset_definition->options = backup_todb($dd_info['#']['OPTIONS']['0']['#']);
-            $dataset_definition->itemcount = backup_todb($dd_info['#']['ITEMCOUNT']['0']['#']);
-
-            //We have to recode the category field (only if the category != 0)
-            if ($dataset_definition->category != 0) {
-                $category = backup_getid($restore->backup_unique_code,"question_categories",$dataset_definition->category);
-                if ($category) {
-                    $dataset_definition->category = $category->new_id;
-                }
-            }
-
-            //Now, we hace to decide when to create the new records or reuse an existing one
-            $create_definition = false;
-
-            //If the dataset_definition->category = 0, it's a individual question dataset_definition, so we'll create it
-            if ($dataset_definition->category == 0) {
-                $create_definition = true;
-            } else {
-                //The category isn't 0, so it's a category question dataset_definition, we have to see if it exists
-                //Look for a definition with the same category, name and type
-                if ($definitionrec = $DB->get_records('question_dataset_definitions', array('category'=>$dataset_definition->category,
-                                                           'name'=>$dataset_definition->name,
-                                                           'type'=>$dataset_definition->type))) {
-                    //Such dataset_definition exist. Now we must check if it has enough itemcount
-                    if ($definitionrec->itemcount < $dataset_definition->itemcount) {
-                        //We haven't enough itemcount, so we have to create the definition as an individual question one.
-                        $dataset_definition->category = 0;
-                        $create_definition = true;
-                    } else {
-                        //We have enough itemcount, so we'll reuse the existing definition
-                        $create_definition = false;
-                        $newid = $definitionrec->id;
-                    }
-                } else {
-                    //Such dataset_definition doesn't exist. We'll create it.
-                    $create_definition = true;
-                }
-            }
-
-            //If we've to create the definition, do it
-            if ($create_definition) {
-                //The structure is equal to the db, so insert the question_dataset_definitions
-                $newid = $DB->insert_record ("question_dataset_definitions",$dataset_definition);
-                if ($newid) {
-                    //Restore question_dataset_items
-                    $status = quiz_restore_pre15_dataset_items($newid,$dd_info,$restore);
-                }
-            }
-
-            //Now, we must have a definition (created o reused). Its id is in newid. Create the question_datasets record
-            //to join the question and the dataset_definition
-            if ($newid) {
-                $question_dataset->question = $new_question_id;
-                $question_dataset->datasetdefinition = $newid;
-                $newid = $DB->insert_record ("question_datasets",$question_dataset);
-            }
-
-            if (!$newid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    function quiz_restore_pre15_dataset_items ($definitionid,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the items array
-        $dataset_items = $info['#']['DATASET_ITEMS']['0']['#']['DATASET_ITEM'];
-
-        //Iterate over dataset_items
-        for($i = 0; $i < sizeof($dataset_items); $i++) {
-            $di_info = $dataset_items[$i];
-            //traverse_xmlize($di_info);                                                                  //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the question_dataset_ITEMS record structure
-            $dataset_item->definition = $definitionid;
-            $dataset_item->itemnumber = backup_todb($di_info['#']['NUMBER']['0']['#']);
-            $dataset_item->value = backup_todb($di_info['#']['VALUE']['0']['#']);
-
-            //The structure is equal to the db, so insert the question_dataset_items
-            $newid = $DB->insert_record ("question_dataset_items",$dataset_item);
-
-            if (!$newid) {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //STEP 2. Restore quizzes and associated structures
-    //    (course dependent)
-    function quiz_restore_pre15_mods($mod,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get record from backup_ids
-        $data = backup_getid($restore->backup_unique_code,$mod->modtype,$mod->id);
-
-        if ($data) {
-            //Now get completed xmlized object
-            $info = $data->info;
-            //if necessary, write to restorelog and adjust date/time fields
-            if ($restore->course_startdateoffset) {
-                restore_log_date_changes('Quiz', $restore, $info['MOD']['#'], array('TIMEOPEN', 'TIMECLOSE'));
-            }
-            //traverse_xmlize($info);                                                                     //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //Now, build the QUIZ record structure
-            $quiz->course = $restore->course_id;
-            $quiz->name = backup_todb($info['MOD']['#']['NAME']['0']['#']);
-            $quiz->intro = backup_todb($info['MOD']['#']['INTRO']['0']['#']);
-            $quiz->timeopen = backup_todb($info['MOD']['#']['TIMEOPEN']['0']['#']);
-            $quiz->timeclose = backup_todb($info['MOD']['#']['TIMECLOSE']['0']['#']);
-            $quiz->attempts = backup_todb($info['MOD']['#']['ATTEMPTS_NUMBER']['0']['#']);
-            $quiz->attemptonlast = backup_todb($info['MOD']['#']['ATTEMPTONLAST']['0']['#']);
-            $quiz->feedback = backup_todb($info['MOD']['#']['FEEDBACK']['0']['#']);
-            $quiz->correctanswers = backup_todb($info['MOD']['#']['CORRECTANSWERS']['0']['#']);
-            $quiz->grademethod = backup_todb($info['MOD']['#']['GRADEMETHOD']['0']['#']);
-            if (isset($info['MOD']['#']['DECIMALPOINTS']['0']['#'])) { //Only if it's set, to apply DB default else.
-                $quiz->decimalpoints = backup_todb($info['MOD']['#']['DECIMALPOINTS']['0']['#']);
-            }
-            $quiz->review = backup_todb($info['MOD']['#']['REVIEW']['0']['#']);
-            $quiz->questionsperpage = backup_todb($info['MOD']['#']['QUESTIONSPERPAGE']['0']['#']);
-            $quiz->shufflequestions = backup_todb($info['MOD']['#']['SHUFFLEQUESTIONS']['0']['#']);
-            $quiz->shuffleanswers = backup_todb($info['MOD']['#']['SHUFFLEANSWERS']['0']['#']);
-            $quiz->questions = backup_todb($info['MOD']['#']['QUESTIONS']['0']['#']);
-            $quiz->sumgrades = backup_todb($info['MOD']['#']['SUMGRADES']['0']['#']);
-            $quiz->grade = backup_todb($info['MOD']['#']['GRADE']['0']['#']);
-            $quiz->timecreated = backup_todb($info['MOD']['#']['TIMECREATED']['0']['#']);
-            $quiz->timemodified = backup_todb($info['MOD']['#']['TIMEMODIFIED']['0']['#']);
-            $quiz->timelimit = backup_todb($info['MOD']['#']['TIMELIMIT']['0']['#']) * 60;
-            $quiz->password = backup_todb($info['MOD']['#']['PASSWORD']['0']['#']);
-            $quiz->subnet = backup_todb($info['MOD']['#']['SUBNET']['0']['#']);
-            $quiz->popup = backup_todb($info['MOD']['#']['POPUP']['0']['#']);
-
-            //We have to recode the questions field (a list of questions id)
-            $newquestions = array();
-            if ($questionsarr = explode (",",$quiz->questions)) {
-                foreach ($questionsarr as $key => $value) {
-                    if ($question = backup_getid($restore->backup_unique_code,"question",$value)) {
-                        $newquestions[] = $question->new_id;
-                    }
-                }
-            }
-            $quiz->questions = implode (",", $newquestions);
-
-            //Recalculate the questions field to include page breaks if necessary
-            $quiz->questions = quiz_repaginate($quiz->questions, $quiz->questionsperpage);
-
-            //Calculate the new review field contents (logic extracted from upgrade)
-            $review = (QUIZ_REVIEW_IMMEDIATELY & (QUIZ_REVIEW_RESPONSES + QUIZ_REVIEW_SCORES));
-            if ($quiz->feedback) {
-                $review += (QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_FEEDBACK);
-            }
-            if ($quiz->correctanswers) {
-                $review += (QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS);
-            }
-            if ($quiz->review & 1) {
-                $review += QUIZ_REVIEW_CLOSED;
-            }
-            if ($quiz->review & 2) {
-                $review += QUIZ_REVIEW_OPEN;
-            }
-            $quiz->review = $review;
-
-            //The structure is equal to the db, so insert the quiz
-            $newid = $DB->insert_record ("quiz",$quiz);
-
-            //Do some output
-            if (!defined('RESTORE_SILENTLY')) {
-                echo "<li>".get_string("modulename","quiz")." \"".format_string($quiz->name,true)."\"</li>";
-            }
-            backup_flush(300);
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,$mod->modtype,
-                             $mod->id, $newid);
-                //We have to restore the quiz_question_instances now (old quiz_question_grades, course level)
-                $status = quiz_question_instances_restore_pre15_mods($newid,$info,$restore);
-                //Now check if want to restore user data and do it.
-                if (restore_userdata_selected($restore,'quiz',$mod->id)) {
-                    //Restore quiz_attempts
-                    $status = quiz_attempts_restore_pre15_mods ($newid,$info,$restore, $quiz->questions);
-                    if ($status) {
-                        //Restore quiz_grades
-                        $status = quiz_grades_restore_pre15_mods ($newid,$info,$restore);
-                    }
-                }
-            } else {
-                $status = false;
-            }
-        } else {
-            $status = false;
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_question_instances (old quiz_question_grades)
-    function quiz_question_instances_restore_pre15_mods($quiz_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the quiz_question_grades array
-        $grades = $info['MOD']['#']['QUESTION_GRADES']['0']['#']['QUESTION_GRADE'];
-
-        //Iterate over question_grades
-        for($i = 0; $i < sizeof($grades); $i++) {
-            $gra_info = $grades[$i];
-            //traverse_xmlize($gra_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($gra_info['#']['ID']['0']['#']);
-
-            //Now, build the QUESTION_GRADES record structure
-            $grade->quiz = $quiz_id;
-            $grade->question = backup_todb($gra_info['#']['QUESTION']['0']['#']);
-            $grade->grade = backup_todb($gra_info['#']['GRADE']['0']['#']);
-
-            //We have to recode the question field
-            $question = backup_getid($restore->backup_unique_code,"question",$grade->question);
-            if ($question) {
-                $grade->question = $question->new_id;
-            }
-
-            //The structure is equal to the db, so insert the quiz_question_grades
-            $newid = $DB->insert_record ("quiz_question_instances",$grade);
-
-            //Do some output
-            if (($i+1) % 10 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 200 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"quiz_question_instances",$oldid,
-                             $newid);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_attempts
-    function quiz_attempts_restore_pre15_mods($quiz_id,$info,$restore,$quizquestions) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the quiz_attempts array
-        $attempts = $info['MOD']['#']['ATTEMPTS']['0']['#']['ATTEMPT'];
-
-        //Iterate over attempts
-        for($i = 0; $i < sizeof($attempts); $i++) {
-            $att_info = $attempts[$i];
-            //traverse_xmlize($att_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($att_info['#']['ID']['0']['#']);
-            $olduserid = backup_todb($att_info['#']['USERID']['0']['#']);
-
-            //Now, build the ATTEMPTS record structure
-            $attempt->quiz = $quiz_id;
-            $attempt->userid = backup_todb($att_info['#']['USERID']['0']['#']);
-            $attempt->attempt = backup_todb($att_info['#']['ATTEMPTNUM']['0']['#']);
-            $attempt->sumgrades = backup_todb($att_info['#']['SUMGRADES']['0']['#']);
-            $attempt->timestart = backup_todb($att_info['#']['TIMESTART']['0']['#']);
-            $attempt->timefinish = backup_todb($att_info['#']['TIMEFINISH']['0']['#']);
-            $attempt->timemodified = backup_todb($att_info['#']['TIMEMODIFIED']['0']['#']);
-
-            //We have to recode the userid field
-            $user = backup_getid($restore->backup_unique_code,"user",$attempt->userid);
-            if ($user) {
-                $attempt->userid = $user->new_id;
-            }
-
-            //Set the layout field (inherited from quiz by default)
-            $attempt->layout = $quizquestions;
-
-            //Set the preview field (code from upgrade)
-            $cm = get_coursemodule_from_instance('quiz', $quiz_id);
-            if (has_capability('mod/quiz:preview', get_context_instance(CONTEXT_MODULE, $cm->id))) {
-                $attempt->preview = 1;
-            }
-
-            //Set the uniqueid field
-            $attempt->uniqueid = question_new_attempt_uniqueid();
-
-            //The structure is equal to the db, so insert the quiz_attempts
-            $newid = $DB->insert_record ("quiz_attempts",$attempt);
-
-            //Do some output
-            if (($i+1) % 10 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 200 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"quiz_attempts",$oldid,
-                             $newid);
-                //Now process question_states (old quiz_responses table)
-                $status = question_states_restore_pre15_mods($newid,$att_info,$restore);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //This function restores the question_states (old quiz_responses)
-    function question_states_restore_pre15_mods($attempt_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the quiz_responses array
-        $responses = $info['#']['RESPONSES']['0']['#']['RESPONSE'];
-        //Iterate over responses
-        for($i = 0; $i < sizeof($responses); $i++) {
-            $res_info = $responses[$i];
-            //traverse_xmlize($res_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($res_info['#']['ID']['0']['#']);
-
-            //Now, build the RESPONSES record structure
-            $response->attempt = $attempt_id;
-            $response->question = backup_todb($res_info['#']['QUESTION']['0']['#']);
-            $response->answer = backup_todb($res_info['#']['ANSWER']['0']['#']);
-            $response->grade = backup_todb($res_info['#']['GRADE']['0']['#']);
-
-            //We have to recode the question field
-            $question = backup_getid($restore->backup_unique_code,"question",$response->question);
-            if ($question) {
-                $response->question = $question->new_id;
-            }
-
-            //Set the raw_grade field (default to the existing grade one, no penalty in pre15 backups)
-            $response->raw_grade = $response->grade;
-
-            //We have to recode the answer field
-            //It depends of the question type !!
-            //We get the question first
-            $question = $DB->get_record("question", array("id"=>$response->question));
-            //It exists
-            if ($question) {
-                //Depending of the qtype, we make different recodes
-                switch ($question->qtype) {
-                    case 1:    //SHORTANSWER QTYPE
-                        //Nothing to do. The response is a text.
-                        break;
-                    case 2:    //TRUEFALSE QTYPE
-                        //The answer is one answer id. We must recode it
-                        $answer = backup_getid($restore->backup_unique_code,"question_answers",$response->answer);
-                        if ($answer) {
-                            $response->answer = $answer->new_id;
-                        }
-                        break;
-                    case 3:    //MULTICHOICE QTYPE
-                        //The answer is a comma separated list of answers. We must recode them
-                        $answer_field = "";
-                        $in_first = true;
-                        $tok = strtok($response->answer,",");
-                        while ($tok) {
-                            //Get the answer from backup_ids
-                            $answer = backup_getid($restore->backup_unique_code,"question_answers",$tok);
-                            if ($answer) {
-                                if ($in_first) {
-                                    $answer_field .= $answer->new_id;
-                                    $in_first = false;
-                                } else {
-                                    $answer_field .= ",".$answer->new_id;
-                                }
-                            }
-                            //check for next
-                            $tok = strtok(",");
-                        }
-                        $response->answer = $answer_field;
-                        break;
-                    case 4:    //RANDOM QTYPE
-                        //The answer links to another question id, we must recode it
-                        $answer_link = backup_getid($restore->backup_unique_code,"question",$response->answer);
-                        if ($answer_link) {
-                            $response->answer = $answer_link->new_id;
-                        }
-                        break;
-                    case 5:    //MATCH QTYPE
-                        //The answer is a comma separated list of hypen separated math_subs (for question and answer)
-                        $answer_field = "";
-                        $in_first = true;
-                        $tok = strtok($response->answer,",");
-                        while ($tok) {
-                            //Extract the match_sub for the question and the answer
-                            $exploded = explode("-",$tok);
-                            $match_question_id = $exploded[0];
-                            $match_answer_id = $exploded[1];
-                            //Get the match_sub from backup_ids (for the question)
-                            $match_que = backup_getid($restore->backup_unique_code,"question_match_sub",$match_question_id);
-                            //Get the match_sub from backup_ids (for the answer)
-                            $match_ans = backup_getid($restore->backup_unique_code,"question_match_sub",$match_answer_id);
-                            if ($match_que) {
-                                //It the question hasn't response, it must be 0
-                                if (!$match_ans and $match_answer_id == 0) {
-                                    $match_ans->new_id = 0;
-                                }
-                                if ($in_first) {
-                                    $answer_field .= $match_que->new_id."-".$match_ans->new_id;
-                                    $in_first = false;
-                                } else {
-                                    $answer_field .= ",".$match_que->new_id."-".$match_ans->new_id;
-                                }
-                            }
-                            //check for next
-                            $tok = strtok(",");
-                        }
-                        $response->answer = $answer_field;
-                        break;
-                    case 6:    //RANDOMSAMATCH QTYPE
-                        //The answer is a comma separated list of hypen separated question_id and answer_id. We must recode them
-                        $answer_field = "";
-                        $in_first = true;
-                        $tok = strtok($response->answer,",");
-                        while ($tok) {
-                            //Extract the question_id and the answer_id
-                            $exploded = explode("-",$tok);
-                            $question_id = $exploded[0];
-                            $answer_id = $exploded[1];
-                            //Get the question from backup_ids
-                            $que = backup_getid($restore->backup_unique_code,"question",$question_id);
-                            //Get the answer from backup_ids
-                            $ans = backup_getid($restore->backup_unique_code,"question_answers",$answer_id);
-                            if ($que) {
-                                //It the question hasn't response, it must be 0
-                                if (!$ans and $answer_id == 0) {
-                                    $ans->new_id = 0;
-                                }
-                                if ($in_first) {
-                                    $answer_field .= $que->new_id."-".$ans->new_id;
-                                    $in_first = false;
-                                } else {
-                                    $answer_field .= ",".$que->new_id."-".$ans->new_id;
-                                }
-                            }
-                            //check for next
-                            $tok = strtok(",");
-                        }
-                        $response->answer = $answer_field;
-                        break;
-                    case 7:    //DESCRIPTION QTYPE
-                        //Nothing to do (there is no awser to this qtype)
-                        //But this case must exist !!
-                        break;
-                    case 8:    //NUMERICAL QTYPE
-                        //Nothing to do. The response is a text.
-                        break;
-                    case 9:    //MULTIANSWER QTYPE
-                        //The answer is a comma separated list of hypen separated multianswer ids and answers. We must recode them.
-                        //We need to have the sequence of questions here to be able to detect qtypes
-                        $multianswerdb = $DB->get_record('question_multianswer',array('question'=>$response->question));
-                        //Make an array of sequence to easy access
-                        $sequencearr = explode(",",$multianswerdb->sequence);
-                        $answer_field = "";
-                        $in_first = true;
-                        $tok = strtok($response->answer,",");
-                        $counter = 1;
-                        while ($tok) {
-                            //Extract the multianswer_id and the answer
-                            $exploded = explode("-",$tok);
-                            $multianswer_id = $exploded[0];
-                            $answer = $exploded[1];
-                            //Get position key (if it fails, next iteration)
-                            if ($oldposrec = backup_getid($restore->backup_unique_code,'multianswer-pos',$multianswer_id)) {
-                                $positionkey = $oldposrec->new_id;
-                            } else {
-                                //Next iteration
-                                $tok = strtok(",");
-                                continue;
-                            }
-                            //Calculate question type
-                            $questiondb = $DB->get_record('question', array('id'=>$sequencearr[$counter-1]));
-                            $questiontype = $questiondb->qtype;
-                            //Now, depending of the answertype field in question_multianswer
-                            //we do diferent things
-                            if ($questiontype == "1") {
-                                //Shortanswer
-                                //The answer is text, do nothing
-                            } else if ($questiontype == "3") {
-                                //Multichoice
-                                //The answer is an answer_id, look for it in backup_ids
-                                $ans = backup_getid($restore->backup_unique_code,"question_answers",$answer);
-                                $answer = $ans->new_id;
-                            } else if ($questiontype == "8") {
-                                //Numeric
-                                //The answer is text, do nothing
-                            }
-
-                            //Finaly, build the new answer field for each pair
-                            if ($in_first) {
-                                $answer_field .= $positionkey."-".$answer;
-                                $in_first = false;
-                            } else {
-                                $answer_field .= ",".$positionkey."-".$answer;
-                            }
-                            //check for next
-                            $tok = strtok(",");
-                            $counter++;
-                        }
-                        $response->answer = $answer_field;
-                        break;
-                    case 10:    //CALCULATED QTYPE
-                        //Nothing to do. The response is a text.
-                        break;
-                    default:   //UNMATCHED QTYPE.
-                        //This is an error (unimplemented qtype)
-                        $status = false;
-                        break;
-                }
-            } else {
-                $status = false;
-            }
-
-            //The structure is equal to the db, so insert the question_states
-            $newid = $DB->insert_record ("question_states",$response);
-
-            //Do some output
-            if (($i+1) % 10 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 200 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"question_states",$oldid,
-                             $newid);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //This function restores the quiz_grades
-    function quiz_grades_restore_pre15_mods($quiz_id,$info,$restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Get the quiz_grades array
-        $grades = $info['MOD']['#']['GRADES']['0']['#']['GRADE'];
-
-        //Iterate over grades
-        for($i = 0; $i < sizeof($grades); $i++) {
-            $gra_info = $grades[$i];
-            //traverse_xmlize($gra_info);                                                                 //Debug
-            //print_object ($GLOBALS['traverse_array']);                                                  //Debug
-            //$GLOBALS['traverse_array']="";                                                              //Debug
-
-            //We'll need this later!!
-            $oldid = backup_todb($gra_info['#']['ID']['0']['#']);
-            $olduserid = backup_todb($gra_info['#']['USERID']['0']['#']);
-
-            //Now, build the GRADES record structure
-            $grade->quiz = $quiz_id;
-            $grade->userid = backup_todb($gra_info['#']['USERID']['0']['#']);
-            $grade->grade = backup_todb($gra_info['#']['GRADEVAL']['0']['#']);
-            $grade->timemodified = backup_todb($gra_info['#']['TIMEMODIFIED']['0']['#']);
-
-            //We have to recode the userid field
-            $user = backup_getid($restore->backup_unique_code,"user",$grade->userid);
-            if ($user) {
-                $grade->userid = $user->new_id;
-            }
-
-            //The structure is equal to the db, so insert the quiz_grades
-            $newid = $DB->insert_record ("quiz_grades",$grade);
-
-            //Do some output
-            if (($i+1) % 10 == 0) {
-                if (!defined('RESTORE_SILENTLY')) {
-                    echo ".";
-                    if (($i+1) % 200 == 0) {
-                        echo "<br />";
-                    }
-                }
-                backup_flush(300);
-            }
-
-            if ($newid) {
-                //We have the newid, update backup_ids
-                backup_putid($restore->backup_unique_code,"quiz_grades",$oldid,
-                             $newid);
-            } else {
-                $status = false;
-            }
-        }
-
-        return $status;
-    }
-
-    //This function converts texts in FORMAT_WIKI to FORMAT_MARKDOWN for
-    //some texts in the module
-    function quiz_restore_pre15_wiki2markdown ($restore) {
-        global $CFG, $DB;
-
-        $status = true;
-
-        //Convert question->questiontext
-        if ($records = $DB->get_records_sql ("SELECT q.id, q.questiontext, q.questiontextformat
-                                                FROM {question} q,
-                                                     {backup_ids} b
-                                               WHERE b.backup_code = ? AND
-                                                     b.table_name = 'question' AND
-                                                     q.id = b.new_id AND
-                                                     q.questiontextformat = ".FORMAT_WIKI, array($restore->backup_unique_code))) {
-            foreach ($records as $record) {
-                //Rebuild wiki links
-                $record->questiontext = restore_decode_wiki_content($record->questiontext, $restore);
-                //Convert to Markdown
-                $wtm = new WikiToMarkdown();
-                $record->questiontext = $wtm->convert($record->questiontext, $restore->course_id);
-                $record->questiontextformat = FORMAT_MARKDOWN;
-                $DB->update_record('question', $record);
-                //Do some output
-                $i++;
-                if (($i+1) % 1 == 0) {
-                    if (!defined('RESTORE_SILENTLY')) {
-                        echo ".";
-                        if (($i+1) % 20 == 0) {
-                            echo "<br />";
-                        }
-                    }
-                    backup_flush(300);
-                }
-            }
-        }
-        return $status;
-    }
-
-    //This function returns a log record with all the necessary transformations
-    //done. It's used by restore_log_module() to restore modules log.
-    function quiz_restore_pre15_logs($restore,$log) {
-
-        $status = false;
-
-        //Depending of the action, we recode different things
-        switch ($log->action) {
-        case "add":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the info field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    $log->url = "view.php?id=".$log->cmid;
-                    $log->info = $mod->new_id;
-                    $status = true;
-                }
-            }
-            break;
-        case "update":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the info field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    $log->url = "view.php?id=".$log->cmid;
-                    $log->info = $mod->new_id;
-                    $status = true;
-                }
-            }
-            break;
-        case "view":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the info field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    $log->url = "view.php?id=".$log->cmid;
-                    $log->info = $mod->new_id;
-                    $status = true;
-                }
-            }
-            break;
-        case "view all":
-            $log->url = "index.php?id=".$log->course;
-            $status = true;
-            break;
-        case "report":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the info field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    $log->url = "report.php?id=".$log->cmid;
-                    $log->info = $mod->new_id;
-                    $status = true;
-                }
-            }
-            break;
-        case "attempt":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the info field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    //Extract the attempt id from the url field
-                    $attid = substr(strrchr($log->url,"="),1);
-                    //Get the new_id of the attempt (to recode the url field)
-                    $att = backup_getid($restore->backup_unique_code,"quiz_attempts",$attid);
-                    if ($att) {
-                        $log->url = "review.php?id=".$log->cmid."&attempt=".$att->new_id;
-                        $log->info = $mod->new_id;
-                        $status = true;
-                    }
-                }
-            }
-            break;
-        case "submit":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the info field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    //Extract the attempt id from the url field
-                    $attid = substr(strrchr($log->url,"="),1);
-                    //Get the new_id of the attempt (to recode the url field)
-                    $att = backup_getid($restore->backup_unique_code,"quiz_attempts",$attid);
-                    if ($att) {
-                        $log->url = "review.php?id=".$log->cmid."&attempt=".$att->new_id;
-                        $log->info = $mod->new_id;
-                        $status = true;
-                    }
-                }
-            }
-            break;
-        case "review":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the info field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    //Extract the attempt id from the url field
-                    $attid = substr(strrchr($log->url,"="),1);
-                    //Get the new_id of the attempt (to recode the url field)
-                    $att = backup_getid($restore->backup_unique_code,"quiz_attempts",$attid);
-                    if ($att) {
-                        $log->url = "review.php?id=".$log->cmid."&attempt=".$att->new_id;
-                        $log->info = $mod->new_id;
-                        $status = true;
-                    }
-                }
-            }
-            break;
-        case "editquestions":
-            if ($log->cmid) {
-                //Get the new_id of the module (to recode the url field)
-                $mod = backup_getid($restore->backup_unique_code,$log->module,$log->info);
-                if ($mod) {
-                    $log->url = "view.php?id=".$log->cmid;
-                    $log->info = $mod->new_id;
-                    $status = true;
-                }
-            }
-            break;
-        default:
-            if (!defined('RESTORE_SILENTLY')) {
-                echo "action (".$log->module."-".$log->action.") unknow. Not restored<br />";                 //Debug
-            }
-            break;
-        }
-
-        if ($status) {
-            $status = $log;
-        }
-        re