From 767cb7f03abc2336dcbaf1611e0e9e5781132f96 Mon Sep 17 00:00:00 2001 From: Eloy Lafuente Date: Tue, 21 Sep 2010 01:28:30 +0000 Subject: [PATCH] MDL-22151 & MDL-22138 - Quiz activity backup & questions banks too! CV S: ---------------------------------------------------------------------- --- backup/backup.class.php | 4 +- backup/moodle2/backup_course_task.class.php | 3 + backup/moodle2/backup_custom_fields.php | 6 + backup/moodle2/backup_final_task.class.php | 10 + backup/moodle2/backup_plan_builder.class.php | 2 + backup/moodle2/backup_plugin.class.php | 76 +++++++ backup/moodle2/backup_qtype_plugin.class.php | 158 +++++++++++++++ backup/moodle2/backup_stepslib.php | 190 +++++++++++++++++- backup/moodle2/restore_stepslib.php | 18 +- .../dbops/backup_question_dbops.class.php | 80 ++++++++ .../dbops/backup_structure_dbops.class.php | 12 +- backup/util/helper/backup_helper.class.php | 2 +- backup/util/includes/backup_includes.php | 1 + .../util/plan/backup_structure_step.class.php | 37 ++++ mod/data/field/textarea/field.class.php | 2 +- .../backup_quiz_activity_task.class.php | 86 ++++++++ .../backup/moodle2/backup_quiz_stepslib.php | 140 +++++++++++++ mod/quiz/lib.php | 1 + 18 files changed, 804 insertions(+), 24 deletions(-) create mode 100644 backup/moodle2/backup_plugin.class.php create mode 100644 backup/moodle2/backup_qtype_plugin.class.php create mode 100644 backup/util/dbops/backup_question_dbops.class.php create mode 100644 mod/quiz/backup/moodle2/backup_quiz_activity_task.class.php create mode 100644 mod/quiz/backup/moodle2/backup_quiz_stepslib.php diff --git a/backup/backup.class.php b/backup/backup.class.php index ba7bce84b49e1..75efd31de5bce 100644 --- a/backup/backup.class.php +++ b/backup/backup.class.php @@ -106,8 +106,8 @@ abstract class backup implements checksumable { const OPERATION_RESTORE ='restore';// We are performing one restore // Version (to keep CFG->backup_version (and release) updated automatically) - const VERSION = 2010072300; - const RELEASE = '2.0 Preview 5'; + const VERSION = 2010092100; + const RELEASE = '2.0 RC1'; } /* diff --git a/backup/moodle2/backup_course_task.class.php b/backup/moodle2/backup_course_task.class.php index da2f21abf18b6..1076dfeed8b09 100644 --- a/backup/moodle2/backup_course_task.class.php +++ b/backup/moodle2/backup_course_task.class.php @@ -78,6 +78,9 @@ public function build() { // Annotate the groups used in already annotated groupings $this->add_step(new backup_annotate_groups_from_groupings('annotate_groups')); + // Annotate the question_categories belonging to the course context + $this->add_step(new backup_calculate_question_categories('course_question_categories')); + // Generate the roles file (optionally role assignments and always role overrides) $this->add_step(new backup_roles_structure_step('course_roles', 'roles.xml')); diff --git a/backup/moodle2/backup_custom_fields.php b/backup/moodle2/backup_custom_fields.php index af7e07dbe9203..d899a65ef32ca 100644 --- a/backup/moodle2/backup_custom_fields.php +++ b/backup/moodle2/backup_custom_fields.php @@ -102,6 +102,12 @@ public function fill_values($values) { } } +/** + * Implementation of backup_optigroup_element to be used by plugins stuff. + * Split just for better separation and future specialisation + */ +class backup_plugin_element extends backup_optigroup_element { } + /** * Implementation of backup_optigroup_element to be used by subplugins stuff. * Split just for better separation and future specialisation diff --git a/backup/moodle2/backup_final_task.class.php b/backup/moodle2/backup_final_task.class.php index 320442b57373c..93a07c01663b7 100644 --- a/backup/moodle2/backup_final_task.class.php +++ b/backup/moodle2/backup_final_task.class.php @@ -48,6 +48,16 @@ public function build() { // including membership based on setting $this->add_step(new backup_groups_structure_step('groups', 'groups.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 + // complete question banks we don't need to restrict at all and can be + // 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_plan_builder.class.php b/backup/moodle2/backup_plan_builder.class.php index 9066338232036..a1cb25cf834c0 100644 --- a/backup/moodle2/backup_plan_builder.class.php +++ b/backup/moodle2/backup_plan_builder.class.php @@ -32,6 +32,8 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_block_task.class.php'); require_once($CFG->dirroot . '/backup/moodle2/backup_default_block_task.class.php'); require_once($CFG->dirroot . '/backup/moodle2/backup_xml_transformer.class.php'); +require_once($CFG->dirroot . '/backup/moodle2/backup_plugin.class.php'); +require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_plugin.class.php'); require_once($CFG->dirroot . '/backup/moodle2/backup_subplugin.class.php'); require_once($CFG->dirroot . '/backup/moodle2/backup_settingslib.php'); require_once($CFG->dirroot . '/backup/moodle2/backup_stepslib.php'); diff --git a/backup/moodle2/backup_plugin.class.php b/backup/moodle2/backup_plugin.class.php new file mode 100644 index 0000000000000..bd1049819e80c --- /dev/null +++ b/backup/moodle2/backup_plugin.class.php @@ -0,0 +1,76 @@ +. + +/** + * @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 backups + * + * TODO: Finish phpdocs + */ +abstract class backup_plugin { + + protected $plugintype; + protected $pluginname; + protected $connectionpoint; + protected $optigroup; // Optigroup, parent of all optigroup elements + + public function __construct($plugintype, $pluginname, $optigroup) { + $this->plugintype = $plugintype; + $this->pluginname = $pluginname; + $this->optigroup = $optigroup; + $this->connectionpoint = ''; + } + + public function define_plugin_structure($connectionpoint) { + + $this->connectionpoint = $connectionpoint; + + $methodname = 'define_' . $connectionpoint . '_plugin_structure'; + + if (method_exists($this, $methodname)) { + $this->$methodname(); + } + } + + /** + * Factory method that will return one backup_plugin_element (backup_optigroup_element) + * with its name automatically calculated, based one the plugin being handled (type, name) + */ + protected function get_plugin_element($final_elements = null, $conditionparam = null, $conditionvalue = null) { + // Something exclusive for this backup_plugin_element (backup_optigroup_element) + // because it hasn't XML representation + $name = 'optigroup_' . $this->plugintype . '_' . $this->pluginname . '_' . $this->connectionpoint; + $optigroup_element = new backup_plugin_element($name, $final_elements, $conditionparam, $conditionvalue); + $this->optigroup->add_child($optigroup_element); // Add optigroup_element to stay connected since beginning + return $optigroup_element; + } + + /** + * Simple helper function that suggests one name for the main nested element in plugins + * It's not mandatory to use it but recommended ;-) + */ + protected function get_recommended_name() { + return 'plugin_' . $this->plugintype . '_' . $this->pluginname . '_' . $this->connectionpoint; + } + +} diff --git a/backup/moodle2/backup_qtype_plugin.class.php b/backup/moodle2/backup_qtype_plugin.class.php new file mode 100644 index 0000000000000..e44cf397cb7ae --- /dev/null +++ b/backup/moodle2/backup_qtype_plugin.class.php @@ -0,0 +1,158 @@ +. + +/** + * @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 backup_plugin in order to implement some + * helper methods related with the questions (qtype plugin) + * + * TODO: Finish phpdocs + */ +abstract class backup_qtype_plugin extends backup_plugin { + + /** + * Attach to $element (usually questions) the needed backup structures + * for question_answers for a given question + * Used by various qtypes (calculated, essay, multianswer, + * multichoice, numerical, shortanswer, truefalse) + */ + protected function add_question_question_answers($element) { + // Check $element is one nested_backup_element + if (! $element instanceof backup_nested_element) { + throw new backup_step_exception('question_answers_bad_parent_element', $element); + } + + // Define the elements + $answers = new backup_nested_element('answers'); + $answer = new backup_nested_element('answer', array('id'), array( + 'answertext', 'answerformat', 'fraction', 'feedback', + 'feedbackformat')); + + // Build the tree + $element->add_child($answers); + $answers->add_child($answer); + + // Set the sources + $answer->set_source_table('question_answers', array('question' => backup::VAR_PARENTID)); + + // Aliases + $answer->set_source_alias('answer', 'answertext'); + + // don't need to annotate ids nor files + } + + /** + * Attach to $element (usually questions) the needed backup structures + * for question_numerical_units for a given question + * Used both by calculated and numerical qtypes + */ + protected function add_question_numerical_units($element) { + // Check $element is one nested_backup_element + if (! $element instanceof backup_nested_element) { + throw new backup_step_exception('question_numerical_units_bad_parent_element', $element); + } + + // Define the elements + $units = new backup_nested_element('numerical_units'); + $unit = new backup_nested_element('numerical_unit', array('id'), array( + 'multiplier', 'unit')); + + // Build the tree + $element->add_child($units); + $units->add_child($unit); + + // Set the sources + $unit->set_source_table('question_numerical_units', array('question' => backup::VAR_PARENTID)); + + // don't need to annotate ids nor files + } + + /** + * Attach to $element (usually questions) the needed backup structures + * for question_numerical_options for a given question + * Used both by calculated and numerical qtypes + */ + protected function add_question_numerical_options($element) { + // Check $element is one nested_backup_element + if (! $element instanceof backup_nested_element) { + throw new backup_step_exception('question_numerical_options_bad_parent_element', $element); + } + + // Define the elements + $options = new backup_nested_element('numerical_options'); + $option = new backup_nested_element('numerical_option', array('id'), array( + 'instructions', 'instructionsformat', 'showunits', 'unitsleft', + 'unitgradingtype', 'unitpenalty')); + + // Build the tree + $element->add_child($options); + $options->add_child($option); + + // Set the sources + $option->set_source_table('question_numerical_options', array('question' => backup::VAR_PARENTID)); + + // don't need to annotate ids nor files + } + + /** + * Attach to $element (usually questions) the needed backup structures + * for question_datasets for a given question + * Used by calculated qtypes + */ + protected function add_question_datasets($element) { + // Check $element is one nested_backup_element + if (! $element instanceof backup_nested_element) { + throw new backup_step_exception('question_datasets_bad_parent_element', $element); + } + + // Define the elements + $definitions = new backup_nested_element('dataset_definitions'); + $definition = new backup_nested_element('dataset_definition', array('id'), array( + 'category', 'name', 'type', 'options', + 'itemcount')); + + $items = new backup_nested_element('dataset_items'); + $item = new backup_nested_element('dataset_item', array('id'), array( + 'number', 'value')); + + // Build the tree + $element->add_child($definitions); + $definitions->add_child($definition); + + $definition->add_child($items); + $items->add_child($item); + + // Set the sources + $definition->set_source_sql('SELECT * + FROM {question_dataset_definitions} qdd + JOIN {question_datasets} qd ON qd.datasetdefinition = qdd.id + WHERE qd.question = ?', array(backup::VAR_PARENTID)); + + $item->set_source_table('question_dataset_items', array('definition' => backup::VAR_PARENTID)); + + // Aliases + $item->set_source_alias('itemnumber', 'number'); + + // don't need to annotate ids nor files + } +} diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index e46634f815590..c8536dcbafc7f 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -148,6 +148,109 @@ 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 + */ +abstract class backup_questions_activity_structure_step extends backup_activity_structure_step { + + /** + * Attach to $element (usually attempts) the needed backup structures + * for question_states for a given question_attempt + */ + protected function add_question_attempts_states($element, $questionattemptname) { + // Check $element is one nested_backup_element + if (! $element instanceof backup_nested_element) { + throw new backup_step_exception('question_states_bad_parent_element', $element); + } + // Check that the $questionattemptname is final element in $element + if (! $element->get_final_element($questionattemptname)) { + throw new backup_step_exception('question_states_bad_question_attempt_element', $questionattemptname); + } + + // TODO: Some day we should stop these "encrypted" state->answers and + // TODO: delegate to qtypes plugin to proper XML writting the needed info on each question + + // TODO: Should be doing here some introspection in the "answer" element, based on qtype, + // TODO: to know which real questions are being used (for randoms and other qtypes...) + // TODO: Not needed if consistency is guaranteed, but it isn't right now :-( + + // Define the elements + $states = new backup_nested_element('states'); + $state = new backup_nested_element('state', array('id'), array( + 'question', 'seq_number', 'answer', 'timestamp', + 'event', 'grade', 'raw_grade', 'penalty')); + + // Build the tree + $element->add_child($states); + $states->add_child($state); + + // Set the sources + $state->set_source_table('question_states', array('attempt' => '../../' . $questionattemptname)); + + // Annotate ids + $state->annotate_ids('question', 'question'); + } + + /** + * Attach to $element (usually attempts) the needed backup structures + * for question_sessions for a given question_attempt + */ + protected function add_question_attempts_sessions($element, $questionattemptname) { + // Check $element is one nested_backup_element + if (! $element instanceof backup_nested_element) { + throw new backup_step_exception('question_sessions_bad_parent_element', $element); + } + // Check that the $questionattemptname is final element in $element + if (! $element->get_final_element($questionattemptname)) { + throw new backup_step_exception('question_sessions_bad_question_attempt_element', $questionattemptname); + } + + // Define the elements + $sessions = new backup_nested_element('sessions'); + $session = new backup_nested_element('session', array('id'), array( + 'questionid', 'newest', 'newgraded', 'sumpenalty', + 'manualcomment', 'manualcommentformat', 'flagged')); + + // Build the tree + $element->add_child($sessions); + $sessions->add_child($session); + + // Set the sources + $session->set_source_table('question_sessions', array('attemptid' => '../../' . $questionattemptname)); + + // Annotate ids + $session->annotate_ids('question', 'questionid'); + + // Annotate files + // Note: question_sessions haven't files associated. On purpose manualcomment is lacking + // support for them, so we don't need to annotated them here. + } +} + +/** + * backup structure step in charge of calculating the categories to be + * included in backup, based in the context being backuped (module/course) + * and the already annotated questions present in backup_ids_temp + */ +class backup_calculate_question_categories extends backup_execution_step { + + protected function define_execution() { + backup_question_dbops::calculate_question_categories($this->get_backupid(), $this->task->get_contextid()); + } +} + +/** + * backup structure step in charge of deleting all the questions annotated + * in the backup_ids_temp table + */ +class backup_delete_temp_questions extends backup_execution_step { + + protected function define_execution() { + backup_question_dbops::delete_temp_questions($this->get_backupid()); + } +} + /** * Abstract structure step, parent of all the block structure steps. Used to wrap the * block structure definition within the main tag @@ -1429,6 +1532,91 @@ protected function define_execution() { } } +/** + * This step will generate all the file annotations for the already + * annotated (final) question_categories. It calculates the different + * contexts that are being backup and, annotates all the files + * on every context belonging to the "question" component. As far as + * we are always including *complete* question banks it is safe and + * optimal to do that in this (one pass) way + */ +class backup_annotate_all_question_files extends backup_execution_step { + + protected function define_execution() { + global $DB; + + // Get all the different contexts for the final question_categories + // annotated along the whole backup + $rs = $DB->get_recordset_sql("SELECT DISTINCT qc.contextid + FROM {question_categories} qc + JOIN {backup_ids_temp} bi ON bi.itemid = qc.id + WHERE bi.backupid = ? + AND bi.itemname = 'question_categoryfinal'", array($this->get_backupid())); + 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 + backup_structure_dbops::annotate_files($this->get_backupid(), $record->contextid, 'question', null, null); + } + $rs->close(); + } +} + +/** + * structure step in charge of constructing the questions.xml file for all the + * question categories and questions required by the backup + * and letters related to one activity + */ +class backup_questions_structure_step extends backup_structure_step { + + protected function define_structure() { + + // Define each element separated + + $qcategories = new backup_nested_element('question_categories'); + + $qcategory = new backup_nested_element('question_category', array('id'), array( + 'name', 'contextid', 'contextlevel', 'contextinstanceid', + 'info', 'infoformat', 'stamp', 'parent', + 'sortorder')); + + $questions = new backup_nested_element('questions'); + + $question = new backup_nested_element('question', array('id'), array( + 'parent', 'name', 'questiontext', 'questiontextformat', + 'generalfeedback', 'generalfeedbackformat', 'defaultgrade', 'penalty', + 'qtype', 'length', 'stamp', 'version', + 'hidden', 'timecreated', 'timemodified', 'createdby', 'modifiedby')); + + // attach qtype plugin structure to $question element, only one allowed + $this->add_plugin_structure('qtype', $question, false); + + // Build the tree + + $qcategories->add_child($qcategory); + $qcategory->add_child($questions); + + $questions->add_child($question); + + // Define the sources + + $qcategory->set_source_sql(" + SELECT gc.*, contextlevel, instanceid AS contextinstanceid + FROM {question_categories} gc + JOIN {backup_ids_temp} bi ON bi.itemid = gc.id + JOIN {context} co ON co.id = gc.contextid + WHERE bi.backupid = ? + AND bi.itemname = 'question_categoryfinal'", array(backup::VAR_BACKUPID)); + + $question->set_source_table('question', array('category' => backup::VAR_PARENTID)); + + // don't need to annotate ids nor files + + return $qcategories; + } +} + + + /** * This step will generate all the file annotations for the already * annotated (final) users. Need to do this here because each user @@ -1622,4 +1810,4 @@ protected function define_structure() { return $cc; } -} \ No newline at end of file +} diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index cdb07295963c3..a8276133dd249 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -1605,23 +1605,11 @@ public function process_block($data) { throw new restore_step_exception('restore_block_missing_parent_ctx', $data->parentcontextid); } - // get instance of block object, we need to query it - $data->blockname = clean_param($data->blockname, PARAM_SAFEDIR); - $blockfile = $CFG->dirroot.'/blocks/'.$data->blockname.'/block_'.$data->blockname.'.php'; - if (!file_exists($blockfile)) { - return false; - } - include_once($blockfile); - $classname = 'block_'.$data->blockname; - if (!class_exists($classname)) { - return false; - } - $blockobject = new $classname(); - - //TODO: it would be nice to use standard plugin supports instead of this instance_allow_multiple() + // TODO: it would be nice to use standard plugin supports instead of this instance_allow_multiple() // If there is already one block of that type in the parent context // and the block is not multiple, stop processing - if (!$blockobject->instance_allow_multiple()) { + // Use blockslib loader / method executor + if (!block_method_result($data->blockname, 'instance_allow_multiple')) { if ($DB->record_exists_sql("SELECT bi.id FROM {block_instances} bi JOIN {block} b ON b.name = bi.blockname diff --git a/backup/util/dbops/backup_question_dbops.class.php b/backup/util/dbops/backup_question_dbops.class.php new file mode 100644 index 0000000000000..67ff54fa28111 --- /dev/null +++ b/backup/util/dbops/backup_question_dbops.class.php @@ -0,0 +1,80 @@ +. + +/** + * @package moodlecore + * @subpackage backup-dbops + * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Non instantiable helper class providing DB support to the questions backup stuff + * + * This class contains various static methods available for all the DB operations + * performed by questions stuff + * + * TODO: Finish phpdocs + */ +abstract class backup_question_dbops extends backup_dbops { + + /** + * Calculates all the question_categories to be included + * in backup, based in a given context (course/module) and + * the already annotated questions present in backup_ids_temp + */ + public static function calculate_question_categories($backupid, $contextid) { + global $DB; + + // First step, annotate all the categories for the given context (course/module) + // i.e. the whole context questions bank + $DB->execute("INSERT INTO {backup_ids_temp} (backupid, itemname, itemid) + SELECT ?, 'question_category', id + FROM {question_categories} + WHERE contextid = ?", array($backupid, $contextid)); + + // Now, based in the annotated questions, annotate all the categories they + // belong to (whole context question banks too) + // First, get all the contexts we are going to save their question bank (no matter + // where they are in the contexts hierarchy, transversals... whatever) + $contexts = $DB->get_fieldset_sql("SELECT DISTINCT qc2.contextid + FROM {question_categories} qc2 + JOIN {question} q ON q.category = qc2.id + JOIN {backup_ids_temp} bi ON bi.itemid = q.id + WHERE bi.backupid = ? + AND bi.itemname = 'question' + AND qc2.contextid != ?", array($backupid, $contextid)); + // And now, simply insert all the question categories (complete question bank) + // for those contexts if we have found any + if ($contexts) { + list($contextssql, $contextparams) = $DB->get_in_or_equal($contexts); + $params = array_merge(array($backupid), $contextparams); + $DB->execute("INSERT INTO {backup_ids_temp} (backupid, itemname, itemid) + SELECT ?, 'question_category', id + FROM {question_categories} + WHERE contextid $contextssql", $params); + } + } + + /** + * Delete all the annotated questions present in backup_ids_temp + */ + public static function delete_temp_questions($backupid) { + global $DB; + $DB->delete_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'question')); + } +} diff --git a/backup/util/dbops/backup_structure_dbops.class.php b/backup/util/dbops/backup_structure_dbops.class.php index ee2cdd17eca4b..00134da40d554 100644 --- a/backup/util/dbops/backup_structure_dbops.class.php +++ b/backup/util/dbops/backup_structure_dbops.class.php @@ -108,9 +108,13 @@ public static function annotate_files($backupid, $contextid, $component, $filear $sql = 'SELECT id FROM {files} WHERE contextid = ? - AND component = ? - AND filearea = ?'; - $params = array($contextid, $component, $filearea); + AND component = ?'; + $params = array($contextid, $component); + + if (!is_null($filearea)) { // Add filearea to query and params if necessary + $sql .= ' AND filearea = ?'; + $params[] = $filearea; + } if (!is_null($itemid)) { // Add itemid to query and params if necessary $sql .= ' AND itemid = ?'; @@ -139,7 +143,7 @@ public static function move_annotations_to_final($backupid, $itemname) { } } $rs->close(); - // All the remaining 'user' annotations can be safely deleted + // All the remaining $itemname annotations can be safely deleted $DB->delete_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => $itemname)); } diff --git a/backup/util/helper/backup_helper.class.php b/backup/util/helper/backup_helper.class.php index b388b1a14e68b..759142d01a2fd 100644 --- a/backup/util/helper/backup_helper.class.php +++ b/backup/util/helper/backup_helper.class.php @@ -287,7 +287,7 @@ public static function is_sqlparam($value) { * inforef.xml files. Used both by backup and restore */ public static function get_inforef_itemnames() { - return array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item'); + return array('user', 'grouping', 'group', 'role', 'file', 'scale', 'outcome', 'grade_item', 'question_category'); } } diff --git a/backup/util/includes/backup_includes.php b/backup/util/includes/backup_includes.php index 4fc42a26d867d..b70e6c74dbb57 100644 --- a/backup/util/includes/backup_includes.php +++ b/backup/util/includes/backup_includes.php @@ -40,6 +40,7 @@ require_once($CFG->dirroot . '/backup/util/dbops/backup_structure_dbops.class.php'); require_once($CFG->dirroot . '/backup/util/dbops/backup_controller_dbops.class.php'); require_once($CFG->dirroot . '/backup/util/dbops/backup_plan_dbops.class.php'); +require_once($CFG->dirroot . '/backup/util/dbops/backup_question_dbops.class.php'); require_once($CFG->dirroot . '/backup/util/checks/backup_check.class.php'); require_once($CFG->dirroot . '/backup/util/structure/base_atom.class.php'); require_once($CFG->dirroot . '/backup/util/structure/base_attribute.class.php'); diff --git a/backup/util/plan/backup_structure_step.class.php b/backup/util/plan/backup_structure_step.class.php index 63fca5cfdc50c..adf99e6fcf3f7 100644 --- a/backup/util/plan/backup_structure_step.class.php +++ b/backup/util/plan/backup_structure_step.class.php @@ -100,6 +100,43 @@ public function execute() { // Protected API starts here + /** + * Add plugin structure to any element in the activity 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 + * 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) + */ + protected function add_plugin_structure($plugintype, $element, $multiple) { + + global $CFG; + + // Check the requested plugintype is a valid one + if (!array_key_exists($plugintype, get_plugin_types($plugintype))) { + throw new backup_step_exception('incorrect_plugin_type', $plugintype); + } + + // Arrived here, plugin is correct, let's create the optigroup + $optigroupname = $plugintype . '_' . $element->get_name() . '_plugin'; + $optigroup = new backup_optigroup($optigroupname, null, $multiple); + $element->add_child($optigroup); // Add optigroup to stay connected since beginning + + // Get all the optigroup_elements, looking across all the plugin dirs + $pluginsdirs = get_plugin_list($plugintype); + foreach ($pluginsdirs as $name => $plugindir) { + $classname = 'backup_' . $plugintype . '_' . $name . '_plugin'; + $backupfile = $plugindir . '/backup/moodle2/' . $classname . '.class.php'; + if (file_exists($backupfile)) { + require_once($backupfile); + $backupplugin = new $classname($plugintype, $name, $optigroup); + // Add plugin returned structure to optigroup + $backupplugin->define_plugin_structure($element->get_name()); + } + } + } + /** * To conditionally decide if one step will be executed or no * diff --git a/mod/data/field/textarea/field.class.php b/mod/data/field/textarea/field.class.php index 248834523b43b..3a28d57f47e03 100755 --- a/mod/data/field/textarea/field.class.php +++ b/mod/data/field/textarea/field.class.php @@ -67,7 +67,7 @@ function display_add_field($recordid=0) { $formats[$fid] = $strformats[$fid]; } $editor->use_editor($field, $options); - $str .= '
'; + $str .= '
'; $str .= '