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';
}
/*
// 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'));
}
}
+/**
+ * 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
// 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
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');
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Class implementing the plugins support for moodle2 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;
+ }
+
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Class extending standard 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
+ }
+}
}
}
+/**
+ * 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 <block ...> tag
}
}
+/**
+ * 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
return $cc;
}
-}
\ No newline at end of file
+}
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
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-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'));
+ }
+}
$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 = ?';
}
}
$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));
}
* 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');
}
}
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');
// 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
*
$formats[$fid] = $strformats[$fid];
}
$editor->use_editor($field, $options);
- $str .= '<div><textarea id="'.$field.'" name="'.$field.'" rows="15" cols="80">'.s($text).'</textarea></div>';
+ $str .= '<div><textarea id="'.$field.'" name="'.$field.'" rows="'.$this->field->param3.'" cols="'.$this->field->param2.'">'.s($text).'</textarea></div>';
$str .= '<div><select name="'.$field.'_content1">';
foreach ($formats as $key=>$desc) {
$selected = ($format == $key) ? 'selected="selected"' : '';
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/mod/quiz/backup/moodle2/backup_quiz_stepslib.php'); // Because it exists (must)
+
+/**
+ * quiz backup task that provides all the settings and steps to perform one
+ * complete backup of the activity
+ */
+class backup_quiz_activity_task extends backup_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() {
+ // Generate the quiz.xml file containing all the quiz information
+ // and annotating used questions
+ $this->add_step(new backup_quiz_activity_structure_step('quiz_structure', 'quiz.xml'));
+
+ // Note: Following steps must be present
+ // in all the activities using question banks (only quiz for now)
+ // TODO: Specialise these step to a new subclass of backup_activity_task
+
+ // Process all the annotated questions to calculate the question
+ // categories needing to be included in backup for this activity
+ // plus the categories belonging to the activity context itself
+ $this->add_step(new backup_calculate_question_categories('activity_question_categories'));
+
+ // Clean backup_temp_ids table from questions. We already
+ // have used them to detect question_categories and aren't
+ // needed anymore
+ $this->add_step(new backup_delete_temp_questions('clean_temp_questions'));
+ }
+
+ /**
+ * Code the transformations to perform in the activity in
+ * order to get transportable (encoded) links
+ */
+ static public function encode_content_links($content) {
+ global $CFG;
+
+ $base = preg_quote($CFG->wwwroot,"/");
+
+ // Link to the list of quizzes
+ $search="/(".$base."\/mod\/quiz\/index.php\?id\=)([0-9]+)/";
+ $content= preg_replace($search, '$@QUIZINDEX*$2@$', $content);
+
+ // Link to quiz view by moduleid
+ $search="/(".$base."\/mod\/quiz\/view.php\?id\=)([0-9]+)/";
+ $content= preg_replace($search, '$@QUIZVIEWBYID*$2@$', $content);
+
+ // Link to quiz view by quizid
+ $search="/(".$base."\/mod\/quiz\/view.php\?q\=)([0-9]+)/";
+ $content= preg_replace($search, '$@QUIZVIEWBYQ*$2@$', $content);
+
+ return $content;
+ }
+}
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Define all the backup steps that will be used by the backup_quiz_activity_task
+ */
+
+/**
+ * Define the complete quiz structure for backup, with file and id annotations
+ */
+class backup_quiz_activity_structure_step extends backup_questions_activity_structure_step {
+
+ protected function define_structure() {
+
+ // To know if we are including userinfo
+ $userinfo = $this->get_setting_value('userinfo');
+
+ // Define each element separated
+ $quiz = new backup_nested_element('quiz', array('id'), array(
+ 'name', 'intro', 'introformat', 'timeopen',
+ 'timeclose', 'optionflags', 'penaltyscheme', 'attempts',
+ 'attemptonlast', 'grademethod', 'decimalpoints', 'questiondecimalpoints',
+ 'review', 'questionsperpage', 'shufflequestions', 'shuffleanswers',
+ 'questions', 'sumgrades', 'grade', 'timecreated',
+ 'timemodified', 'timelimit', 'password', 'subnet',
+ 'popup', 'delay1', 'delay2', 'showuserpicture'));
+
+ $qinstances = new backup_nested_element('question_instances');
+
+ $qinstance = new backup_nested_element('question_instance', array('id'), array(
+ 'question', 'grade'));
+
+ $feedbacks = new backup_nested_element('feedbacks');
+
+ $feedback = new backup_nested_element('feedback', array('id'), array(
+ 'feedbacktext', 'feedbacktextformat', 'mingrade', 'maxgrade'));
+
+ $overrides = new backup_nested_element('overrides');
+
+ $override = new backup_nested_element('override', array('id'), array(
+ 'userid', 'groupid', 'timeopen', 'timeclose',
+ 'timelimit', 'attempts', 'password'));
+
+ $grades = new backup_nested_element('grades');
+
+ $grade = new backup_nested_element('grade', array('id'), array(
+ 'userid', 'gradeval', 'timemodified'));
+
+ $attempts = new backup_nested_element('attempts');
+
+ $attempt = new backup_nested_element('attempt', array('id'), array(
+ 'uniqueid', 'userid', 'attemptnum', 'sumgrades',
+ 'timestart', 'timefinish', 'timemodified', 'layout',
+ 'preview'));
+
+ // This module is using questions, so produce the related question states and sessions
+ // attaching them to the $attempt element based in 'uniqueid' matching
+ $this->add_question_attempts_states($attempt, 'uniqueid');
+ $this->add_question_attempts_sessions($attempt, 'uniqueid');
+
+ // Build the tree
+
+ $quiz->add_child($qinstances);
+ $qinstances->add_child($qinstance);
+
+ $quiz->add_child($feedbacks);
+ $feedbacks->add_child($feedback);
+
+ $quiz->add_child($overrides);
+ $overrides->add_child($override);
+
+ $quiz->add_child($grades);
+ $grades->add_child($grade);
+
+ $quiz->add_child($attempts);
+ $attempts->add_child($attempt);
+
+ // Define sources
+ $quiz->set_source_table('quiz', array('id' => backup::VAR_ACTIVITYID));
+
+ $qinstance->set_source_table('quiz_question_instances', array('quiz' => backup::VAR_PARENTID));
+
+ $feedback->set_source_table('quiz_feedback', array('quizid' => backup::VAR_PARENTID));
+
+ // Quiz overrides to backup are different depending of user info
+ $overrideparams = array('quiz' => backup::VAR_PARENTID);
+ if (!$userinfo) { // Without userinfo, skip user overrides
+ $overrideparams['userid'] = backup_helper::is_sqlparam(null);
+
+ }
+ $override->set_source_table('quiz_overrides', $overrideparams);
+
+ // All the rest of elements only happen if we are including user info
+ 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
+ $grade->set_source_alias('grade', 'gradeval');
+ $attempt->set_source_alias('attempt', 'attemptnum');
+
+ // Define id annotations
+ $qinstance->annotate_ids('question', 'question');
+ $override->annotate_ids('user', 'userid');
+ $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
+ $feedback->annotate_files('mod_quiz', 'feedback', 'id');
+
+ // Return the root element (quiz), wrapped into standard activity structure
+ return $this->prepare_activity_structure($quiz);
+ }
+}
case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
case FEATURE_GRADE_HAS_GRADE: return true;
case FEATURE_GRADE_OUTCOMES: return true;
+ case FEATURE_BACKUP_MOODLE2: return true;
default: return null;
}