diff --git a/backup/moodle2/backup_final_task.class.php b/backup/moodle2/backup_final_task.class.php
index 83058866636cf..d39a20e610a44 100644
--- a/backup/moodle2/backup_final_task.class.php
+++ b/backup/moodle2/backup_final_task.class.php
@@ -48,6 +48,9 @@ public function build() {
// 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 @@ public function build() {
// 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
diff --git a/backup/moodle2/backup_qtype_plugin.class.php b/backup/moodle2/backup_qtype_plugin.class.php
index e44cf397cb7ae..c0d48436f4260 100644
--- a/backup/moodle2/backup_qtype_plugin.class.php
+++ b/backup/moodle2/backup_qtype_plugin.class.php
@@ -143,7 +143,7 @@ protected function add_question_datasets($element) {
$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 @@ protected function add_question_datasets($element) {
// 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();
+ }
}
diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php
index e0f6c9f5de85c..d03ae24390fed 100644
--- a/backup/moodle2/backup_stepslib.php
+++ b/backup/moodle2/backup_stepslib.php
@@ -160,7 +160,7 @@ protected function prepare_activity_structure($activitystructure) {
/**
* 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 @@ protected function define_execution() {
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 @@ protected function define_structure() {
$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;
}
diff --git a/backup/moodle2/restore_final_task.class.php b/backup/moodle2/restore_final_task.class.php
index 9febd7269546f..cd82ab8f3e279 100644
--- a/backup/moodle2/restore_final_task.class.php
+++ b/backup/moodle2/restore_final_task.class.php
@@ -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.
diff --git a/backup/moodle2/restore_plan_builder.class.php b/backup/moodle2/restore_plan_builder.class.php
index 3f096899e4dfd..46ea999c091ae 100644
--- a/backup/moodle2/restore_plan_builder.class.php
+++ b/backup/moodle2/restore_plan_builder.class.php
@@ -31,6 +31,10 @@
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
index 0000000000000..bb0898de6e600
--- /dev/null
+++ b/backup/moodle2/restore_plugin.class.php
@@ -0,0 +1,172 @@
+.
+
+/**
+ * @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
index 0000000000000..91cebff60ad05
--- /dev/null
+++ b/backup/moodle2/restore_qtype_plugin.class.php
@@ -0,0 +1,302 @@
+.
+
+/**
+ * @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;
+ }
+}
diff --git a/backup/moodle2/restore_root_task.class.php b/backup/moodle2/restore_root_task.class.php
index 239d20bd82c9c..04b7f4b5c3832 100644
--- a/backup/moodle2/restore_root_task.class.php
+++ b/backup/moodle2/restore_root_task.class.php
@@ -40,6 +40,9 @@ public function build() {
// 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 @@ public function build() {
$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 @@ public function build() {
// 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;
}
diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php
index 8b5bf18474fa1..9b799d26d4db0 100644
--- a/backup/moodle2/restore_stepslib.php
+++ b/backup/moodle2/restore_stepslib.php
@@ -488,10 +488,13 @@ public function process_file($data) {
// 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 @@ protected function after_execute() {
}
}
+/**
+ * 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 @@ protected function apply_activity_instance($newitemid) {
$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;
+ }
+}
diff --git a/backup/moodle2/restore_subplugin.class.php b/backup/moodle2/restore_subplugin.class.php
index 5c95b1f2a0afb..0f732dc4dc5c8 100644
--- a/backup/moodle2/restore_subplugin.class.php
+++ b/backup/moodle2/restore_subplugin.class.php
@@ -64,6 +64,23 @@ public function define_subplugin_structure($connectionpoint) {
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
diff --git a/backup/restorelib.php b/backup/restorelib.php
index fd5781e65471a..afd7c82d54096 100644
--- a/backup/restorelib.php
+++ b/backup/restorelib.php
@@ -1,28 +1,4 @@
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 @@ function restore_set_format_data($restore,$xml_file) {
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 '
';
- }
-
- // 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 '- '.get_string('gradeletters','grades').'
';
- }
- // 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 '- '.get_string('gradeitems','grades').'
';
- }
- $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 "
";
- }
- }
- 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 '- '.get_string('gradecategoryhistory','grades').'
';
- }
- $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 "
";
- }
- }
- backup_flush(300);
- }
- }
- }
- }
- }
-
- // process histories
- if ($gghcount && $status) {
- if (!defined('RESTORE_SILENTLY')) {
- echo '- '.get_string('gradegradeshistory','grades').'
';
- }
- $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 "
";
- }
- }
- backup_flush(300);
- }
- }
- }
- }
- }
-
- // process histories
-
- if ($gihcount && $status) {
- if (!defined('RESTORE_SILENTLY')) {
- echo '- '.get_string('gradeitemshistory','grades').'
';
- }
- $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 "
";
- }
- }
- backup_flush(300);
- }
- }
- }
- }
- }
-
- // process histories
- if ($gohcount && $status) {
- if (!defined('RESTORE_SILENTLY')) {
- echo '- '.get_string('gradeoutcomeshistory','grades').'
';
- }
- $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 "
";
- }
- }
- backup_flush(300);
- }
- }
- }
- }
- }
- }
-
- if (!defined('RESTORE_SILENTLY')) {
- //End ul
- echo '
';
- }
- return $status;
- }
-
//This function creates all the structures messages and contacts
function restore_create_messages($restore,$xml_file) {
global $CFG, $DB;
diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php
index 1e9ac82427d35..98fd2ce053482 100644
--- a/backup/util/dbops/restore_dbops.class.php
+++ b/backup/util/dbops/restore_dbops.class.php
@@ -233,17 +233,421 @@ public static function load_users_to_tempids($restoreid, $usersfile) {
$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 @@ public static function send_files_to_pool($basepath, $restoreid, $component, $fi
// 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 @@ public static function precheck_included_users($restoreid, $courseid, $userid, $
}
/**
- * 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 @@ public static function process_included_users($restoreid, $courseid, $userid, $s
}
}
+ /**
+ * 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;
diff --git a/backup/util/helper/restore_prechecks_helper.class.php b/backup/util/helper/restore_prechecks_helper.class.php
index 6361cd29937b8..04335160ef68a 100644
--- a/backup/util/helper/restore_prechecks_helper.class.php
+++ b/backup/util/helper/restore_prechecks_helper.class.php
@@ -120,6 +120,14 @@ public static function execute_prechecks($controller, $droptemptablesafter = fal
$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
index 0000000000000..c15a9f2d3dad6
--- /dev/null
+++ b/backup/util/helper/restore_questions_parser_processor.class.php
@@ -0,0 +1,87 @@
+.
+
+/**
+ * @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;
+ }
+}
diff --git a/backup/util/includes/restore_includes.php b/backup/util/includes/restore_includes.php
index 69dc7c6157528..4452a146b943c 100644
--- a/backup/util/includes/restore_includes.php
+++ b/backup/util/includes/restore_includes.php
@@ -40,6 +40,7 @@
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');
diff --git a/backup/util/plan/backup_structure_step.class.php b/backup/util/plan/backup_structure_step.class.php
index adf99e6fcf3f7..970e7755d6dc2 100644
--- a/backup/util/plan/backup_structure_step.class.php
+++ b/backup/util/plan/backup_structure_step.class.php
@@ -101,10 +101,10 @@ public function execute() {
// 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)
diff --git a/backup/util/plan/restore_structure_step.class.php b/backup/util/plan/restore_structure_step.class.php
index 26efb6a830525..aef63fabf97a6 100644
--- a/backup/util/plan/restore_structure_step.class.php
+++ b/backup/util/plan/restore_structure_step.class.php
@@ -98,8 +98,8 @@ public function execute() {
// 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 @@ public function apply_date_offset($value) {
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
*
diff --git a/lang/en/backup.php b/lang/en/backup.php
index 55330e0217668..b22cccd60f431 100644
--- a/lang/en/backup.php
+++ b/lang/en/backup.php
@@ -134,6 +134,10 @@
$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';
diff --git a/mod/forum/backup/moodle2/backup_forum_stepslib.php b/mod/forum/backup/moodle2/backup_forum_stepslib.php
index 36bcf97aebb05..9eb48c26132e4 100644
--- a/mod/forum/backup/moodle2/backup_forum_stepslib.php
+++ b/mod/forum/backup/moodle2/backup_forum_stepslib.php
@@ -76,6 +76,11 @@ protected function define_structure() {
'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 @@ protected function define_structure() {
$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 @@ protected function define_structure() {
$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 @@ protected function define_structure() {
$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
diff --git a/mod/forum/backup/moodle2/restore_forum_stepslib.php b/mod/forum/backup/moodle2/restore_forum_stepslib.php
index 3f78f1515ae53..17e882086441a 100644
--- a/mod/forum/backup/moodle2/restore_forum_stepslib.php
+++ b/mod/forum/backup/moodle2/restore_forum_stepslib.php
@@ -43,6 +43,7 @@ protected function define_structure() {
$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 @@ protected function process_forum_read($data) {
$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;
diff --git a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
index 4cc027a15e06d..7a05c5a49c185 100644
--- a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
+++ b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
@@ -44,7 +44,8 @@ protected function define_structure() {
'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 @@ protected function define_structure() {
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 @@ protected function define_structure() {
$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
index 0000000000000..bf79c7fae189f
--- /dev/null
+++ b/mod/quiz/backup/moodle2/restore_quiz_activity_task.class.php
@@ -0,0 +1,77 @@
+.
+
+/**
+ * @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
index 0000000000000..41e535d7757e3
--- /dev/null
+++ b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php
@@ -0,0 +1,176 @@
+.
+
+/**
+ * @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');
+ }
+}
diff --git a/mod/quiz/restorelib.php b/mod/quiz/restorelib.php
index b64053e49edd7..ad24dc052be48 100644
--- a/mod/quiz/restorelib.php
+++ b/mod/quiz/restorelib.php
@@ -1,627 +1,4 @@
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 "".get_string("modulename","quiz")." \"".format_string($quiz->name,true)."\"";
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 '
'.s($content).'
changed to
'.s($result).'
';
- }
- }
- }
- //Do some output
- if (($i+1) % 5 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 100 == 0) {
- echo "
";
- }
- }
- 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 "
";
- }
- }
- 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.
@@ -833,20 +210,3 @@ function quiz_restore_logs($restore,$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
index 78061fa76755b..0000000000000
--- a/mod/quiz/restorelibpre15.php
+++ /dev/null
@@ -1,1907 +0,0 @@
-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 "".get_string('category', 'quiz')." \"".$quiz_cat->name."\"
";
- }
- } else {
- if (!defined('RESTORE_SILENTLY')) {
- //We must never arrive here !!
- echo "".get_string('category', 'quiz')." \"".$quiz_cat->name."\" Error!
";
- }
- $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 '';
- }
- }
-
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "".get_string("modulename","quiz")." \"".format_string($quiz->name,true)."\"";
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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 "
";
- }
- }
- 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
"; //Debug
- }
- break;
- }
-
- if ($status) {
- $status = $log;
- }
- return $status;
- }
-
diff --git a/question/restorelib.php b/question/restorelib.php
index cad37de569081..c832e37ee1f1c 100644
--- a/question/restorelib.php
+++ b/question/restorelib.php
@@ -1,965 +1,4 @@
id)
- // |
- // |
- // |.......................................
- // | .
- // | .
- // | -------question_datasets------ .
- // | | (CL,pk->id,fk->question, | .
- // | | fk->dataset_definition) | .
- // | | | .
- // | | | .
- // | | | .
- // | | question_dataset_definitions
- // | | (CL,pk->id,fk->category)
- // question |
- // (CL,pk->id,fk->category,files) |
- // | question_dataset_items
- // | (CL,pk->id,fk->definition)
- // |
- // |
- // |
- // --------------------------------------------------------------------------------------------------------------
- // | | | | | | |
- // | | | | | | |
- // | | | | question_calculated | |
- // question_truefalse | question_multichoice | (CL,pl->id,fk->question) | |
- // (CL,pk->id,fk->question) | (CL,pk->id,fk->question) | . | | question_randomsamatch
- // . | . | . | |--(CL,pk->id,fk->question)
- // . question_shortanswer . question_numerical . question_multianswer. |
- // . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) . (CL,pk->id,fk->question) |
- // . . . . . . | question_match
- // . . . . . . |--(CL,pk->id,fk->question)
- // . . . . . . | .
- // . . . . . . | .
- // . . . . . . | .
- // . . . . . . | question_match_sub
- // ........................................................................................ |--(CL,pk->id,fk->question)
- // . |
- // . |
- // . | question_numerical_units
- // question_answers |--(CL,pk->id,fk->question)
- // (CL,pk->id,fk->question)----------------------------------------------------------
- //
- //
- // The following holds the information about student interaction with the questions
- //
- // question_sessions
- // (UL,pk->id,fk->attempt,question)
- // .
- // .
- // question_states
- // (UL,pk->id,fk->attempt,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
- //
- //-----------------------------------------------------------
-
- include_once($CFG->libdir.'/questionlib.php');
-
- /**
- * Returns the best question category (id) found to restore one
- * question category from a backup file. Works by stamp.
- *
- * @param object $restore preferences for restoration
- * @param array $contextinfo fragment of decoded xml
- * @return object best context instance for this category to be in
- */
- function restore_question_get_best_category_context($restore, $contextinfo) {
- global $DB;
-
- switch ($contextinfo['LEVEL'][0]['#']) {
- case 'module':
- if (!$instanceinfo = backup_getid($restore->backup_unique_code, 'course_modules', $contextinfo['INSTANCE'][0]['#'])){
- //module has not been restored, probably not selected for restore
- return false;
- }
- $tocontext = get_context_instance(CONTEXT_MODULE, $instanceinfo->new_id);
- break;
- case 'course':
- $tocontext = get_context_instance(CONTEXT_COURSE, $restore->course_id);
- break;
- case 'coursecategory':
- //search COURSECATEGORYLEVEL steps up the course cat tree or
- //to the top of the tree if steps are exhausted.
- $catno = $contextinfo['COURSECATEGORYLEVEL'][0]['#'];
- $catid = $DB->get_field('course', 'category', array('id'=>$restore->course_id));
- while ($catno > 1){
- $nextcatid = $DB->get_field('course_categories', 'parent', array('id'=>$catid));
- if ($nextcatid == 0){
- break;
- }
- $catid = $nextcatid;
- $catno--;
- }
- $tocontext = get_context_instance(CONTEXT_COURSECAT, $catid);
- break;
- case 'system':
- $tocontext = get_context_instance(CONTEXT_SYSTEM);
- break;
- }
- return $tocontext;
- }
-
- function restore_question_categories($info, $restore) {
- $status = true;
- //Iterate over each category
- foreach ($info as $category) {
- $status = $status && restore_question_category($category, $restore);
- }
- $status = $status && restore_recode_category_parents($restore);
- return $status;
- }
-
- function restore_question_category($category, $restore){
- global $DB;
-
- $status = true;
- //Skip empty categories (some backups can contain them)
- if (!empty($category->id)) {
- //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
- $question_cat = new stdClass;
- $question_cat->name = backup_todb($info['QUESTION_CATEGORY']['#']['NAME']['0']['#']);
- $question_cat->info = backup_todb($info['QUESTION_CATEGORY']['#']['INFO']['0']['#']);
- $question_cat->stamp = backup_todb($info['QUESTION_CATEGORY']['#']['STAMP']['0']['#']);
- //parent is fixed after all categories are restored and we know all the new ids.
- $question_cat->parent = backup_todb($info['QUESTION_CATEGORY']['#']['PARENT']['0']['#']);
- $question_cat->sortorder = backup_todb($info['QUESTION_CATEGORY']['#']['SORTORDER']['0']['#']);
- if (!$question_cat->stamp) {
- $question_cat->stamp = make_unique_id_code();
- }
- if (isset($info['QUESTION_CATEGORY']['#']['PUBLISH'])) {
- $course = $restore->course_id;
- $publish = backup_todb($info['QUESTION_CATEGORY']['#']['PUBLISH']['0']['#']);
- if ($publish){
- $tocontext = get_context_instance(CONTEXT_SYSTEM);
- } else {
- $tocontext = get_context_instance(CONTEXT_COURSE, $course);
- }
- } else {
- if (!$tocontext = restore_question_get_best_category_context($restore, $info['QUESTION_CATEGORY']['#']['CONTEXT']['0']['#'])){
- return $status; // context doesn't exist - a module has not been restored
- }
- }
- $question_cat->contextid = $tocontext->id;
-
- //does cat exist ?? if it does we check if the cat and questions already exist whether we have
- //add permission or not if we have no permission to add questions to SYSTEM or COURSECAT context
- //AND the question does not already exist then we create questions in COURSE context.
- if (!$fcat = $DB->get_record('question_categories', array('contextid'=>$question_cat->contextid, 'stamp'=>$question_cat->stamp))) {
- //no preexisting cat
- if ((($tocontext->contextlevel == CONTEXT_SYSTEM) || ($tocontext->contextlevel == CONTEXT_COURSECAT))
- && !has_capability('moodle/question:add', $tocontext)){
- //no preexisting cat and no permission to create questions here
- //must restore to course.
- $tocontext = get_context_instance(CONTEXT_COURSE, $restore->course_id);
- }
- $question_cat->contextid = $tocontext->id;
- if (!$fcat = $DB->get_record('question_categories', array('contextid'=>$question_cat->contextid, 'stamp'=>$question_cat->stamp))) {
- $question_cat->id = $DB->insert_record ("question_categories", $question_cat);
- } else {
- $question_cat = $fcat;
- }
- //we'll be restoring all questions here.
- backup_putid($restore->backup_unique_code, "question_categories", $category->id, $question_cat->id);
- } else {
- $question_cat = $fcat;
- //we found an existing best category
- //but later if context is above course need to check if there are questions need creating in category
- //if we do need to create questions and permissions don't allow it create new category in course
- }
-
- //Do some output
- if (!defined('RESTORE_SILENTLY')) {
- echo "".get_string('category', 'quiz')." \"".$question_cat->name."\"
";
- }
-
- backup_flush(300);
-
- //start with questions
- if ($question_cat->id) {
- //We have the newid, update backup_ids
- //Now restore question
- $status = restore_questions($category->id, $question_cat, $info, $restore);
- } else {
- $status = false;
- }
- if (!defined('RESTORE_SILENTLY')) {
- echo '';
- }
- } else {
- echo 'Could not get backup info for question category'. $category->id;
- }
- }
- return $status;
- }
-
- function restore_recode_category_parents($restore){
- global $CFG, $DB;
- $status = true;
- //Now we have to recode the parent field of each restored category
- $categories = $DB->get_records_sql("SELECT old_id, new_id
- FROM {backup_ids}
- WHERE backup_code = ? AND
- table_name = 'question_categories'", array($restore->backup_unique_code));
- if ($categories) {
- //recode all parents to point at their old parent cats no matter what context the parent is now in
- foreach ($categories as $category) {
- $restoredcategory = $DB->get_record('question_categories', array('id'=>$category->new_id));
- if ($restoredcategory && $restoredcategory->parent != 0) {
- $updateobj = new stdClass();
- $updateobj->id = $restoredcategory->id;
- $idcat = backup_getid($restore->backup_unique_code,'question_categories',$restoredcategory->parent);
- if ($idcat->new_id) {
- $updateobj->parent = $idcat->new_id;
- } else {
- $updateobj->parent = 0;
- }
- $DB->update_record('question_categories', $updateobj);
- }
- }
- //now we have recoded all parents, check through all parents and set parent to be
- //grand parent / great grandparent etc where there is one in same context
- //or else set parent to 0 (top level category).
- $toupdate = array();
- foreach ($categories as $category) {
- $restoredcategory = $DB->get_record('question_categories', array('id'=>$category->new_id));
- if ($restoredcategory && $restoredcategory->parent != 0) {
- $nextparentid = $restoredcategory->parent;
- do {
- if (!$parent = $DB->get_record('question_categories', array('id'=>$nextparentid))) {
- if (!defined('RESTORE_SILENTLY')) {
- echo 'Could not find parent for question category '. $category->id.' recoding as top category item.
';
- }
- break;//record fetch failed finish loop
- } else {
- $nextparentid = $parent->parent;
- }
- } while (($nextparentid != 0) && ($parent->contextid != $restoredcategory->contextid));
- if (!$parent || ($parent->id != $restoredcategory->parent)){
- //change needs to be made to the parent field.
- if ($parent && ($parent->contextid == $restoredcategory->contextid)){
- $toupdate[$restoredcategory->id] = $parent->id;
- } else {
- //searched up the tree till we came to the top and did not find cat in same
- //context or there was an error getting next parent record
- $toupdate[$restoredcategory->id] = 0;
- }
- }
- }
- }
- //now finally do the changes to parent field.
- foreach ($toupdate as $id => $parent){
- $updateobj = new stdClass();
- $updateobj->id = $id;
- $updateobj->parent = $parent;
- $DB->update_record('question_categories', $updateobj);
- }
- }
- return $status;
- }
-
- function restore_questions ($old_category_id, $best_question_cat, $info, $restore) {
- global $CFG, $QTYPES, $DB;
-
- $status = true;
- $restored_questions = array();
-
- //Get the questions array
- if (!empty($info['QUESTION_CATEGORY']['#']['QUESTIONS'])) {
- $questions = $info['QUESTION_CATEGORY']['#']['QUESTIONS']['0']['#']['QUESTION'];
- } else {
- $questions = array();
- }
-
- //Iterate over questions
- for($i = 0; $i < sizeof($questions); $i++) {
- $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 = new stdClass();
- $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->generalfeedback = backup_todb_optional_field($que_info, 'GENERALFEEDBACK', '');
- $question->defaultgrade = backup_todb($que_info['#']['DEFAULTGRADE']['0']['#']);
- $question->penalty = backup_todb($que_info['#']['PENALTY']['0']['#']);
- $question->qtype = backup_todb($que_info['#']['QTYPE']['0']['#']);
- $question->length = backup_todb($que_info['#']['LENGTH']['0']['#']);
- $question->stamp = backup_todb($que_info['#']['STAMP']['0']['#']);
- $question->version = backup_todb($que_info['#']['VERSION']['0']['#']);
- $question->hidden = backup_todb($que_info['#']['HIDDEN']['0']['#']);
- $question->timecreated = backup_todb_optional_field($que_info, 'TIMECREATED', 0);
- $question->timemodified = backup_todb_optional_field($que_info, 'TIMEMODIFIED', 0);
-
- // Set the createdby field, if the user was in the backup, or if we are on the same site.
- $createdby = backup_todb_optional_field($que_info, 'CREATEDBY', null);
- if (!empty($createdby)) {
- $user = backup_getid($restore->backup_unique_code, 'user', $createdby);
- if ($user) {
- $question->createdby = $user->new_id;
- } else if (backup_is_same_site($restore)) {
- $question->createdby = $createdby;
- }
- }
-
- // Set the modifiedby field, if the user was in the backup, or if we are on the same site.
- $modifiedby = backup_todb_optional_field($que_info, 'MODIFIEDBY', null);
- if (!empty($createdby)) {
- $user = backup_getid($restore->backup_unique_code, 'user', $modifiedby);
- if ($user) {
- $question->modifiedby = $user->new_id;
- } else if (backup_is_same_site($restore)) {
- $question->modifiedby = $modifiedby;
- }
- }
-
- if ($restore->backup_version < 2006032200) {
- // The qtype was an integer that now needs to be converted to the name
- $qtypenames = array(1=>'shortanswer',2=>'truefalse',3=>'multichoice',4=>'random',5=>'match',
- 6=>'randomsamatch',7=>'description',8=>'numerical',9=>'multianswer',10=>'calculated',
- 11=>'rqp',12=>'essay');
- $question->qtype = $qtypenames[$question->qtype];
- }
-
- //Check if the question exists by category, stamp, and version
- //first check for the question in the context specified in backup
- $existingquestion = $DB->get_record ("question", array("category"=>$best_question_cat->id, "stamp"=>$question->stamp,"version"=>$question->version));
- //If the question exists, only record its id
- //always use existing question, no permissions check here
- if ($existingquestion) {
- $question = $existingquestion;
- $creatingnewquestion = false;
- } else {
- //then if context above course level check permissions and if no permission
- //to restore above course level then restore to cat in course context.
- $bestcontext = get_context_instance_by_id($best_question_cat->contextid);
- if (($bestcontext->contextlevel == CONTEXT_SYSTEM || $bestcontext->contextlevel == CONTEXT_COURSECAT)
- && !has_capability('moodle/question:add', $bestcontext)){
- if (!isset($course_question_cat)) {
- $coursecontext = get_context_instance(CONTEXT_COURSE, $restore->course_id);
- $course_question_cat = clone($best_question_cat);
- $course_question_cat->contextid = $coursecontext->id;
- //create cat if it doesn't exist
- if (!$fcat = $DB->get_record('question_categories', array('contextid'=>$course_question_cat->contextid, 'stamp'=>$course_question_cat->stamp))) {
- $course_question_cat->id = $DB->insert_record("question_categories", $course_question_cat);
- backup_putid($restore->backup_unique_code, "question_categories", $old_category_id, $course_question_cat->id);
- } else {
- $course_question_cat = $fcat;
- }
- //will fix category parents after all questions and categories restored. Will set parent to 0 if
- //no parent in same context.
- }
- $question->category = $course_question_cat->id;
- //does question already exist in course cat
- $existingquestion = $DB->get_record("question", array("category"=>$question->category, "stamp"=>$question->stamp, "version"=>$question->version));
- } else {
- //permissions ok, restore to best cat
- $question->category = $best_question_cat->id;
- }
- if (!$existingquestion){
- //The structure is equal to the db, so insert the question
- $question->id = $DB->insert_record ("question", $question);
- $creatingnewquestion = true;
- } else {
- $question = $existingquestion;
- $creatingnewquestion = false;
- }
- }
-
- // Fixing bug #5482: random questions have parent field set to its own id,
- // see: $QTYPES['random']->get_question_options()
- if ($question->qtype == 'random' && $creatingnewquestion) {
- $question->parent = $question->id;
- $DB->set_field('question', 'parent', $question->parent, array('id'=>$question->id));
- }
-
- //Save newid to backup tables
- if ($question->id) {
- //We have the newid, update backup_ids
- backup_putid($restore->backup_unique_code, "question", $oldid, $question->id);
- }
-
- $restored_questions[$i] = new stdClass;
- $restored_questions[$i]->newid = $question->id;
- $restored_questions[$i]->oldid = $oldid;
- $restored_questions[$i]->qtype = $question->qtype;
- $restored_questions[$i]->parent = $question->parent;
- $restored_questions[$i]->is_new = $creatingnewquestion;
- }
- backup_flush(300);
-
- // Loop again, now all the question id mappings exist, so everything can
- // be restored.
- for($i = 0; $i < sizeof($questions); $i++) {
- $que_info = $questions[$i];
-
- $newid = $restored_questions[$i]->newid;
- $oldid = $restored_questions[$i]->oldid;
-
- $question = new stdClass();
- $question->qtype = $restored_questions[$i]->qtype;
- $question->parent = $restored_questions[$i]->parent;
-
-
- /// If it's a new question in the DB, restore it
- if ($restored_questions[$i]->is_new) {
-
- /// We have to recode the parent field
- if ($question->parent && $question->qtype != 'random') {
- /// If the parent field needs to be changed, do it here. Random questions are dealt with above.
- if ($parent = backup_getid($restore->backup_unique_code,"question",$question->parent)) {
- $question->parent = $parent->new_id;
- if ($question->parent != $restored_questions[$i]->parent) {
- $DB->set_field('question', 'parent', $question->parent, array('id'=>$newid));
- }
- } else {
- echo 'Could not recode parent '.$question->parent.' for question '.$oldid.'
';
- $status = false;
- }
- }
-
- //Now, restore every question_answers in this question
- $status = question_restore_answers($oldid,$newid,$que_info,$restore);
- // Restore questiontype specific data
- if (array_key_exists($question->qtype, $QTYPES)) {
- $status = $QTYPES[$question->qtype]->restore($oldid,$newid,$que_info,$restore);
- } else {
- echo 'Unknown question type '.$question->qtype.' for question '.$oldid.'
';
- $status = false;
- }
- } 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 states
- //in each attempt.
- $status = question_restore_map_answers($oldid,$newid,$que_info,$restore);
- // Do the questiontype specific mapping
- if (array_key_exists($question->qtype, $QTYPES)) {
- $status = $QTYPES[$question->qtype]->restore_map($oldid,$newid,$que_info,$restore);
- } else {
- echo 'Unknown question type '.$question->qtype.' for question '.$oldid.'
';
- $status = false;
- }
- }
-
- //Do some output
- if (($i+1) % 2 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 40 == 0) {
- echo "
";
- }
- }
- backup_flush(300);
- }
- }
- return $status;
- }
-
- function backup_todb_optional_field($data, $field, $default) {
- if (array_key_exists($field, $data['#'])) {
- return backup_todb($data['#'][$field]['0']['#']);
- } else {
- return $default;
- }
- }
-
- function question_restore_answers ($old_question_id,$new_question_id,$info,$restore) {
- global $CFG, $DB;
-
- $status = true;
- $qtype = backup_todb($info['#']['QTYPE']['0']['#']);
-
- //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 = new stdClass;
- $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']['#']);
-
- // Update 'match everything' answers for numerical questions coming from old backup files.
- if ($qtype == 'numerical' && $answer->answer == '') {
- $answer->answer = '*';
- }
-
- //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 "
";
- }
- }
- 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 question_restore_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 states (user level).
-
- //Get the answer from DB (by question and answer)
- $db_answer = $DB->get_record ("question_answers", array("question"=>$new_question_id, "answer"=>$answer->answer));
-
- //Do some output
- if (($i+1) % 50 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 1000 == 0) {
- echo "
";
- }
- }
- 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 question_restore_numerical_units($old_question_id,$new_question_id,$info,$restore) {
- global $CFG, $DB;
-
- $status = true;
-
- //Get the numerical array
- if (!empty($info['#']['NUMERICAL_UNITS'])) {
- $numerical_units = $info['#']['NUMERICAL_UNITS']['0']['#']['NUMERICAL_UNIT'];
- } else {
- $numerical_units = array();
- }
-
- //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
-
- // Check to see if this until already exists in the database, which it might, for
- // Historical reasons.
- $unit = backup_todb($nu_info['#']['UNIT']['0']['#']);
- if (!$DB->record_exists('question_numerical_units', array('question'=>$new_question_id, 'unit'=>$unit))) {
-
- //Now, build the question_numerical_UNITS record structure.
- $numerical_unit = new stdClass;
- $numerical_unit->question = $new_question_id;
- $numerical_unit->multiplier = backup_todb($nu_info['#']['MULTIPLIER']['0']['#']);
- $numerical_unit->unit = $unit;
-
- //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 question_restore_numerical_options($old_question_id,$new_question_id,$info,$restore) {
- global $CFG, $DB;
-
- $status = true;
- //Get the numerical_options array
- // need to check as old questions don't have calculated_options record
- if(isset($info['#']['NUMERICAL_OPTIONS'])){
- $numerical_options = $info['#']['numerical_OPTIONS'];
-
- //Iterate over numerical_options
- for($i = 0; $i < sizeof($numerical_options); $i++){
- $num_info = $numerical_options[$i];
- //traverse_xmlize($cal_info); //Debug
- //print_object ($GLOBALS['traverse_array']); //Debug
- //$GLOBALS['traverse_array']=""; //Debug
-
- //Now, build the question_numerical_options record structure
- $numerical_options->questionid = $new_question_id;
- $numerical_options->instructions = backup_todb($num_info['#']['INSTRUCTIONS']['0']['#']);
- $numerical_options->showunits = backup_todb($num_info['#']['SHOWUNITS']['0']['#']);
- $numerical_options->unitsleft = backup_todb($num_info['#']['UNITSLEFT']['0']['#']);
- $numerical_options->unitgradingtype = backup_todb($num_info['#']['UNITGRADINGTYPE']['0']['#']);
- $numerical_options->unitpenalty = backup_todb($num_info['#']['UNITPENALTY']['0']['#']);
-
- //The structure is equal to the db, so insert the question_numerical__options
- $newid = $DB->insert_record ("question_numerical__options",$numerical__options);
-
- //Do some output
- if (($i+1) % 50 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 1000 == 0) {
- echo "
";
- }
- }
- backup_flush(300);
- }
- }
- }
- }
-
-
- function question_restore_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 = new stdClass;
- $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;
- } else {
- echo 'Could not recode category id '.$dataset_definition->category.' for dataset definition'.$dataset_definition->name.'
';
- }
- }
-
- //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 = question_restore_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 = new stdClass;
- $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 question_restore_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 = new stdClass;
- $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;
- }
-
-
- //This function restores the question_states
- function question_states_restore_mods($attempt_id,$info,$restore) {
- global $CFG, $QTYPES, $DB;
-
- $status = true;
-
- //Get the question_states array
- $states = $info['#']['STATES']['0']['#']['STATE'];
- //Iterate over states
- for($i = 0; $i < sizeof($states); $i++) {
- $res_info = $states[$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 STATES record structure
- $state = new stdClass;
- $state->attempt = $attempt_id;
- $state->question = backup_todb($res_info['#']['QUESTION']['0']['#']);
- $state->seq_number = backup_todb($res_info['#']['SEQ_NUMBER']['0']['#']);
- $state->answer = backup_todb($res_info['#']['ANSWER']['0']['#']);
- $state->timestamp = backup_todb($res_info['#']['TIMESTAMP']['0']['#']);
- $state->event = backup_todb($res_info['#']['EVENT']['0']['#']);
- $state->grade = backup_todb($res_info['#']['GRADE']['0']['#']);
- $state->raw_grade = backup_todb($res_info['#']['RAW_GRADE']['0']['#']);
- $state->penalty = backup_todb($res_info['#']['PENALTY']['0']['#']);
- $state->oldid = $oldid; // So it is available to restore_recode_answer.
-
- //We have to recode the question field
- $question = backup_getid($restore->backup_unique_code,"question",$state->question);
- if ($question) {
- $state->question = $question->new_id;
- } else {
- echo 'Could not recode question id '.$state->question.' for state '.$oldid.'
';
- }
-
- //We have to recode the answer field
- //It depends of the question type !!
- //We get the question first
- if (!$question = $DB->get_record("question", array("id"=>$state->question))) {
- print_error("Can't find the record for question $state->question for which I am trying to restore a state");
- }
- //Depending on the qtype, we make different recodes
- if ($state->answer) {
- $state->answer = $QTYPES[$question->qtype]->restore_recode_answer($state, $restore);
- }
-
- //The structure is equal to the db, so insert the question_states
- $newid = $DB->insert_record ("question_states",$state);
-
- //Do some output
- if (($i+1) % 10 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 200 == 0) {
- echo "
";
- }
- }
- 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;
- }
- }
-
- //Get the question_sessions array
- $sessions = $info['#']['NEWEST_STATES']['0']['#']['NEWEST_STATE'];
- //Iterate over question_sessions
- for($i = 0; $i < sizeof($sessions); $i++) {
- $res_info = $sessions[$i];
- //traverse_xmlize($res_info); //Debug
- //print_object ($GLOBALS['traverse_array']); //Debug
- //$GLOBALS['traverse_array']=""; //Debug
-
- //Now, build the NEWEST_STATES record structure
- $session = new stdClass;
- $session->attemptid = $attempt_id;
- $session->questionid = backup_todb($res_info['#']['QUESTIONID']['0']['#']);
- $session->newest = backup_todb($res_info['#']['NEWEST']['0']['#']);
- $session->newgraded = backup_todb($res_info['#']['NEWGRADED']['0']['#']);
- $session->sumpenalty = backup_todb($res_info['#']['SUMPENALTY']['0']['#']);
-
- if (isset($res_info['#']['MANUALCOMMENT']['0']['#'])) {
- $session->manualcomment = backup_todb($res_info['#']['MANUALCOMMENT']['0']['#']);
- } else { // pre 1.7 backups
- $session->manualcomment = backup_todb($res_info['#']['COMMENT']['0']['#']);
- }
-
- //We have to recode the question field
- $question = backup_getid($restore->backup_unique_code,"question",$session->questionid);
- if ($question) {
- $session->questionid = $question->new_id;
- } else {
- echo 'Could not recode question id '.$session->questionid.'
';
- }
-
- //We have to recode the newest field
- $state = backup_getid($restore->backup_unique_code,"question_states",$session->newest);
- if ($state) {
- $session->newest = $state->new_id;
- } else {
- echo 'Could not recode newest state id '.$session->newest.'
';
- }
-
- //If the session has been graded we have to recode the newgraded field
- if ($session->newgraded) {
- $state = backup_getid($restore->backup_unique_code,"question_states",$session->newgraded);
- if ($state) {
- $session->newgraded = $state->new_id;
- } else {
- echo 'Could not recode newest graded state id '.$session->newgraded.'
';
- }
- }
-
- //The structure is equal to the db, so insert the question_sessions
- $newid = $DB->insert_record ("question_sessions",$session);
-
- }
-
- return $status;
- }
/**
* Recode content links in question texts.
diff --git a/question/type/calculated/backup/moodle2/backup_qtype_calculated_plugin.class.php b/question/type/calculated/backup/moodle2/backup_qtype_calculated_plugin.class.php
index 9cc224dba8c2b..b82e94d5e8908 100644
--- a/question/type/calculated/backup/moodle2/backup_qtype_calculated_plugin.class.php
+++ b/question/type/calculated/backup/moodle2/backup_qtype_calculated_plugin.class.php
@@ -28,17 +28,15 @@
*/
class backup_qtype_calculated_plugin extends backup_qtype_plugin {
- protected function get_qtype_name() {
- return 'calculated';
- }
-
/**
* Returns the qtype information to attach to question element
*/
protected function define_question_plugin_structure() {
// Define the virtual plugin element with the condition to fulfill
- $plugin = $this->get_plugin_element(null, '../../qtype', $this->get_qtype_name());
+ // Note: we use $this->pluginname so for extended plugins this will work
+ // automatically: calculatedsimple and calculatedmulti
+ $plugin = $this->get_plugin_element(null, '../../qtype', $this->pluginname);
// Create one standard named plugin element (the visible container)
$pluginwrapper = new backup_nested_element($this->get_recommended_name());
@@ -86,4 +84,26 @@ protected function define_question_plugin_structure() {
return $plugin;
}
+
+ /**
+ * 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() {
+ // TODO: Discuss. Commented below are the "in theory" correct
+ // mappings for those fileareas. Instead we are using question for
+ // them, that will cause problems in the future if we want to change
+ // any of them to be 1..n (i.e. we should be always pointing to own id)
+ return array(
+ //'instruction' => 'question_numerical_option',
+ //'correctfeedback' => 'question_calculated_option',
+ //'partiallycorrectfeedback' => 'question_calculated_option',
+ //'incorrectfeedback' => 'question_calculated_option');
+ 'instruction' => 'question_created',
+ 'correctfeedback' => 'question_created',
+ 'partiallycorrectfeedback' => 'question_created',
+ 'incorrectfeedback' => 'question_created');
+ }
}
diff --git a/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php b/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php
new file mode 100644
index 0000000000000..5ed31f253acd9
--- /dev/null
+++ b/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php
@@ -0,0 +1,116 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one calculated qtype plugin
+ */
+class restore_qtype_calculated_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // This qtype uses question_answers, add them
+ $this->add_question_question_answers($paths);
+
+ // This qtype uses question_numerical_options and question_numerical_units, add them
+ $this->add_question_numerical_options($paths);
+ $this->add_question_numerical_units($paths);
+
+ // This qtype uses question datasets, add them
+ $this->add_question_datasets($paths);
+
+ // Add own qtype stuff
+ $elename = 'calculated_record';
+ $elepath = $this->get_pathfor('/calculated_records/calculated_record'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+ $elename = 'calculated_option';
+ $elepath = $this->get_pathfor('/calculated_options/calculated_option'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/calculated_record element
+ */
+ public function process_calculated_record($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_calculated too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ $data->answer = $this->get_mappingid('question_answer', $data->answer);
+ // Insert record
+ $newitemid = $DB->insert_record('question_calculated', $data);
+ // Create mapping (not needed, no files nor childs nor states here)
+ //$this->set_mapping('question_calculated', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+
+ /**
+ * Process the qtype/calculated_option element
+ */
+ public function process_calculated_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_calculated too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ // Insert record
+ $newitemid = $DB->insert_record('question_calculated_options', $data);
+ // Create mapping (not needed, no files nor childs nor states here)
+ // $this->set_mapping('question_calculated_option', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+}
diff --git a/question/type/calculated/questiontype.php b/question/type/calculated/questiontype.php
index 8552ebc3facf9..b0faf5e344fdd 100644
--- a/question/type/calculated/questiontype.php
+++ b/question/type/calculated/questiontype.php
@@ -2042,108 +2042,6 @@ function get_virtual_qtype() {
return $this->virtualqtype;
}
- /// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $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;
- }
-
- //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 "
";
- }
- }
- backup_flush(300);
- }
- //Get the calculated_options array
- // need to check as old questions don't have calculated_options record
- if(isset($info['#']['CALCULATED_OPTIONS'])){
- $calculatedoptions = $info['#']['CALCULATED_OPTIONS'];
-
- //Iterate over calculated_options
- for($i = 0; $i < sizeof($calculatedoptions); $i++){
- $cal_info = $calculatedoptions[$i];
- //traverse_xmlize($cal_info); //Debug
- //print_object ($GLOBALS['traverse_array']); //Debug
- //$GLOBALS['traverse_array']=""; //Debug
-
- //Now, build the question_calculated_options record structure
- $calculated_options->questionid = $new_question_id;
- $calculated_options->synchronize = backup_todb($cal_info['#']['SYNCHRONIZE']['0']['#']);
- $calculated_options->single = backup_todb($cal_info['#']['SINGLE']['0']['#']);
- $calculated_options->shuffleanswers = isset($cal_info['#']['SHUFFLEANSWERS']['0']['#'])?backup_todb($mul_info['#']['SHUFFLEANSWERS']['0']['#']):'';
- $calculated_options->correctfeedback = backup_todb($cal_info['#']['CORRECTFEEDBACK']['0']['#']);
- $calculated_options->partiallycorrectfeedback = backup_todb($cal_info['#']['PARTIALLYCORRECTFEEDBACK']['0']['#']);
- $calculated_options->incorrectfeedback = backup_todb($cal_info['#']['INCORRECTFEEDBACK']['0']['#']);
- $calculated_options->answernumbering = backup_todb($cal_info['#']['ANSWERNUMBERING']['0']['#']);
-
- //The structure is equal to the db, so insert the question_calculated_options
- $newid = $DB->insert_record ("question_calculated_options",$calculated_options);
-
- //Do some output
- if (($i+1) % 50 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 1000 == 0) {
- echo "
";
- }
- }
- backup_flush(300);
- }
- }
- }
- //Now restore numerical_units
- $status = question_restore_numerical_units ($old_question_id,$new_question_id,$cal_info,$restore);
- $status = question_restore_numerical_options($old_question_id,$new_question_id,$info,$restore);
- //Now restore dataset_definitions
- if ($status && $newid) {
- $status = question_restore_dataset_definitions ($old_question_id,$new_question_id,$cal_info,$restore);
- }
-
- if (!$newid) {
- $status = false;
- }
- }
-
- return $status;
- }
-
/**
* Runs all the code required to set up and save an essay question for testing purposes.
* Alternate DB table prefix may be used to facilitate data deletion.
diff --git a/question/type/calculatedmulti/backup/moodle2/backup_qtype_calculatedmulti_plugin.class.php b/question/type/calculatedmulti/backup/moodle2/backup_qtype_calculatedmulti_plugin.class.php
index 26f2443a7874b..c71382d2cbf2e 100644
--- a/question/type/calculatedmulti/backup/moodle2/backup_qtype_calculatedmulti_plugin.class.php
+++ b/question/type/calculatedmulti/backup/moodle2/backup_qtype_calculatedmulti_plugin.class.php
@@ -28,12 +28,4 @@
/**
* Provides the information to backup calculatedmulti questions
*/
-class backup_qtype_calculatedmulti_plugin extends backup_qtype_calculated_plugin {
-
- /**
- * overwrite this with the current qtype
- */
- protected function get_qtype_name() {
- return 'calculatedmulti';
- }
-}
+class backup_qtype_calculatedmulti_plugin extends backup_qtype_calculated_plugin {}
diff --git a/question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php b/question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php
new file mode 100644
index 0000000000000..b2183a0a04a95
--- /dev/null
+++ b/question/type/calculatedmulti/backup/moodle2/restore_qtype_calculatedmulti_plugin.class.php
@@ -0,0 +1,61 @@
+.
+
+/**
+ * @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 . '/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php');
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one calculatedmulti qtype plugin
+ */
+class restore_qtype_calculatedmulti_plugin extends restore_qtype_calculated_plugin {
+
+ /**
+ * Given one question_states record, return the answer
+ * recoded pointing to all the restored stuff for calculatedmulti questions
+ *
+ * answer format is datasetxx-yy:zz, where xx is the itemnumber in the dataset
+ * (doesn't need conversion), and both yy and zz are two (hypen speparated)
+ * lists of comma separated question_answers, the first to specify the order
+ * of the answers and the second to specify the responses.
+ *
+ * in fact, this qtype behaves exactly like the multichoice one, so we'll delegate
+ * recoding of those yy:zz to it
+ */
+ public function recode_state_answer($state) {
+ $answer = $state->answer;
+ $result = '';
+ // datasetxx-yy:zz format
+ if (preg_match('~^dataset([0-9]+)-(.*)$~', $answer, $matches)) {
+ $itemid = $matches[1];
+ $subanswer = $matches[2];
+ // Delegate subanswer recode to multichoice qtype, faking one question_states record
+ $substate = new stdClass();
+ $substate->answer = $subanswer;
+ $newanswer = $this->step->restore_recode_answer($substate, 'multichoice');
+ $result = 'dataset' . $itemid . '-' . $newanswer;
+ }
+ return $result ? $result : $answer;
+ }
+}
diff --git a/question/type/calculatedsimple/backup/moodle2/backup_qtype_calculatedsimple_plugin.class.php b/question/type/calculatedsimple/backup/moodle2/backup_qtype_calculatedsimple_plugin.class.php
index 9efbe5aebf8cc..1095bfdeedd18 100644
--- a/question/type/calculatedsimple/backup/moodle2/backup_qtype_calculatedsimple_plugin.class.php
+++ b/question/type/calculatedsimple/backup/moodle2/backup_qtype_calculatedsimple_plugin.class.php
@@ -28,12 +28,4 @@
/**
* Provides the information to backup calculatedsimple questions
*/
-class backup_qtype_calculatedsimple_plugin extends backup_qtype_calculated_plugin {
-
- /**
- * overwrite this with the current qtype
- */
- protected function get_qtype_name() {
- return 'calculatedsimple';
- }
-}
+class backup_qtype_calculatedsimple_plugin extends backup_qtype_calculated_plugin {}
diff --git a/question/type/calculatedsimple/backup/moodle2/restore_qtype_calculatedsimple_plugin.class.php b/question/type/calculatedsimple/backup/moodle2/restore_qtype_calculatedsimple_plugin.class.php
new file mode 100644
index 0000000000000..930c7ffc20c08
--- /dev/null
+++ b/question/type/calculatedsimple/backup/moodle2/restore_qtype_calculatedsimple_plugin.class.php
@@ -0,0 +1,32 @@
+.
+
+/**
+ * @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 . '/question/type/calculated/backup/moodle2/restore_qtype_calculated_plugin.class.php');
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one calculatedsimple qtype plugin
+ */
+class restore_qtype_calculatedsimple_plugin extends restore_qtype_calculated_plugin {}
diff --git a/question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php b/question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php
new file mode 100644
index 0000000000000..554443dca3e45
--- /dev/null
+++ b/question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php
@@ -0,0 +1,49 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one essay qtype plugin
+ */
+class restore_qtype_essay_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // This qtype uses question_answers, add them
+ // Crazy we use answers to store feedback!
+ $this->add_question_question_answers($paths);
+
+ // Add own qtype stuff
+ // essay qtype has not own structures (but the question_answers use above)
+
+ return $paths; // And we return the interesting paths
+ }
+}
diff --git a/question/type/match/backup/moodle2/backup_qtype_match_plugin.class.php b/question/type/match/backup/moodle2/backup_qtype_match_plugin.class.php
index c5202d2f0f6b5..5ea9e66324e82 100644
--- a/question/type/match/backup/moodle2/backup_qtype_match_plugin.class.php
+++ b/question/type/match/backup/moodle2/backup_qtype_match_plugin.class.php
@@ -64,4 +64,15 @@ protected function define_question_plugin_structure() {
return $plugin;
}
+
+ /**
+ * 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() {
+ return array(
+ 'subquestion' => 'question_match_sub');
+ }
}
diff --git a/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php b/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php
new file mode 100644
index 0000000000000..5560852265b1d
--- /dev/null
+++ b/question/type/match/backup/moodle2/restore_qtype_match_plugin.class.php
@@ -0,0 +1,171 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one match qtype plugin
+ */
+class restore_qtype_match_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // Add own qtype stuff
+ $elename = 'matchoptions';
+ $elepath = $this->get_pathfor('/matchoptions'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+ $elename = 'match';
+ $elepath = $this->get_pathfor('/matches/match'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/matchoptions element
+ */
+ public function process_matchoptions($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_match too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ // Keep question_match->subquestions unmodified
+ // after_execute_question() will perform the remapping once all subquestions
+ // have been created
+ // Insert record
+ $newitemid = $DB->insert_record('question_match', $data);
+ // Create mapping
+ $this->set_mapping('question_match', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+
+ /**
+ * Process the qtype/matches/match element
+ */
+ public function process_match($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_match_sub too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ // Insert record
+ $newitemid = $DB->insert_record('question_match_sub', $data);
+ // Create mapping (there are files and states based on this)
+ $this->set_mapping('question_match_sub', $oldid, $newitemid);
+
+ // match questions require mapping of question_match_sub, because
+ // they are used by question_states->answer
+ } else {
+ // Look for matching subquestion (by question, questiontext and answertext)
+ $sub = $DB->get_record_select('question_match_sub',
+ 'question = ? AND '.$DB->sql_compare_text('questiontext').' = '.$DB->sql_compare_text('?').' AND answertext = ?',
+ array($newquestionid, $data->questiontext, $data->answertext), 'id', IGNORE_MULTIPLE);
+ // Found, let's create the mapping
+ if ($sub) {
+ $this->set_mapping('question_match_sub', $oldid, $sub->id);
+ // Something went really wrong, cannot map subquestion for one match question
+ } else {
+ throw restore_step_exception('error_question_match_sub_missing_in_db', $data);
+ }
+ }
+ }
+
+ /**
+ * This method is executed once the whole restore_structure_step,
+ * more exactly ({@link restore_create_categories_and_questions})
+ * has ended processing the whole xml structure. Its name is:
+ * "after_execute_" + connectionpoint ("question")
+ *
+ * For match qtype we use it to restore the subquestions column,
+ * containing one list of question_match_sub ids
+ */
+ public function after_execute_question() {
+ global $DB;
+ // Now that all the question_match_subs have been restored, let's process
+ // the created question_match subquestions (list of question_match_sub ids)
+ $rs = $DB->get_recordset_sql("SELECT qm.id, qm.subquestions
+ FROM {question_match} qm
+ JOIN {backup_ids_temp} bi ON bi.newitemid = qm.question
+ WHERE bi.backupid = ?
+ AND bi.itemname = 'question_created'", array($this->get_restoreid()));
+ foreach ($rs as $rec) {
+ $subquestionsarr = explode(',', $rec->subquestions);
+ foreach ($subquestionsarr as $key => $subquestion) {
+ $subquestionsarr[$key] = $this->get_mappingid('question_match_sub', $subquestion);
+ }
+ $subquestions = implode(',', $subquestionsarr);
+ $DB->set_field('question_match', 'subquestions', $subquestions, array('id' => $rec->id));
+ }
+ $rs->close();
+ }
+
+ /**
+ * Given one question_states record, return the answer
+ * recoded pointing to all the restored stuff for match questions
+ *
+ * answer is one comma separated list of hypen separated pairs
+ * containing question_match_sub->id and question_match_sub->code
+ */
+ public function recode_state_answer($state) {
+ $answer = $state->answer;
+ $resultarr = array();
+ foreach (explode(',', $answer) as $pair) {
+ $pairarr = explode('-', $pair);
+ $id = $pairarr[0];
+ $code = $pairarr[1];
+ $newid = $this->get_mappingid('question_match_sub', $id);
+ $resultarr[] = implode('-', array($newid, $code));
+ }
+ return implode(',', $resultarr);
+ }
+}
diff --git a/question/type/match/questiontype.php b/question/type/match/questiontype.php
index b4ea788c67309..0756590cf7c8c 100644
--- a/question/type/match/questiontype.php
+++ b/question/type/match/questiontype.php
@@ -483,177 +483,6 @@ function get_random_guess_score($question) {
return 1 / count($question->options->subquestions);
}
-/// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $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];
-
- //We'll need this later!!
- $oldid = backup_todb($mat_info['#']['ID']['0']['#']);
-
- //Now, build the question_match_SUB record structure
- $match_sub = new stdClass;
- $match_sub->question = $new_question_id;
- $match_sub->code = isset($mat_info['#']['CODE']['0']['#'])?backup_todb($mat_info['#']['CODE']['0']['#']):'';
- if (!$match_sub->code) {
- $match_sub->code = $oldid;
- }
- $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 "
";
- }
- }
- 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 = new stdClass;
- $match->question = $new_question_id;
- $match->subquestions = $subquestions_field;
-
- // Get the shuffleanswers option, if it is there.
- if (!empty($info['#']['MATCHOPTIONS']['0']['#']['SHUFFLEANSWERS'])) {
- $match->shuffleanswers = backup_todb($info['#']['MATCHOPTIONS']['0']['#']['SHUFFLEANSWERS']['0']['#']);
- } else {
- $match->shuffleanswers = 1;
- }
-
- //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 restore_map($old_question_id,$new_question_id,$info,$restore) {
- global $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];
-
- //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 states (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 "
";
- }
- }
- 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 restore_recode_answer($state, $restore) {
-
- //The answer is a comma separated list of hypen separated math_subs (for question and answer)
- $answer_field = "";
- $in_first = true;
- $tok = strtok($state->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)
- if (!$match_que = backup_getid($restore->backup_unique_code,"question_match_sub",$match_question_id)) {
- echo 'Could not recode question in question_match_sub '.$match_question_id.'
';
- } else {
- if ($in_first) {
- $in_first = false;
- } else {
- $answer_field .= ',';
- }
- $answer_field .= $match_que->new_id.'-'.$match_answer_id;
- }
- //check for next
- $tok = strtok(",");
- }
- return $answer_field;
- }
-
/**
* Decode links in question type specific tables.
* @return bool success or failure.
diff --git a/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php
new file mode 100644
index 0000000000000..dbfd5fe9e5e25
--- /dev/null
+++ b/question/type/multianswer/backup/moodle2/restore_qtype_multianswer_plugin.class.php
@@ -0,0 +1,148 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one multianswer qtype plugin
+ */
+class restore_qtype_multianswer_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // This qtype uses question_answers, add them
+ $this->add_question_question_answers($paths);
+
+ // Add own qtype stuff
+ $elename = 'multianswer';
+ $elepath = $this->get_pathfor('/multianswer'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/multianswer element
+ */
+ public function process_multianswer($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_multianswer too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ // Note: multianswer->sequence is a list of question->id values. We aren't
+ // recoding them here (because some questions can be missing yet). Instead
+ // we'll perform the recode in the {@link after_execute} method of the plugin
+ // that gets executed once all questions have been created
+ // Insert record
+ $newitemid = $DB->insert_record('question_multianswer', $data);
+ // Create mapping (need it for after_execute recode of sequence)
+ $this->set_mapping('question_multianswer', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+
+ /**
+ * This method is executed once the whole restore_structure_step
+ * this step is part of ({@link restore_create_categories_and_questions})
+ * has ended processing the whole xml structure. Its name is:
+ * "after_execute_" + connectionpoint ("question")
+ *
+ * For multianswer qtype we use it to restore the sequence column,
+ * containing one list of question ids
+ */
+ public function after_execute_question() {
+ global $DB;
+ // Now that all the questions have been restored, let's process
+ // the created question_multianswer sequences (list of question ids)
+ $rs = $DB->get_recordset_sql("SELECT qma.id, qma.sequence
+ FROM {question_multianswer} qma
+ JOIN {backup_ids_temp} bi ON bi.newitemid = qma.question
+ WHERE bi.backupid = ?
+ AND bi.itemname = 'question_created'", array($this->get_restoreid()));
+ foreach ($rs as $rec) {
+ $sequencearr = explode(',', $rec->sequence);
+ foreach ($sequencearr as $key => $question) {
+ $sequencearr[$key] = $this->get_mappingid('question', $question);
+ }
+ $sequence = implode(',', $sequencearr);
+ $DB->set_field('question_multianswer', 'sequence', $sequence, array('id' => $rec->id));
+ }
+ $rs->close();
+ }
+
+ /**
+ * Given one question_states record, return the answer
+ * recoded pointing to all the restored stuff for multianswer questions
+ *
+ * answer is one comma separated list of hypen separated pairs
+ * containing sequence (pointing to questions sequence in question_multianswer)
+ * and mixed answers. We'll delegate
+ * the recoding of answers to the proper qtype
+ */
+ public function recode_state_answer($state) {
+ global $DB;
+ $answer = $state->answer;
+ $resultarr = array();
+ // Get sequence of questions
+ $sequence = $DB->get_field('question_multianswer', 'sequence', array('question' => $state->question));
+ $sequencearr = explode(',', $sequence);
+ // Let's process each pair
+ foreach (explode(',', $answer) as $pair) {
+ $pairarr = explode('-', $pair);
+ $sequenceid = $pairarr[0];
+ $subanswer = $pairarr[1];
+ // Calculate the questionid based on sequenceid
+ // Note it is already one *new* questionid that doesn't need mapping
+ $questionid = $sequencearr[$sequenceid-1];
+ // Fetch qtype of the question (needed for delegation)
+ $questionqtype = $DB->get_field('question', 'qtype', array('id' => $questionid));
+ // Delegate subanswer recode to proper qtype, faking one question_states record
+ $substate = new stdClass();
+ $substate->question = $questionid;
+ $substate->answer = $subanswer;
+ $newanswer = $this->step->restore_recode_answer($substate, $questionqtype);
+ $resultarr[] = implode('-', array($sequenceid, $newanswer));
+ }
+ return implode(',', $resultarr);
+ }
+
+}
diff --git a/question/type/multianswer/questiontype.php b/question/type/multianswer/questiontype.php
index 62e2cb653fdc6..38beac0ac2f6f 100644
--- a/question/type/multianswer/questiontype.php
+++ b/question/type/multianswer/questiontype.php
@@ -673,173 +673,6 @@ function get_random_guess_score($question) {
return $totalfraction / count($question->options->questions);
}
-/// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = true;
-
- //Get the multianswers array
- $multianswers = $info['#']['MULTIANSWERS']['0']['#']['MULTIANSWER'];
- //Iterate over multianswers
- for($i = 0; $i < sizeof($multianswers); $i++) {
- $mul_info = $multianswers[$i];
-
- //We need this later
- $oldid = backup_todb($mul_info['#']['ID']['0']['#']);
-
- //Now, build the question_multianswer record structure
- $multianswer = new stdClass;
- $multianswer->question = $new_question_id;
- $multianswer->sequence = backup_todb($mul_info['#']['SEQUENCE']['0']['#']);
-
- //We have to recode the sequence field (a list of question ids)
- //Extracts question id from sequence
- $sequence_field = "";
- $in_first = true;
- $tok = strtok($multianswer->sequence,",");
- while ($tok) {
- //Get the answer from backup_ids
- $question = backup_getid($restore->backup_unique_code,"question",$tok);
- if ($question) {
- if ($in_first) {
- $sequence_field .= $question->new_id;
- $in_first = false;
- } else {
- $sequence_field .= ",".$question->new_id;
- }
- }
- //check for next
- $tok = strtok(",");
- }
- //We have the answers field recoded to its new ids
- $multianswer->sequence = $sequence_field;
- //The structure is equal to the db, so insert the question_multianswer
- $newid = $DB->insert_record("question_multianswer", $multianswer);
-
- //Save ids in backup_ids
- if ($newid) {
- backup_putid($restore->backup_unique_code,"question_multianswer",
- $oldid, $newid);
- }
-
- //Do some output
- if (($i+1) % 50 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 1000 == 0) {
- echo "
";
- }
- }
- backup_flush(300);
- }
- }
-
- return $status;
- }
-
- function restore_map($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = true;
-
- //Get the multianswers array
- $multianswers = $info['#']['MULTIANSWERS']['0']['#']['MULTIANSWER'];
- //Iterate over multianswers
- for($i = 0; $i < sizeof($multianswers); $i++) {
- $mul_info = $multianswers[$i];
-
- //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']['#']);
-
- //If we are in this method is because the question exists in DB, so its
- //multianswer must exist too.
- //Now, we are going to look for that multianswer in DB and to create the
- //mappings in backup_ids to use them later where restoring states (user level).
-
- //Get the multianswer from DB (by question and positionkey)
- $db_multianswer = $DB->get_record ("question_multianswer",array("question"=>$new_question_id,
- "positionkey"=>$multianswer->positionkey));
- //Do some output
- if (($i+1) % 50 == 0) {
- if (!defined('RESTORE_SILENTLY')) {
- echo ".";
- if (($i+1) % 1000 == 0) {
- echo "
";
- }
- }
- backup_flush(300);
- }
-
- //We have the database multianswer, so update backup_ids
- if ($db_multianswer) {
- //We have the newid, update backup_ids
- backup_putid($restore->backup_unique_code,"question_multianswer",$oldid,
- $db_multianswer->id);
- } else {
- $status = false;
- }
- }
-
- return $status;
- }
-
- function restore_recode_answer($state, $restore) {
- global $DB, $OUTPUT;
- //The answer is a comma separated list of hypen separated sequence number and answers. We may have to recode the answers
- $answer_field = "";
- $in_first = true;
- $tok = strtok($state->answer,",");
- while ($tok) {
- //Extract the multianswer_id and the answer
- $exploded = explode("-",$tok);
- $seqnum = $exploded[0];
- $answer = $exploded[1];
- // $sequence is an ordered array of the question ids.
- if (!$sequence = $DB->get_field('question_multianswer', 'sequence', array('question' => $state->question))) {
- print_error('missingoption', 'question', '', $state->question);
- }
- $sequence = explode(',', $sequence);
- // The id of the current question.
- $wrappedquestionid = $sequence[$seqnum-1];
- // now we can find the question
- if (!$wrappedquestion = $DB->get_record('question', array('id' => $wrappedquestionid))) {
- echo $OUTPUT->notification("Can't find the subquestion $wrappedquestionid that is used as part $seqnum in cloze question $state->question");
- }
- // For multichoice question we need to recode the answer
- if ($answer and $wrappedquestion->qtype == 'multichoice') {
- //The answer is an answer_id, look for it in backup_ids
- if (!$ans = backup_getid($restore->backup_unique_code,"question_answers",$answer)) {
- echo 'Could not recode cloze multichoice answer '.$answer.'
';
- }
- $answer = $ans->new_id;
- }
- //build the new answer field for each pair
- if ($in_first) {
- $answer_field .= $seqnum."-".$answer;
- $in_first = false;
- } else {
- $answer_field .= ",".$seqnum."-".$answer;
- }
- //check for next
- $tok = strtok(",");
- }
- return $answer_field;
- }
-
/**
* Runs all the code required to set up and save an essay question for testing purposes.
* Alternate DB table prefix may be used to facilitate data deletion.
diff --git a/question/type/multichoice/backup/moodle2/backup_qtype_multichoice_plugin.class.php b/question/type/multichoice/backup/moodle2/backup_qtype_multichoice_plugin.class.php
index d9dd3e123bb74..0134a1ca42210 100644
--- a/question/type/multichoice/backup/moodle2/backup_qtype_multichoice_plugin.class.php
+++ b/question/type/multichoice/backup/moodle2/backup_qtype_multichoice_plugin.class.php
@@ -62,4 +62,24 @@ protected function define_question_plugin_structure() {
return $plugin;
}
+
+ /**
+ * 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() {
+ // TODO: Discuss. Commented below are the "in theory" correct
+ // mappings for those fileareas. Instead we are using question for
+ // them, that will cause problems in the future if we want to change
+ // any of them to be 1..n (i.e. we should be always pointing to own id)
+ return array(
+ //'correctfeedback' => 'question_multichoice',
+ //'partiallycorrectfeedback' => 'question_multichoice',
+ //'incorrectfeedback' => 'question_multichoice');
+ 'correctfeedback' => 'question_created',
+ 'partiallycorrectfeedback' => 'question_created',
+ 'incorrectfeedback' => 'question_created');
+ }
}
diff --git a/question/type/multichoice/backup/moodle2/restore_qtype_multichoice_plugin.class.php b/question/type/multichoice/backup/moodle2/restore_qtype_multichoice_plugin.class.php
new file mode 100644
index 0000000000000..2950b49a74f9c
--- /dev/null
+++ b/question/type/multichoice/backup/moodle2/restore_qtype_multichoice_plugin.class.php
@@ -0,0 +1,124 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one multichoice qtype plugin
+ */
+class restore_qtype_multichoice_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // This qtype uses question_answers, add them
+ $this->add_question_question_answers($paths);
+
+ // Add own qtype stuff
+ $elename = 'multichoice';
+ $elepath = $this->get_pathfor('/multichoice'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/multichoice element
+ */
+ public function process_multichoice($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_multichoice too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ // Map sequence of question_answer ids
+ $answersarr = explode(',', $data->answers);
+ foreach ($answersarr as $key => $answer) {
+ $answersarr[$key] = $this->get_mappingid('question_answer', $answer);
+ }
+ $data->answers = implode(',', $answersarr);
+ // Insert record
+ $newitemid = $DB->insert_record('question_multichoice', $data);
+ // Create mapping (not needed, no files nor childs nor states here)
+ //$this->set_mapping('question_multichoice', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+
+ /**
+ * Given one question_states record, return the answer
+ * recoded pointing to all the restored stuff for multichoice questions
+ *
+ * answer are two (hypen speparated) lists of comma separated question_answers
+ * the first to specify the order of the answers and the second to specify the
+ * responses. Note the order list (the first one) can be optional
+ */
+ public function recode_state_answer($state) {
+ $answer = $state->answer;
+ $orderarr = array();
+ $responsesarr = array();
+ $lists = explode(':', $answer);
+ // if only 1 list, answer is missing the order list, adjust
+ if (count($lists) == 1) {
+ $lists[1] = $lists[0]; // here we have the responses
+ $lists[0] = ''; // here we have the order
+ }
+ // Map order
+ foreach (explode(',', $lists[0]) as $id) {
+ if ($newid = $this->get_mappingid('question_answer', $id)) {
+ $orderarr[] = $newid;
+ }
+ }
+ // Map responses
+ foreach (explode(',', $lists[1]) as $id) {
+ if ($newid = $this->get_mappingid('question_answer', $id)) {
+ $responsesarr[] = $newid;
+ }
+ }
+ // Build the final answer, if not order, only responses
+ $result = '';
+ if (empty($orderarr)) {
+ $result = implode(',', $responsesarr);
+ } else {
+ $result = implode(',', $orderarr) . ':' . implode(',', $responsesarr);
+ }
+ return $result;
+ }
+}
diff --git a/question/type/multichoice/questiontype.php b/question/type/multichoice/questiontype.php
index 772b37ace0c1b..49c669c272011 100644
--- a/question/type/multichoice/questiontype.php
+++ b/question/type/multichoice/questiontype.php
@@ -420,134 +420,6 @@ function get_random_guess_score($question) {
return $totalfraction / count($question->options->answers);
}
- /// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = true;
-
- //Get the multichoices array
- $multichoices = $info['#']['MULTICHOICE'];
-
- //Iterate over multichoices
- for($i = 0; $i < sizeof($multichoices); $i++) {
- $mul_info = $multichoices[$i];
-
- //Now, build the question_multichoice record structure
- $multichoice = new stdClass;
- $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']['#']);
- $multichoice->shuffleanswers = isset($mul_info['#']['SHUFFLEANSWERS']['0']['#'])?backup_todb($mul_info['#']['SHUFFLEANSWERS']['0']['#']):'';
- if (array_key_exists("CORRECTFEEDBACK", $mul_info['#'])) {
- $multichoice->correctfeedback = backup_todb($mul_info['#']['CORRECTFEEDBACK']['0']['#']);
- } else {
- $multichoice->correctfeedback = '';
- }
- if (array_key_exists("PARTIALLYCORRECTFEEDBACK", $mul_info['#'])) {
- $multichoice->partiallycorrectfeedback = backup_todb($mul_info['#']['PARTIALLYCORRECTFEEDBACK']['0']['#']);
- } else {
- $multichoice->partiallycorrectfeedback = '';
- }
- if (array_key_exists("INCORRECTFEEDBACK", $mul_info['#'])) {
- $multichoice->incorrectfeedback = backup_todb($mul_info['#']['INCORRECTFEEDBACK']['0']['#']);
- } else {
- $multichoice->incorrectfeedback = '';
- }
- if (array_key_exists("ANSWERNUMBERING", $mul_info['#'])) {
- $multichoice->answernumbering = backup_todb($mul_info['#']['ANSWERNUMBERING']['0']['#']);
- } else {
- $multichoice->answernumbering = 'abc';
- }
-
- //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
- $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 "
";
- }
- }
- backup_flush(300);
- }
-
- if (!$newid) {
- $status = false;
- }
- }
-
- return $status;
- }
-
- function restore_recode_answer($state, $restore) {
- $pos = strpos($state->answer, ':');
- $order = array();
- $responses = array();
- if (false === $pos) { // No order of answers is given, so use the default
- if ($state->answer) {
- $responses = explode(',', $state->answer);
- }
- } else {
- $order = explode(',', substr($state->answer, 0, $pos));
- if ($responsestring = substr($state->answer, $pos + 1)) {
- $responses = explode(',', $responsestring);
- }
- }
- if ($order) {
- foreach ($order as $key => $oldansid) {
- $answer = backup_getid($restore->backup_unique_code,"question_answers",$oldansid);
- if ($answer) {
- $order[$key] = $answer->new_id;
- } else {
- echo 'Could not recode multichoice answer id '.$oldansid.' for state '.$state->oldid.'
';
- }
- }
- }
- if ($responses) {
- foreach ($responses as $key => $oldansid) {
- $answer = backup_getid($restore->backup_unique_code,"question_answers",$oldansid);
- if ($answer) {
- $responses[$key] = $answer->new_id;
- } else {
- echo 'Could not recode multichoice response answer id '.$oldansid.' for state '.$state->oldid.'
';
- }
- }
- }
- return implode(',', $order).':'.implode(',', $responses);
- }
-
/**
* Decode links in question type specific tables.
* @return bool success or failure.
diff --git a/question/type/numerical/backup/moodle2/backup_qtype_numerical_plugin.class.php b/question/type/numerical/backup/moodle2/backup_qtype_numerical_plugin.class.php
index 06fff61b72987..c2f3426bcc8b3 100644
--- a/question/type/numerical/backup/moodle2/backup_qtype_numerical_plugin.class.php
+++ b/question/type/numerical/backup/moodle2/backup_qtype_numerical_plugin.class.php
@@ -68,4 +68,20 @@ protected function define_question_plugin_structure() {
return $plugin;
}
+
+ /**
+ * 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() {
+ // TODO: Discuss. Commented below are the "in theory" correct
+ // mappings for those fileareas. Instead we are using question for
+ // them, that will cause problems in the future if we want to change
+ // any of them to be 1..n (i.e. we should be always pointing to own id)
+ return array(
+ //'instruction' => 'question_numerical_option');
+ 'instruction' => 'question_created');
+ }
}
diff --git a/question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php b/question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php
new file mode 100644
index 0000000000000..fdda5f5c00289
--- /dev/null
+++ b/question/type/numerical/backup/moodle2/restore_qtype_numerical_plugin.class.php
@@ -0,0 +1,83 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one numerical qtype plugin
+ */
+class restore_qtype_numerical_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // This qtype uses question_answers, add them
+ $this->add_question_question_answers($paths);
+
+ // This qtype uses question_numerical_options and question_numerical_units, add them
+ $this->add_question_numerical_options($paths);
+ $this->add_question_numerical_units($paths);
+
+ // Add own qtype stuff
+ $elename = 'numerical';
+ $elepath = $this->get_pathfor('/numerical_records/numerical_record'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/numerical element
+ */
+ public function process_numerical($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 too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ $data->answer = $this->get_mappingid('question_answer', $data->answer);
+ // Insert record
+ $newitemid = $DB->insert_record('question_numerical', $data);
+ // Create mapping (not needed, no files nor childs nor states here)
+ //$this->set_mapping('question_numerical', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+}
diff --git a/question/type/numerical/questiontype.php b/question/type/numerical/questiontype.php
index 2dc1413687020..02ec82c41a59b 100644
--- a/question/type/numerical/questiontype.php
+++ b/question/type/numerical/questiontype.php
@@ -1204,69 +1204,6 @@ function valid_unit($rawresponse, $units) {
return false;
}
- /// RESTORE FUNCTIONS /////////////////
-
- /**
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = true;
-
- //Get the numerical array
- if (isset($info['#']['NUMERICAL'])) {
- $numericals = $info['#']['NUMERICAL'];
- } else {
- $numericals = array();
- }
-
- //Iterate over numericals
- for($i = 0; $i < sizeof($numericals); $i++) {
- $num_info = $numericals[$i];
-
- //Now, build the question_numerical record structure
- $numerical = new stdClass;
- $numerical->question = $new_question_id;
- $numerical->answer = backup_todb($num_info['#']['ANSWER']['0']['#']);
- $numerical->tolerance = backup_todb($num_info['#']['TOLERANCE']['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;
- }
-
- //The structure is equal to the db, so insert the question_numerical
- $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 "
";
- }
- }
- backup_flush(300);
- }
-
- //Now restore numerical_units
- $status = question_restore_numerical_units ($old_question_id,$new_question_id,$num_info,$restore);
-
- //Now restore numerical_options
- $status = question_restore_numerical_options ($old_question_id,$new_question_id,$num_info,$restore);
-
- if (!$newid) {
- $status = false;
- }
- }
-
- return $status;
- }
-
/**
* Runs all the code required to set up and save an essay question for testing purposes.
* Alternate DB table prefix may be used to facilitate data deletion.
diff --git a/question/type/questiontype.php b/question/type/questiontype.php
index aac3e196ad691..9281a7fa71071 100644
--- a/question/type/questiontype.php
+++ b/question/type/questiontype.php
@@ -1632,46 +1632,6 @@ function error_link($cmoptions) {
}
}
-/// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = true;
- $extraquestionfields = $this->extra_question_fields();
-
- if (is_array($extraquestionfields)) {
- $questionextensiontable = array_shift($extraquestionfields);
- $tagname = strtoupper($this->name());
- $recordinfo = $info['#'][$tagname][0];
-
- $record = new stdClass;
- $qidcolname = $this->questionid_column_name();
- $record->$qidcolname = $new_question_id;
- foreach ($extraquestionfields as $field) {
- $record->$field = backup_todb($recordinfo['#'][strtoupper($field)]['0']['#']);
- }
- $DB->insert_record($questionextensiontable, $record);
- }
- //TODO restore extra data in answers
- return $status;
- }
-
- function restore_map($old_question_id,$new_question_id,$info,$restore) {
- // There is nothing to decode
- return true;
- }
-
- function restore_recode_answer($state, $restore) {
- // There is nothing to decode
- return $state->answer;
- }
-
/// IMPORT/EXPORT FUNCTIONS /////////////////
/*
diff --git a/question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php b/question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
new file mode 100644
index 0000000000000..aaaec820389c3
--- /dev/null
+++ b/question/type/random/backup/moodle2/restore_qtype_random_plugin.class.php
@@ -0,0 +1,69 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one random qtype plugin
+ */
+class restore_qtype_random_plugin extends restore_qtype_plugin {
+
+ /**
+ * Given one question_states record, return the answer
+ * recoded pointing to all the restored stuff for random questions
+ *
+ * answer format is randomxx-yy, with xx being question->id and
+ * yy the actual response to the question. We'll delegate the recode
+ * to the corresponding qtype
+ *
+ * also, some old states can contain, simply, one question->id,
+ * support them, just in case
+ */
+ public function recode_state_answer($state) {
+ global $DB;
+
+ $answer = $state->answer;
+ $result = '';
+ // randomxx-yy answer format
+ if (preg_match('~^random([0-9]+)-(.*)$~', $answer, $matches)) {
+ $questionid = $matches[1];
+ $subanswer = $matches[2];
+ $newquestionid = $this->get_mappingid('question', $questionid);
+ $questionqtype = $DB->get_field('question', 'qtype', array('id' => $newquestionid));
+ // Delegate subanswer recode to proper qtype, faking one question_states record
+ $substate = new stdClass();
+ $substate->question = $newquestionid;
+ $substate->answer = $subanswer;
+ $newanswer = $this->step->restore_recode_answer($substate, $questionqtype);
+ $result = 'random' . $newquestionid . '-' . $newanswer;
+
+ // simple question id format
+ } else {
+ $newquestionid = $this->get_mappingid('question', $answer);
+ $result = $newquestionid;
+ }
+ return $result;
+ }
+}
diff --git a/question/type/random/questiontype.php b/question/type/random/questiontype.php
index fd85751c30e8f..84bcba7734720 100644
--- a/question/type/random/questiontype.php
+++ b/question/type/random/questiontype.php
@@ -363,49 +363,6 @@ function compare_responses(&$question, $state, $teststate) {
->compare_responses($wrappedquestion, $state, $teststate);
}
- function restore_recode_answer($state, $restore) {
- // The answer looks like 'randomXX-ANSWER', where XX is
- // the id of the used question and ANSWER the actual
- // response to that question.
- // However, there may still be old-style states around,
- // which store the id of the wrapped question in the
- // state of the random question and store the response
- // in a separate state for the wrapped question
-
- global $QTYPES, $DB;
- $answer_field = "";
-
- if (preg_match('~^random([0-9]+)-(.*)$~', $state->answer, $answerregs)) {
- // Recode the question id in $answerregs[1]
- // Get the question from backup_ids
- if(!$wrapped = backup_getid($restore->backup_unique_code,"question",$answerregs[1])) {
- echo 'Could not recode question in random-'.$answerregs[1].'
';
- return($answer_field);
- }
- // Get the question type for recursion
- if (!$wrappedquestion->qtype = $DB->get_field('question', 'qtype', array('id' => $wrapped->new_id))) {
- echo 'Could not get qtype while recoding question random-'.$answerregs[1].'
';
- return($answer_field);
- }
- $newstate = $state;
- $newstate->question = $wrapped->new_id;
- $newstate->answer = $answerregs[2];
- $answer_field = 'random'.$wrapped->new_id.'-';
-
- // Recode the answer field in $answerregs[2] depending on
- // the qtype of question with id $answerregs[1]
- $answer_field .= $QTYPES[$wrappedquestion->qtype]->restore_recode_answer($newstate, $restore);
- } else {
- // Handle old-style states
- $answer_link = backup_getid($restore->backup_unique_code,"question",$state->answer);
- if ($answer_link) {
- $answer_field = $answer_link->new_id;
- }
- }
-
- return $answer_field;
- }
-
/**
* For random question type return empty string which means won't calculate.
* @param object $question
diff --git a/question/type/randomsamatch/backup/moodle2/restore_qtype_randomsamatch_plugin.class.php b/question/type/randomsamatch/backup/moodle2/restore_qtype_randomsamatch_plugin.class.php
new file mode 100644
index 0000000000000..9e4be36279d85
--- /dev/null
+++ b/question/type/randomsamatch/backup/moodle2/restore_qtype_randomsamatch_plugin.class.php
@@ -0,0 +1,96 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one randomsamatch qtype plugin
+ */
+class restore_qtype_randomsamatch_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // Add own qtype stuff
+ $elename = 'randomsamatch';
+ $elepath = $this->get_pathfor('/randomsamatch'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/randomsamatch element
+ */
+ public function process_randomsamatch($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_randomsamatch too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ // Insert record
+ $newitemid = $DB->insert_record('question_randomsamatch', $data);
+ // Create mapping
+ $this->set_mapping('question_randomsamatch', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+
+ /**
+ * Given one question_states record, return the answer
+ * recoded pointing to all the restored stuff for randomsamatch questions
+ *
+ * answer is one comma separated list of hypen separated pairs
+ * containing question->id and question_answers->id
+ */
+ public function recode_state_answer($state) {
+ $answer = $state->answer;
+ $resultarr = array();
+ foreach (explode(',', $answer) as $pair) {
+ $pairarr = explode('-', $pair);
+ $questionid = $pairarr[0];
+ $answerid = $pairarr[1];
+ $newquestionid = $questionid ? $this->get_mappingid('question', $questionid) : 0;
+ $newanswerid = $answerid ? $this->get_mappingid('question_answer', $answerid) : 0;
+ $resultarr[] = implode('-', array($newquestionid, $newanswerid));
+ }
+ return implode(',', $resultarr);
+ }
+}
diff --git a/question/type/randomsamatch/questiontype.php b/question/type/randomsamatch/questiontype.php
index 177fde4cf7c25..230d799fa1a3a 100644
--- a/question/type/randomsamatch/questiontype.php
+++ b/question/type/randomsamatch/questiontype.php
@@ -339,88 +339,6 @@ function get_possible_responses(&$question) {
function get_random_guess_score($question) {
return 1/$question->options->choose;
}
-
-/// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = true;
-
- //Get the randomsamatchs array
- $randomsamatchs = $info['#']['RANDOMSAMATCH'];
-
- //Iterate over randomsamatchs
- for($i = 0; $i < sizeof($randomsamatchs); $i++) {
- $ran_info = $randomsamatchs[$i];
-
- //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 "
";
- }
- }
- backup_flush(300);
- }
-
- if (!$newid) {
- $status = false;
- }
- }
-
- return $status;
- }
-
- function restore_recode_answer($state, $restore) {
-
- //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($state->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
- if (!$que = backup_getid($restore->backup_unique_code,"question",$question_id)) {
- echo 'Could not recode randomsamatch question '.$question_id.'
';
- }
-
- if ($answer_id == 0) { // no response yet
- $ans->new_id = 0;
- } else {
- //Get the answer from backup_ids
- if (!$ans = backup_getid($restore->backup_unique_code,"question_answers",$answer_id)) {
- echo 'Could not recode randomsamatch answer '.$answer_id.'
';
- }
- }
- 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(",");
- }
- return $answer_field;
- }
-
}
//// END OF CLASS ////
diff --git a/question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php b/question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php
new file mode 100644
index 0000000000000..03202c5f7d0a2
--- /dev/null
+++ b/question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php
@@ -0,0 +1,84 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one shortanswer qtype plugin
+ */
+class restore_qtype_shortanswer_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // This qtype uses question_answers, add them
+ $this->add_question_question_answers($paths);
+
+ // Add own qtype stuff
+ $elename = 'shortanswer';
+ $elepath = $this->get_pathfor('/shortanswer'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/shortanswer element
+ */
+ public function process_shortanswer($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_shortanswer too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ // Map sequence of question_answer ids
+ $answersarr = explode(',', $data->answers);
+ foreach ($answersarr as $key => $answer) {
+ $answersarr[$key] = $this->get_mappingid('question_answer', $answer);
+ }
+ $data->answers = implode(',', $answersarr);
+ // Insert record
+ $newitemid = $DB->insert_record('question_shortanswer', $data);
+ // Create mapping
+ $this->set_mapping('question_shortanswer', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+}
diff --git a/question/type/shortanswer/questiontype.php b/question/type/shortanswer/questiontype.php
index 4404bd89a1394..19ee8d277fd05 100644
--- a/question/type/shortanswer/questiontype.php
+++ b/question/type/shortanswer/questiontype.php
@@ -280,52 +280,6 @@ function get_random_guess_score($question) {
return 0;
}
-/// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = parent::restore($old_question_id, $new_question_id, $info, $restore);
-
- if ($status) {
- $extraquestionfields = $this->extra_question_fields();
- $questionextensiontable = array_shift($extraquestionfields);
-
- //We have to recode the answers field (a list of answers id)
- $questionextradata = $DB->get_record($questionextensiontable, array($this->questionid_column_name() => $new_question_id));
- if (isset($questionextradata->answers)) {
- $answers_field = "";
- $in_first = true;
- $tok = strtok($questionextradata->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
- $questionextradata->answers = $answers_field;
- // Update the question
- $DB->update_record($questionextensiontable, $questionextradata);
- }
- }
-
- return $status;
- }
-
/**
* Prints the score obtained and maximum score available plus any penalty
* information
diff --git a/question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php b/question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php
new file mode 100644
index 0000000000000..7db3fcb6b2ee1
--- /dev/null
+++ b/question/type/truefalse/backup/moodle2/restore_qtype_truefalse_plugin.class.php
@@ -0,0 +1,95 @@
+.
+
+/**
+ * @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();
+
+/**
+ * restore plugin class that provides the necessary information
+ * needed to restore one truefalse qtype plugin
+ */
+class restore_qtype_truefalse_plugin extends restore_qtype_plugin {
+
+ /**
+ * Returns the paths to be handled by the plugin at question level
+ */
+ protected function define_question_plugin_structure() {
+
+ $paths = array();
+
+ // This qtype uses question_answers, add them
+ $this->add_question_question_answers($paths);
+
+ // Add own qtype stuff
+ $elename = 'truefalse';
+ $elepath = $this->get_pathfor('/truefalse'); // we used get_recommended_name() so this works
+ $paths[] = new restore_path_element($elename, $elepath);
+
+
+ return $paths; // And we return the interesting paths
+ }
+
+ /**
+ * Process the qtype/truefalse element
+ */
+ public function process_truefalse($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_truefalse too
+ if ($questioncreated) {
+ // Adjust some columns
+ $data->question = $newquestionid;
+ $data->trueanswer = $this->get_mappingid('question_answer', $data->trueanswer);
+ $data->falseanswer = $this->get_mappingid('question_answer', $data->falseanswer);
+ // Insert record
+ $newitemid = $DB->insert_record('question_truefalse', $data);
+ // Create mapping
+ $this->set_mapping('question_truefalse', $oldid, $newitemid);
+ } else {
+ // Nothing to remap if the question already existed
+ }
+ }
+
+ /**
+ * Given one question_states record, return the answer
+ * recoded pointing to all the restored stuff for truefalse questions
+ *
+ * if not empty, answer is one question_answers->id
+ */
+ public function recode_state_answer($state) {
+ $answer = $state->answer;
+ $result = '';
+ if ($answer) {
+ $result = $this->get_mappingid('question_answer', $answer);
+ }
+ return $result;
+ }
+}
diff --git a/question/type/truefalse/questiontype.php b/question/type/truefalse/questiontype.php
index c2fb46984fa0e..a0935cd5de01c 100644
--- a/question/type/truefalse/questiontype.php
+++ b/question/type/truefalse/questiontype.php
@@ -292,81 +292,6 @@ function get_random_guess_score($question) {
return 0.5;
}
-/// RESTORE FUNCTIONS /////////////////
-
- /*
- * Restores the data in the question
- *
- * This is used in question/restorelib.php
- */
- function restore($old_question_id,$new_question_id,$info,$restore) {
- global $DB;
-
- $status = true;
-
- //Get the truefalse array
- if (array_key_exists('TRUEFALSE', $info['#'])) {
- $truefalses = $info['#']['TRUEFALSE'];
- } else {
- $truefalses = array();
- }
-
- //Iterate over truefalse
- for($i = 0; $i < sizeof($truefalses); $i++) {
- $tru_info = $truefalses[$i];
-
- //Now, build the question_truefalse record structure
- $truefalse = new stdClass;
- $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 "
";
- }
- }
- backup_flush(300);
- }
-
- if (!$newid) {
- $status = false;
- }
- }
-
- return $status;
- }
-
- function restore_recode_answer($state, $restore) {
- //answer may be empty
- if ($state->answer) {
- $answer = backup_getid($restore->backup_unique_code,"question_answers",$state->answer);
- if ($answer) {
- return $answer->new_id;
- } else {
- echo 'Could not recode truefalse answer id '.$state->answer.' for state '.$state->oldid.'
';
- }
- }
- }
-
/**
* Runs all the code required to set up and save an essay question for testing purposes.
* Alternate DB table prefix may be used to facilitate data deletion.