Merge branch 'MDL-43749' of git://github.com/timhunt/moodle

This commit is contained in:
Dan Poltawski
2014-03-03 09:36:54 +08:00
48 changed files with 821 additions and 893 deletions
+3 -2
View File
@@ -4236,7 +4236,7 @@ abstract class restore_questions_activity_structure_step extends restore_activit
/**
* Process the attempt data defined by {@link add_legacy_question_attempt_data()}.
* @param object $data contains all the grouped attempt data ot process.
* @param object $data contains all the grouped attempt data to process.
* @param pbject $quiz data about the activity the attempts belong to. Required
* fields are (basically this only works for the quiz module):
* oldquestions => list of question ids in this activity - using old ids.
@@ -4298,7 +4298,8 @@ abstract class restore_questions_activity_structure_step extends restore_activit
$this->inform_new_usage_id($usage->id);
$data->uniqueid = $usage->id;
$upgrader->save_usage($quiz->preferredbehaviour, $data, $qas, $quiz->questions);
$upgrader->save_usage($quiz->preferredbehaviour, $data, $qas,
$this->questions_recode_layout($quiz->oldquestions));
}
protected function find_question_session_and_states($data, $questionid) {
+32 -24
View File
@@ -718,51 +718,59 @@ function question_preview_popup_params() {
* to pull in extra data. See, for example, the usage in mod/quiz/attemptlib.php, and
* read the code below to see how the SQL is assembled. Throws exceptions on error.
*
* @global object
* @global object
* @param array $questionids array of question ids.
* @param array $questionids array of question ids to load. If null, then all
* questions matched by $join will be loaded.
* @param string $extrafields extra SQL code to be added to the query.
* @param string $join extra SQL code to be added to the query.
* @param array $extraparams values for any placeholders in $join.
* You are strongly recommended to use named placeholder.
* You must use named placeholders.
* @param string $orderby what to order the results by. Optional, default is unspecified order.
*
* @return array partially complete question objects. You need to call get_question_options
* on them before they can be properly used.
*/
function question_preload_questions($questionids, $extrafields = '', $join = '',
$extraparams = array()) {
function question_preload_questions($questionids = null, $extrafields = '', $join = '',
$extraparams = array(), $orderby = '') {
global $DB;
if (empty($questionids)) {
return array();
if ($questionids === null) {
$where = '';
$params = array();
} else {
if (empty($questionids)) {
return array();
}
list($questionidcondition, $params) = $DB->get_in_or_equal(
$questionids, SQL_PARAMS_NAMED, 'qid0000');
$where = 'WHERE q.id ' . $questionidcondition;
}
if ($join) {
$join = ' JOIN '.$join;
$join = 'JOIN ' . $join;
}
if ($extrafields) {
$extrafields = ', ' . $extrafields;
}
list($questionidcondition, $params) = $DB->get_in_or_equal(
$questionids, SQL_PARAMS_NAMED, 'qid0000');
$sql = 'SELECT q.*, qc.contextid' . $extrafields . ' FROM {question} q
JOIN {question_categories} qc ON q.category = qc.id' .
$join .
' WHERE q.id ' . $questionidcondition;
// Load the questions
if (!$questions = $DB->get_records_sql($sql, $extraparams + $params)) {
return array();
if ($orderby) {
$orderby = 'ORDER BY ' . $orderby;
}
$sql = "SELECT q.*, qc.contextid{$extrafields}
FROM {question} q
JOIN {question_categories} qc ON q.category = qc.id
{$join}
{$where}
{$orderby}";
// Load the questions.
$questions = $DB->get_records_sql($sql, $extraparams + $params);
foreach ($questions as $question) {
$question->_partiallyloaded = true;
}
// Note, a possible optimisation here would be to not load the TEXT fields
// (that is, questiontext and generalfeedback) here, and instead load them in
// question_load_questions. That would add one DB query, but reduce the amount
// of data transferred most of the time. I am not going to do this optimisation
// until it is shown to be worthwhile.
return $questions;
}
@@ -44,7 +44,6 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
$quiz->delay1 = 1000;
$quiz->delay2 = 0;
$quiz->timeclose = 0;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -79,7 +78,6 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
$quiz->delay1 = 0;
$quiz->delay2 = 1000;
$quiz->timeclose = 0;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -119,7 +117,6 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
$quiz->delay1 = 2000;
$quiz->delay2 = 1000;
$quiz->timeclose = 0;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -171,7 +168,6 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
$quiz->delay1 = 2000;
$quiz->delay2 = 1000;
$quiz->timeclose = 15000;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -228,7 +224,6 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
$quiz->delay1 = 2000;
$quiz->delay2 = 1000;
$quiz->timeclose = 0;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -48,7 +48,6 @@ class quizaccess_ipaddress_testcase extends basic_testcase {
// does not always work, for example using the mac install package on my laptop.
$quiz->subnet = getremoteaddr(null);
if (!empty($quiz->subnet)) {
$quiz->questions = '';
$quizobj = new quiz($quiz, $cm, null);
$rule = new quizaccess_ipaddress($quizobj, 0);
@@ -61,7 +60,6 @@ class quizaccess_ipaddress_testcase extends basic_testcase {
}
$quiz->subnet = '0.0.0.0';
$quiz->questions = '';
$quizobj = new quiz($quiz, $cm, null);
$rule = new quizaccess_ipaddress($quizobj, 0);
@@ -41,7 +41,6 @@ class quizaccess_numattempts_testcase extends basic_testcase {
public function test_num_attempts_access_rule() {
$quiz = new stdClass();
$quiz->attempts = 3;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -43,7 +43,6 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
$quiz->timeopen = 0;
$quiz->timeclose = 0;
$quiz->overduehandling = 'autosubmit';
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -73,7 +72,6 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
$quiz->timeopen = 10000;
$quiz->timeclose = 0;
$quiz->overduehandling = 'autosubmit';
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -105,7 +103,6 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
$quiz->timeopen = 0;
$quiz->timeclose = 20000;
$quiz->overduehandling = 'autosubmit';
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -144,7 +141,6 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
$quiz->timeopen = 10000;
$quiz->timeclose = 20000;
$quiz->overduehandling = 'autosubmit';
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -197,7 +193,6 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
$quiz->timeclose = 20000;
$quiz->overduehandling = 'graceperiod';
$quiz->graceperiod = 1000;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -41,7 +41,6 @@ class quizaccess_password_testcase extends basic_testcase {
public function test_password_access_rule() {
$quiz = new stdClass();
$quiz->password = 'frog';
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -42,7 +42,6 @@ class quizaccess_safebrowser_testcase extends basic_testcase {
public function test_safebrowser_access_rule() {
$quiz = new stdClass();
$quiz->browsersecurity = 'safebrowser';
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -43,7 +43,6 @@ class quizaccess_securewindow_testcase extends basic_testcase {
public function test_securewindow_access_rule() {
$quiz = new stdClass();
$quiz->browsersecurity = 'securewindow';
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
@@ -40,7 +40,6 @@ class quizaccess_timelimit_testcase extends basic_testcase {
public function test_time_limit_access_rule() {
$quiz = new stdClass();
$quiz->timelimit = 3600;
$quiz->questions = '';
$cm = new stdClass();
$cm->id = 0;
$quizobj = new quiz($quiz, $cm, null);
+14 -18
View File
@@ -65,7 +65,6 @@ class quiz {
protected $cm;
protected $quiz;
protected $context;
protected $questionids;
// Fields set later if that data is needed.
protected $questions = null;
@@ -89,12 +88,6 @@ class quiz {
if ($getcontext && !empty($cm->id)) {
$this->context = context_module::instance($cm->id);
}
$questionids = quiz_questions_in_quiz($this->quiz->questions);
if ($questionids) {
$this->questionids = explode(',', quiz_questions_in_quiz($this->quiz->questions));
} else {
$this->questionids = array(); // Which idiot made explode(',', '') = array('')?
}
}
/**
@@ -132,13 +125,10 @@ class quiz {
* Load just basic information about all the questions in this quiz.
*/
public function preload_questions() {
if (empty($this->questionids)) {
throw new moodle_quiz_exception($this, 'noquestions', $this->edit_url());
}
$this->questions = question_preload_questions($this->questionids,
'qqi.maxmark, qqi.id AS instance',
'{quiz_question_instances} qqi ON qqi.quizid = :quizid AND q.id = qqi.questionid',
array('quizid' => $this->quiz->id));
$this->questions = question_preload_questions(null,
'slot.maxmark, slot.id AS slotid, slot.slot, slot.page',
'{quiz_slots} slot ON slot.quizid = :quizid AND q.id = slot.questionid',
array('quizid' => $this->quiz->id), 'slot.slot');
}
/**
@@ -148,8 +138,11 @@ class quiz {
* @param array $questionids question ids of the questions to load. null for all.
*/
public function load_questions($questionids = null) {
if ($this->questions === null) {
throw new coding_exception('You must call preload_questions before calling load_questions.');
}
if (is_null($questionids)) {
$questionids = $this->questionids;
$questionids = array_keys($this->questions);
}
$questionstoprocess = array();
foreach ($questionids as $id) {
@@ -226,7 +219,10 @@ class quiz {
* @return whether any questions have been added to this quiz.
*/
public function has_questions() {
return !empty($this->questionids);
if ($this->questions === null) {
$this->preload_questions();
}
return !empty($this->questions);
}
/**
@@ -242,7 +238,7 @@ class quiz {
*/
public function get_questions($questionids = null) {
if (is_null($questionids)) {
$questionids = $this->questionids;
$questionids = array_keys($this->questions);
}
$questions = array();
foreach ($questionids as $id) {
@@ -530,7 +526,7 @@ class quiz_attempt {
$this->pagelayout = array();
// Break up the layout string into pages.
$pagelayouts = explode(',0', quiz_clean_layout($this->attempt->layout, true));
$pagelayouts = explode(',0', $this->attempt->layout);
// Strip off any empty last page (normally there is one).
if (end($pagelayouts) == '') {
@@ -47,7 +47,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
'reviewspecificfeedback', 'reviewgeneralfeedback',
'reviewrightanswer', 'reviewoverallfeedback',
'questionsperpage', 'navmethod', 'shufflequestions', 'shuffleanswers',
'questions', 'sumgrades', 'grade', 'timecreated',
'sumgrades', 'grade', 'timecreated',
'timemodified', 'password', 'subnet', 'browsersecurity',
'delay1', 'delay2', 'showuserpicture', 'showblocks'));
@@ -57,7 +57,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
$qinstances = new backup_nested_element('question_instances');
$qinstance = new backup_nested_element('question_instance', array('id'), array(
'questionid', 'maxmark'));
'slot', 'page', 'questionid', 'maxmark'));
$feedbacks = new backup_nested_element('feedbacks');
@@ -107,7 +107,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
// Define sources.
$quiz->set_source_table('quiz', array('id' => backup::VAR_ACTIVITYID));
$qinstance->set_source_table('quiz_question_instances',
$qinstance->set_source_table('quiz_slots',
array('quizid' => backup::VAR_PARENTID));
$feedback->set_source_table('quiz_feedback',
@@ -92,9 +92,10 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
$data->timecreated = $this->apply_date_offset($data->timecreated);
$data->timemodified = $this->apply_date_offset($data->timemodified);
// Needed by {@link process_quiz_attempt_legacy}.
$this->oldquizlayout = $data->questions;
$data->questions = $this->questions_recode_layout($data->questions);
if (property_exists($data, 'questions')) {
// Needed by {@link process_quiz_attempt_legacy}, in which case it will be present.
$this->oldquizlayout = $data->questions;
}
// The setting quiz->attempts can come both in data->attempts and
// data->attempts_number, handle both. MDL-26229.
@@ -243,10 +244,36 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
$data->maxmark = $data->grade;
}
if (!property_exists($data, 'slot')) {
$page = 1;
$slot = 1;
foreach (explode(',', $this->oldquizlayout) as $item) {
if ($item == 0) {
$page += 1;
continue;
}
if ($item == $data->questionid) {
$data->slot = $slot;
$data->page = $page;
break;
}
$slot += 1;
}
}
if (!property_exists($data, 'slot')) {
// There was a question_instance in the backup file for a question
// that was not acutally in the quiz. Drop it.
$this->log('question ' . $data->questionid . ' was associated with quiz ' .
$quiz->id . ' but not actually used. ' .
'The instance has been ignored.', backup::LOG_INFO);
return;
}
$data->quizid = $this->get_new_parentid('quiz');
$data->questionid = $this->get_mappingid('question', $data->questionid);
$DB->insert_record('quiz_question_instances', $data);
$DB->insert_record('quiz_slots', $data);
}
protected function process_quiz_feedback($data) {
+52 -48
View File
@@ -33,7 +33,6 @@
<FIELD NAME="navmethod" TYPE="char" LENGTH="16" NOTNULL="true" DEFAULT="free" SEQUENCE="false" COMMENT="Any constraints on how the user is allowed to navigate around the quiz. Currently recognised values are 'free' and 'seq'."/>
<FIELD NAME="shufflequestions" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether the question order should be shuffled for each attempt."/>
<FIELD NAME="shuffleanswers" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether the parts of the question should be shuffled, in those question types that support it."/>
<FIELD NAME="questions" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Comma-separated list of question ids, with 0s for page breaks. The quiz layout. See also the quiz_question_instances table."/>
<FIELD NAME="sumgrades" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The total of all the question instance maxmarks."/>
<FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The total that the quiz overall grade is scaled to be out of."/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time when the quiz was added to the course."/>
@@ -53,6 +52,57 @@
<INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
</INDEXES>
</TABLE>
<TABLE NAME="quiz_slots" COMMENT="Stores the question used in a quiz, with the order, and for each question, which page it appears on, and the maximum mark (weight).">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="slot" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Where this question comes in order in the list of questions in this quiz. Like question_attempts.slot."/>
<FIELD NAME="quizid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id."/>
<FIELD NAME="page" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The page number that this questions appears on. If the question in slot n appears on page p, then the question in slot n+1 must appear on page p or p+1. Well, except that when a quiz is being created, there may be empty pages, which would cause the page number to jump here."/>
<FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references question.id."/>
<FIELD NAME="maxmark" TYPE="number" LENGTH="12" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="7" COMMENT="How many marks this question contributes to quiz.sumgrades."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="quizid" TYPE="foreign" FIELDS="quizid" REFTABLE="quiz" REFFIELDS="id"/>
<KEY NAME="questionid" TYPE="foreign" FIELDS="questionid" REFTABLE="question" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="quizid-slot" UNIQUE="true" FIELDS="quizid, slot"/>
</INDEXES>
</TABLE>
<TABLE NAME="quiz_feedback" COMMENT="Feedback given to students based on which grade band their overall score lies.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="quizid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id."/>
<FIELD NAME="feedbacktext" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The feedback to show for a attempt where mingrade &lt;= attempt grade &lt; maxgrade. See function quiz_feedback_for_grade in mod/quiz/locallib.php."/>
<FIELD NAME="feedbacktextformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="mingrade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The lower limit of this grade band. Inclusive."/>
<FIELD NAME="maxgrade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The upper limit of this grade band. Exclusive."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="quizid" TYPE="foreign" FIELDS="quizid" REFTABLE="quiz" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="quiz_overrides" COMMENT="The overrides to quiz settings on a per-user and per-group basis.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="quiz" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id"/>
<FIELD NAME="groupid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Foreign key references groups.id. Can be null if this is a per-user override."/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Foreign key references user.id. Can be null if this is a per-group override."/>
<FIELD NAME="timeopen" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time at which students may start attempting this quiz. Can be null, in which case the quiz default is used."/>
<FIELD NAME="timeclose" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time by which students must have completed their attempt. Can be null, in which case the quiz default is used."/>
<FIELD NAME="timelimit" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time limit in seconds. Can be null, in which case the quiz default is used."/>
<FIELD NAME="attempts" TYPE="int" LENGTH="6" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Quiz password. Can be null, in which case the quiz default is used."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="quiz" TYPE="foreign" FIELDS="quiz" REFTABLE="quiz" REFFIELDS="id"/>
<KEY NAME="groupid" TYPE="foreign" FIELDS="groupid" REFTABLE="groups" REFFIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="quiz_attempts" COMMENT="Stores users attempts at quizzes.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="Standard Moodle primary key."/>
@@ -97,52 +147,6 @@
<INDEX NAME="userid" UNIQUE="false" FIELDS="userid"/>
</INDEXES>
</TABLE>
<TABLE NAME="quiz_question_instances" COMMENT="Stores the maximum possible grade (weight) for each question used in a quiz.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="quizid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id."/>
<FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references question.id."/>
<FIELD NAME="maxmark" TYPE="number" LENGTH="12" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="7" COMMENT="How many marks this question contributes to quiz.sumgrades."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="quizid" TYPE="foreign" FIELDS="quizid" REFTABLE="quiz" REFFIELDS="id"/>
<KEY NAME="questionid" TYPE="foreign" FIELDS="questionid" REFTABLE="question" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="quiz_feedback" COMMENT="Feedback given to students based on which grade band their overall score lies.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="quizid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id."/>
<FIELD NAME="feedbacktext" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The feedback to show for a attempt where mingrade &lt;= attempt grade &lt; maxgrade. See function quiz_feedback_for_grade in mod/quiz/locallib.php."/>
<FIELD NAME="feedbacktextformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="mingrade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The lower limit of this grade band. Inclusive."/>
<FIELD NAME="maxgrade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The upper limit of this grade band. Exclusive."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="quizid" TYPE="foreign" FIELDS="quizid" REFTABLE="quiz" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="quiz_overrides" COMMENT="The overrides to quiz settings on a per-user and per-group basis.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="quiz" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references quiz.id"/>
<FIELD NAME="groupid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Foreign key references groups.id. Can be null if this is a per-user override."/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Foreign key references user.id. Can be null if this is a per-group override."/>
<FIELD NAME="timeopen" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time at which students may start attempting this quiz. Can be null, in which case the quiz default is used."/>
<FIELD NAME="timeclose" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time by which students must have completed their attempt. Can be null, in which case the quiz default is used."/>
<FIELD NAME="timelimit" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time limit in seconds. Can be null, in which case the quiz default is used."/>
<FIELD NAME="attempts" TYPE="int" LENGTH="6" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Quiz password. Can be null, in which case the quiz default is used."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="quiz" TYPE="foreign" FIELDS="quiz" REFTABLE="quiz" REFFIELDS="id"/>
<KEY NAME="groupid" TYPE="foreign" FIELDS="groupid" REFTABLE="groups" REFFIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="quiz_reports" COMMENT="Lists all the installed quiz reports and their display order and so on. No need to worry about deleting old records. Only records with an equivalent directory are displayed.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
@@ -158,4 +162,4 @@
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
</XMLDB>
+246
View File
@@ -515,6 +515,252 @@ function xmldb_quiz_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2014021300, 'quiz');
}
if ($oldversion < 2014022000) {
// Define table quiz_question_instances to be renamed to quiz_slots.
$table = new xmldb_table('quiz_question_instances');
// Launch rename table for quiz_question_instances.
$dbman->rename_table($table, 'quiz_slots');
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022000, 'quiz');
}
if ($oldversion < 2014022001) {
// Define field slot to be added to quiz_slots.
$table = new xmldb_table('quiz_slots');
$field = new xmldb_field('slot', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'id');
// Conditionally launch add field slot.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022001, 'quiz');
}
if ($oldversion < 2014022002) {
// Define field page to be added to quiz_slots.
$table = new xmldb_table('quiz_slots');
$field = new xmldb_field('page', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'quizid');
// Conditionally launch add field page.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022002, 'quiz');
}
if ($oldversion < 2014022003) {
// Use the information in the old quiz.questions column to fill in the
// new slot and page columns.
$numquizzes = $DB->count_records('quiz');
if ($numquizzes > 0) {
$pbar = new progress_bar('quizquestionstoslots', 500, true);
$transaction = $DB->start_delegated_transaction();
$numberdone = 0;
$quizzes = $DB->get_recordset('quiz', null, 'id', 'id,questions,sumgrades');
foreach ($quizzes as $quiz) {
if ($quiz->questions === '') {
$questionsinorder = array();
} else {
$questionsinorder = explode(',', $quiz->questions);
}
$questionidtoslotrowid = $DB->get_records_menu('quiz_slots',
array('quizid' => $quiz->id), '', 'questionid, id');
$problemfound = false;
$currentpage = 1;
$slot = 1;
foreach ($questionsinorder as $questionid) {
if ($questionid === '0') {
// Page break.
$currentpage++;
continue;
}
if (array_key_exists($questionid, $questionidtoslotrowid)) {
// Normal case. quiz_slots entry is present.
// Just need to add slot and page.
$quizslot = new stdClass();
$quizslot->id = $questionidtoslotrowid[$questionid];
$quizslot->slot = $slot;
$quizslot->page = $currentpage;
$DB->update_record('quiz_slots', $quizslot);
unset($questionidtoslotrowid[$questionid]); // So we can do a sanity check later.
$slot++;
continue;
} else {
// This should not happen. The question was listed in
// quiz.questions, but there was not an entry for it in
// quiz_slots (formerly quiz_question_instances).
// Previously, if such question ids were found, then
// starting an attempt at the quiz would throw an exception.
// Here, we try to add the missing data.
$problemfound = true;
$defaultmark = $DB->get_field('question', 'defaultmark',
array('id' => $questionid), IGNORE_MISSING);
if ($defaultmark === false) {
debugging('During upgrade, detected that question ' .
$questionid . ' was listed as being part of quiz ' .
$quiz->id . ' but this question no longer exists. Ignoring it.', DEBUG_NORMAL);
// Non-existent question. Ignore it completely.
continue;
}
debugging('During upgrade, detected that question ' .
$questionid . ' was listed as being part of quiz ' .
$quiz->id . ' but there was not entry for it in ' .
'quiz_question_instances/quiz_slots. Creating an entry with default mark.', DEBUG_NORMAL);
$quizslot = new stdClass();
$quizslot->quizid = $quiz->id;
$quizslot->slot = $slot;
$quizslot->page = $currentpage;
$quizslot->questionid = $questionid;
$quizslot->maxmark = $defaultmark;
$DB->insert_record('quiz_slots', $quizslot);
$slot++;
continue;
}
}
// Now, as a sanity check, ensure we have done all the
// quiz_slots rows linked to this quiz.
if (!empty($questionidtoslotrowid)) {
debugging('During upgrade, detected that questions ' .
implode(', ', array_keys($questionidtoslotrowid)) .
' had instances in quiz ' . $quiz->id . ' but were not actually used. ' .
'The instances have been removed.', DEBUG_NORMAL);
$DB->delete_records_list('quiz_slots', 'id', array_values($questionidtoslotrowid));
$problemfound = true;
}
// If there were problems found, we probably need to re-compute
// quiz.sumgrades.
if ($problemfound) {
// C/f the quiz_update_sumgrades function in locallib.php,
// but note that what we do here is a bit simpler.
$newsumgrades = $DB->get_field_sql(
"SELECT SUM(maxmark)
FROM {quiz_slots}
WHERE quizid = ?",
array($quiz->id));
if (abs($newsumgrades - $quiz->sumgrades) > 0.000005) {
debugging('Because of the previously mentioned problems, ' .
'sumgrades for quiz ' . $quiz->id .
' was changed from ' . $quiz->sumgrades . ' to ' .
$newsumgrades . ' You should probably check that this quiz is still working: ' .
$CFG->wwwroot . '/mod/quiz/view.php?q=' . $quiz->id . '.', DEBUG_NORMAL);
$DB->set_field('quiz', 'sumgrades', $newsumgrades, array('id' => $quiz->id));
}
}
// Done with this quiz. Update progress bar.
$numberdone++;
$pbar->update($numberdone, $numquizzes,
"Upgrading quiz structure - {$numberdone}/{$numquizzes}.");
}
$transaction->allow_commit();
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022003, 'quiz');
}
if ($oldversion < 2014022004) {
// If, for any reason, there were any quiz_slots missed, then try
// to do something about that now before we add the NOT NULL constraints.
// In fact, becuase of the sanity check at the end of the above check,
// any such quiz_slots rows must refer to a non-existent quiz id, so
// delete them.
$DB->delete_records_select('quiz_slots',
'NOT EXISTS (SELECT 1 FROM {quiz} q WHERE q.id = quizid)');
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022004, 'quiz');
// Now, if any quiz_slots rows are left with slot or page NULL, something
// is badly wrong.
if ($DB->record_exists_select('quiz_slots', 'slot IS NULL OR page IS NULL')) {
throw new coding_exception('Something went wrong in the quiz upgrade step for MDL-43749. ' .
'Some quiz_slots still contain NULLs which will break the NOT NULL constraints we need to add. ' .
'Please report this problem at http://tracker.moodle.org/ so that it can be investigated. Thank you.');
}
}
if ($oldversion < 2014022005) {
// Changing nullability of field slot on table quiz_slots to not null.
$table = new xmldb_table('quiz_slots');
$field = new xmldb_field('slot', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'id');
// Launch change of nullability for field slot.
$dbman->change_field_notnull($table, $field);
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022005, 'quiz');
}
if ($oldversion < 2014022006) {
// Changing nullability of field page on table quiz_slots to not null.
$table = new xmldb_table('quiz_slots');
$field = new xmldb_field('page', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'quizid');
// Launch change of nullability for field page.
$dbman->change_field_notnull($table, $field);
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022006, 'quiz');
}
if ($oldversion < 2014022007) {
// Define index quizid-slot (unique) to be added to quiz_slots.
$table = new xmldb_table('quiz_slots');
$index = new xmldb_index('quizid-slot', XMLDB_INDEX_UNIQUE, array('quizid', 'slot'));
// Conditionally launch add index quizid-slot.
if (!$dbman->index_exists($table, $index)) {
$dbman->add_index($table, $index);
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022007, 'quiz');
}
if ($oldversion < 2014022008) {
// Define field questions to be dropped from quiz.
$table = new xmldb_table('quiz');
$field = new xmldb_field('questions');
// Conditionally launch drop field questions.
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014022008, 'quiz');
}
return true;
}
+99 -78
View File
@@ -119,7 +119,6 @@ $scrollpos = optional_param('scrollpos', '', PARAM_INT);
list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
question_edit_setup('editq', '/mod/quiz/edit.php', true);
$quiz->questions = quiz_clean_layout($quiz->questions);
$defaultcategoryobj = question_make_default_categories($contexts->all());
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
@@ -161,23 +160,14 @@ add_to_log($cm->course, 'quiz', 'editquestions',
// You need mod/quiz:manage in addition to question capabilities to access this page.
require_capability('mod/quiz:manage', $contexts->lowest());
if (empty($quiz->grades)) {
$quiz->grades = quiz_get_all_question_grades($quiz);
}
// Process commands ============================================================
if ($quiz->shufflequestions) {
// Strip page breaks before processing actions, so that re-ordering works
// as expected when shuffle questions is on.
$quiz->questions = quiz_repaginate($quiz->questions, 0);
}
// Get the list of question ids had their check-boxes ticked.
$selectedquestionids = array();
$selectedslots = array();
$params = (array) data_submitted();
foreach ($params as $key => $value) {
if (preg_match('!^s([0-9]+)$!', $key, $matches)) {
$selectedquestionids[] = $matches[1];
$selectedslots[] = $matches[1];
}
}
@@ -186,15 +176,13 @@ if ($scrollpos) {
$afteractionurl->param('scrollpos', $scrollpos);
}
if (($up = optional_param('up', false, PARAM_INT)) && confirm_sesskey()) {
$quiz->questions = quiz_move_question_up($quiz->questions, $up);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
quiz_move_question_up($quiz, $up);
quiz_delete_previews($quiz);
redirect($afteractionurl);
}
if (($down = optional_param('down', false, PARAM_INT)) && confirm_sesskey()) {
$quiz->questions = quiz_move_question_down($quiz->questions, $down);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
quiz_move_question_down($quiz, $down);
quiz_delete_previews($quiz);
redirect($afteractionurl);
}
@@ -202,8 +190,7 @@ if (($down = optional_param('down', false, PARAM_INT)) && confirm_sesskey()) {
if (optional_param('repaginate', false, PARAM_BOOL) && confirm_sesskey()) {
// Re-paginate the quiz.
$questionsperpage = optional_param('questionsperpage', $quiz->questionsperpage, PARAM_INT);
$quiz->questions = quiz_repaginate($quiz->questions, $questionsperpage );
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
quiz_repaginate_questions($quiz->id, $questionsperpage );
quiz_delete_previews($quiz);
redirect($afteractionurl);
}
@@ -248,51 +235,46 @@ if ((optional_param('addrandom', false, PARAM_BOOL)) && confirm_sesskey()) {
}
if (optional_param('addnewpagesafterselected', null, PARAM_CLEAN) &&
!empty($selectedquestionids) && confirm_sesskey()) {
foreach ($selectedquestionids as $questionid) {
$quiz->questions = quiz_add_page_break_after($quiz->questions, $questionid);
!empty($selectedslots) && confirm_sesskey()) {
foreach ($selectedslots as $slot) {
quiz_add_page_break_after_slot($quiz, $slot);
}
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
quiz_delete_previews($quiz);
redirect($afteractionurl);
}
$addpage = optional_param('addpage', false, PARAM_INT);
if ($addpage !== false && confirm_sesskey()) {
$quiz->questions = quiz_add_page_break_at($quiz->questions, $addpage);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
quiz_add_page_break_after_slot($quiz, $addpage);
quiz_delete_previews($quiz);
redirect($afteractionurl);
}
$deleteemptypage = optional_param('deleteemptypage', false, PARAM_INT);
if (($deleteemptypage !== false) && confirm_sesskey()) {
$quiz->questions = quiz_delete_empty_page($quiz->questions, $deleteemptypage);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
quiz_delete_empty_page($quiz, $deleteemptypage);
quiz_delete_previews($quiz);
redirect($afteractionurl);
}
$remove = optional_param('remove', false, PARAM_INT);
if ($remove && confirm_sesskey()) {
if ($remove && confirm_sesskey() && quiz_has_question_use($quiz, $remove)) {
// Remove a question from the quiz.
// We require the user to have the 'use' capability on the question,
// so that then can add it back if they remove the wrong one by mistake,
// but, if the question is missing, it can always be removed.
if ($DB->record_exists('question', array('id' => $remove))) {
quiz_require_question_use($remove);
}
quiz_remove_question($quiz, $remove);
quiz_remove_slot($quiz, $remove);
quiz_delete_previews($quiz);
quiz_update_sumgrades($quiz);
redirect($afteractionurl);
}
if (optional_param('quizdeleteselected', false, PARAM_BOOL) &&
!empty($selectedquestionids) && confirm_sesskey()) {
foreach ($selectedquestionids as $questionid) {
if (quiz_has_question_use($questionid)) {
quiz_remove_question($quiz, $questionid);
!empty($selectedslots) && confirm_sesskey()) {
// Work backwards, since removing a question renumbers following slots.
foreach (array_reverse($selectedslots) as $slot) {
if (quiz_has_question_use($quiz, $slot)) {
quiz_remove_slot($quiz, $slot);
}
}
quiz_delete_previews($quiz);
@@ -304,8 +286,6 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
$deletepreviews = false;
$recomputesummarks = false;
$oldquestions = explode(',', $quiz->questions); // The questions in the old order.
$questions = array(); // For questions in the new order.
$rawdata = (array) data_submitted();
$moveonpagequestions = array();
$moveselectedonpage = optional_param('moveselectedonpagetop', 0, PARAM_INT);
@@ -313,73 +293,111 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
$moveselectedonpage = optional_param('moveselectedonpagebottom', 0, PARAM_INT);
}
$newslotorder = array();
foreach ($rawdata as $key => $value) {
if (preg_match('!^g([0-9]+)$!', $key, $matches)) {
// Parse input for question -> grades.
$questionid = $matches[1];
$quiz->grades[$questionid] = unformat_float($value);
quiz_update_question_instance($quiz->grades[$questionid], $questionid, $quiz);
$slotnumber = $matches[1];
$newgrade = unformat_float($value);
quiz_update_slot_maxmark($DB->get_record('quiz_slots',
array('quizid' => $quiz->id, 'slot' => $slotnumber), '*', MUST_EXIST), $newgrade);
$deletepreviews = true;
$recomputesummarks = true;
} else if (preg_match('!^o(pg)?([0-9]+)$!', $key, $matches)) {
// Parse input for ordering info.
$questionid = $matches[2];
$slotnumber = $matches[2];
// Make sure two questions don't overwrite each other. If we get a second
// question with the same position, shift the second one along to the next gap.
$value = clean_param($value, PARAM_INT);
while (array_key_exists($value, $questions)) {
while (array_key_exists($value, $newslotorder)) {
$value++;
}
if ($matches[1]) {
// This is a page-break entry.
$questions[$value] = 0;
$newslotorder[$value] = 0;
} else {
$questions[$value] = $questionid;
$newslotorder[$value] = $slotnumber;
}
$deletepreviews = true;
}
}
// If ordering info was given, reorder the questions.
if ($questions) {
ksort($questions);
$questions[] = 0;
$quiz->questions = implode(',', $questions);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
$deletepreviews = true;
}
// Get a list of questions to move, later to be added in the appropriate
// place in the string.
if ($moveselectedonpage) {
$questions = explode(',', $quiz->questions);
$newquestions = array();
// Remove the questions from their original positions first.
foreach ($questions as $questionid) {
if (!in_array($questionid, $selectedquestionids)) {
$newquestions[] = $questionid;
// Make up a $newslotorder, then let the next if statement do the work.
$oldslots = $DB->get_records('quiz_slots', array('quizid' => $quiz->id), 'slot');
$beforepage = array();
$onpage = array();
$afterpage = array();
foreach ($oldslots as $oldslot) {
if (in_array($oldslot->slot, $selectedslots)) {
$onpage[] = $oldslot;
} else if ($oldslot->page <= $moveselectedonpage) {
$beforepage[] = $oldslot;
} else {
$afterpage[] = $oldslot;
}
}
$questions = $newquestions;
// Move to the end of the selected page.
$pagebreakpositions = array_keys($questions, 0);
$numpages = count($pagebreakpositions);
// Ensure the target page number is in range.
for ($i = $moveselectedonpage; $i > $numpages; $i--) {
$questions[] = 0;
$pagebreakpositions[] = count($questions) - 1;
$newslotorder = array();
$currentpage = 1;
$index = 10;
foreach ($beforepage as $slot) {
while ($currentpage < $slot->page) {
$newslotorder[$index] = 0;
$index += 10;
$currentpage += 1;
}
$newslotorder[$index] = $slot->slot;
$index += 10;
}
$moveselectedpos = $pagebreakpositions[$moveselectedonpage - 1];
// Do the move.
array_splice($questions, $moveselectedpos, 0, $selectedquestionids);
$quiz->questions = implode(',', $questions);
while ($currentpage < $moveselectedonpage) {
$newslotorder[$index] = 0;
$index += 10;
$currentpage += 1;
}
foreach ($onpage as $slot) {
$newslotorder[$index] = $slot->slot;
$index += 10;
}
// Update the database.
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
foreach ($afterpage as $slot) {
while ($currentpage < $slot->page) {
$newslotorder[$index] = 0;
$index += 10;
$currentpage += 1;
}
$newslotorder[$index] = $slot->slot;
$index += 10;
}
}
// If ordering info was given, reorder the questions.
if ($newslotorder) {
ksort($newslotorder);
$currentpage = 1;
$currentslot = 1;
$slotreorder = array();
$slotpages = array();
foreach ($newslotorder as $slotnumber) {
if ($slotnumber == 0) {
$currentpage += 1;
continue;
}
$slotreorder[$slotnumber] = $currentslot;
$slotpages[$currentslot] = $currentpage;
$currentslot += 1;
}
$trans = $DB->start_delegated_transaction();
update_field_with_unique_index('quiz_slots',
'slot', $slotreorder, array('quizid' => $quiz->id));
foreach ($slotpages as $slotnumber => $page) {
$DB->set_field('quiz_slots', 'page', $page, array('quizid' => $quiz->id, 'slot' => $slotnumber));
}
$trans->allow_commit();
$deletepreviews = true;
}
@@ -421,7 +439,11 @@ echo $OUTPUT->header();
$quizeditconfig = new stdClass();
$quizeditconfig->url = $thispageurl->out(true, array('qbanktool' => '0'));
$quizeditconfig->dialoglisteners = array();
$numberoflisteners = max(quiz_number_of_pages($quiz->questions), 1);
$numberoflisteners = $DB->get_field_sql("
SELECT COALESCE(MAX(page), 1)
FROM {quiz_slots}
WHERE quizid = ?", array($quiz->id));
for ($pageiter = 1; $pageiter <= $numberoflisteners; $pageiter++) {
$quizeditconfig->dialoglisteners[] = 'addrandomdialoglaunch_' . $pageiter;
}
@@ -487,7 +509,6 @@ echo '<div class="quizcontents ' . $quizcontentsclass . '" id="quizcontentsblock
if ($quiz->shufflequestions) {
$repaginatingdisabledhtml = 'disabled="disabled"';
$repaginatingdisabled = true;
$quiz->questions = quiz_repaginate($quiz->questions, $quiz->questionsperpage);
} else {
$repaginatingdisabledhtml = '';
$repaginatingdisabled = false;
+214 -229
View File
@@ -48,12 +48,20 @@ function quiz_require_question_use($questionid) {
/**
* Verify that the question exists, and the user has permission to use it.
* @param int $questionid The id of the question.
* @param object $quiz the quiz settings.
* @param int $slot which question in the quiz to test.
* @return bool whether the user can use this question.
*/
function quiz_has_question_use($questionid) {
function quiz_has_question_use($quiz, $slot) {
global $DB;
$question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
$question = $DB->get_record_sql("
SELECT q.*
FROM {quiz_slots} slot
JOIN {question} q ON q.id = slot.questionid
WHERE slot.quizid = ? AND slot.slot = ?", array($quiz->id, $slot));
if (!$question) {
return false;
}
return question_has_capability_on($question, 'use');
}
@@ -62,118 +70,127 @@ function quiz_has_question_use($questionid) {
* @param object $quiz the quiz object.
* @param int $questionid The id of the question to be deleted.
*/
function quiz_remove_question($quiz, $questionid) {
function quiz_remove_slot($quiz, $slotnumber) {
global $DB;
$questionids = explode(',', $quiz->questions);
$key = array_search($questionid, $questionids);
if ($key === false) {
$slot = $DB->get_record('quiz_slots', array('quizid' => $quiz->id, 'slot' => $slotnumber));
$maxslot = $DB->get_field_sql('SELECT MAX(slot) FROM {quiz_slots} WHERE quizid = ?', array($quiz->id));
if (!$slot) {
return;
}
unset($questionids[$key]);
$quiz->questions = implode(',', $questionids);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
$DB->delete_records('quiz_question_instances',
array('quizid' => $quiz->instance, 'questionid' => $questionid));
$trans = $DB->start_delegated_transaction();
$DB->delete_records('quiz_slots', array('id' => $slot->id));
for ($i = $slot->slot + 1; $i <= $maxslot; $i++) {
$DB->set_field('quiz_slots', 'slot', $i - 1,
array('quizid' => $quiz->id, 'slot' => $i));
}
$trans->allow_commit();
}
/**
* Remove an empty page from the quiz layout. If that is not possible, do nothing.
* @param string $layout the existinng layout, $quiz->questions.
* @param int $index the position into $layout where the empty page should be removed.
* @return the updated layout
* @param object $quiz the quiz settings.
* @param int $pagenumber the page number to delete.
*/
function quiz_delete_empty_page($layout, $index) {
$questionids = explode(',', $layout);
function quiz_delete_empty_page($quiz, $pagenumber) {
global $DB;
if ($index < -1 || $index >= count($questionids) - 1) {
return $layout;
if ($DB->record_exists('quiz_slots', array('quizid' => $quiz->id, 'page' => $pagenumber))) {
// This was not an empty page.
return;
}
if (($index >= 0 && $questionids[$index] != 0) || $questionids[$index + 1] != 0) {
return $layout; // This was not an empty page.
}
unset($questionids[$index + 1]);
return implode(',', $questionids);
$DB->execute('UPDATE {quiz_slots} SET page = page - 1 WHERE quizid = ? AND page > ?',
array($quiz->id, $pagenumber));
}
/**
* Add a question to a quiz
*
* Adds a question to a quiz by updating $quiz as well as the
* quiz and quiz_question_instances tables. It also adds a page break
* if required.
* @param int $id The id of the question to be added
* quiz and quiz_slots tables. It also adds a page break if required.
* @param int $questionid The id of the question to be added
* @param object $quiz The extended quiz object as used by edit.php
* This is updated by this function
* @param int $page Which page in quiz to add the question on. If 0 (default),
* add at the end
* @param float $maxmark The maximum mark to set for this question. (Optional,
* defaults to question.defaultmark.
* @return bool false if the question was already in the quiz
*/
function quiz_add_quiz_question($id, $quiz, $page = 0) {
function quiz_add_quiz_question($questionid, $quiz, $page = 0, $maxmark = null) {
global $DB;
$questions = explode(',', quiz_clean_layout($quiz->questions));
if (in_array($id, $questions)) {
$slots = $DB->get_records('quiz_slots', array('quizid' => $quiz->id),
'slot', 'questionid, slot, page, id');
if (array_key_exists($questionid, $slots)) {
return false;
}
// Remove ending page break if it is not needed.
if ($breaks = array_keys($questions, 0)) {
// Determine location of the last two page breaks.
$end = end($breaks);
$last = prev($breaks);
$last = $last ? $last : -1;
if (!$quiz->questionsperpage || (($end - $last - 1) < $quiz->questionsperpage)) {
array_pop($questions);
}
}
if (is_int($page) && $page >= 1) {
$numofpages = quiz_number_of_pages($quiz->questions);
if ($numofpages<$page) {
// The page specified does not exist in quiz.
$page = 0;
} else {
// Add ending page break - the following logic requires doing
// this at this point.
$questions[] = 0;
$currentpage = 1;
$addnow = false;
foreach ($questions as $question) {
if ($question == 0) {
$currentpage++;
// The current page is the one after the one we want to add on,
// so we add the question before adding the current page.
if ($currentpage == $page + 1) {
$questions_new[] = $id;
}
}
$questions_new[] = $question;
}
$questions = $questions_new;
}
}
if ($page == 0) {
// Add question.
$questions[] = $id;
// Add ending page break.
$questions[] = 0;
}
$trans = $DB->start_delegated_transaction();
// Save new questionslist in database.
$quiz->questions = implode(',', $questions);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
$maxpage = 1;
$numonlastpage = 0;
foreach ($slots as $slot) {
if ($slot->page > $maxpage) {
$maxpage = $slot->page;
$numonlastpage = 1;
} else {
$numonlastpage += 1;
}
}
// Add the new question instance.
$instance = new stdClass();
$instance->quizid = $quiz->id;
$instance->questionid = $id;
$instance->maxmark = $DB->get_field('question', 'defaultmark', array('id' => $id));
$DB->insert_record('quiz_question_instances', $instance);
$slot = new stdClass();
$slot->quizid = $quiz->id;
$slot->questionid = $questionid;
if ($maxmark !== null) {
$slot->maxmark = $maxmark;
} else {
$slot->maxmark = $DB->get_field('question', 'defaultmark', array('id' => $questionid));
}
if (is_int($page) && $page >= 1) {
// Adding on a given page.
$lastslotbefore = 1;
foreach (array_reverse($slots) as $otherslot) {
if ($otherslot->page > $page) {
$DB->set_field('quiz_slots', 'slot', $otherslot->slot + 1, array('id' => $otherslot->id));
} else {
$lastslotbefore = $otherslot->slot;
break;
}
}
$slot->slot = $lastslotbefore + 1;
$slot->page = min($page, $maxpage + 1);
} else {
$lastslot = end($slots);
if ($lastslot) {
$slot->slot = $lastslot->slot + 1;
} else {
$slot->slot = 1;
}
if ($quiz->questionsperpage && $numonlastpage >= $quiz->questionsperpage) {
$slot->page = $maxpage + 1;
} else {
$slot->page = $maxpage;
}
}
$DB->insert_record('quiz_slots', $slot);
$trans->allow_commit();
}
/**
* Add a random question to the quiz at a given point.
* @param object $quiz the quiz settings.
* @param int $addonpage the page on which to add the question.
* @param int $categoryid the question category to add the question from.
* @param int $number the number of random questions to add.
* @param bool $includesubcategories whether to include questoins from subcategories.
*/
function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
$includesubcategories) {
global $DB;
@@ -195,7 +212,7 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
AND " . $DB->sql_compare_text('questiontext') . " = ?
AND NOT EXISTS (
SELECT *
FROM {quiz_question_instances}
FROM {quiz_slots}
WHERE questionid = q.id)
ORDER BY id", array($category->id, $includesubcategories))) {
// Take as many of these as needed.
@@ -228,136 +245,103 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
}
/**
* Add a page break after at particular position$.
* @param string $layout the existinng layout, $quiz->questions.
* @param int $index the position into $layout where the empty page should be removed.
* @return the updated layout
* Add a page break after a particular slot.
* @param object $quiz the quiz settings.
* @param int $slot the slot to add the page break after.
*/
function quiz_add_page_break_at($layout, $index) {
$questionids = explode(',', $layout);
if ($index < 0 || $index >= count($questionids)) {
return $layout;
}
array_splice($questionids, $index, 0, '0');
return implode(',', $questionids);
}
/**
* Add a page break after a particular question.
* @param string $layout the existinng layout, $quiz->questions.
* @param int $qustionid the question to add the page break after.
* @return the updated layout
*/
function quiz_add_page_break_after($layout, $questionid) {
$questionids = explode(',', $layout);
$key = array_search($questionid, $questionids);
if ($key === false || !$questionid) {
return $layout;
}
array_splice($questionids, $key + 1, 0, '0');
return implode(',', $questionids);
}
/**
* Update the database after $quiz->questions has been changed. For example,
* this deletes preview attempts and updates $quiz->sumgrades.
* @param $quiz the quiz object.
*/
function quiz_save_new_layout($quiz) {
function quiz_add_page_break_after_slot($quiz, $slot) {
global $DB;
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
quiz_delete_previews($quiz);
quiz_update_sumgrades($quiz);
$DB->execute('UPDATE {quiz_slots} SET page = page + 1 WHERE quizid = ? AND slot > ?',
array($quiz->id, $slot));
}
/**
* Save changes to question instance
* Change the max mark for a slot.
*
* Saves changes to the question grades in the quiz_question_instances table.
* Saves changes to the question grades in the quiz_slots table and any
* corresponding question_attempts.
* It does not update 'sumgrades' in the quiz table.
*
* @param float $maxmark the maximal grade for the question.
* @param int $questionid the question id.
* @param stdClass $quiz the quiz settings.
* @param stdClass $slot row from the quiz_slots table.
* @param float $maxmark the new maxmark.
*/
function quiz_update_question_instance($maxmark, $questionid, $quiz) {
function quiz_update_slot_maxmark($slot, $maxmark) {
global $DB;
$instance = $DB->get_record('quiz_question_instances', array('quizid' => $quiz->id,
'questionid' => $questionid));
$slot = quiz_get_slot_for_question($quiz, $questionid);
if (!$instance || !$slot) {
throw new coding_exception('Attempt to change the max mark of a quesion not in the quiz.');
}
if (abs($maxmark - $instance->maxmark) < 1e-7) {
if (abs($maxmark - $slot->maxmark) < 1e-7) {
// Grade has not changed. Nothing to do.
return;
}
$instance->maxmark = $maxmark;
$DB->update_record('quiz_question_instances', $instance);
question_engine::set_max_mark_in_attempts(new qubaids_for_quiz($quiz->id),
$slot, $maxmark);
}
// Private function used by the following two.
function _quiz_move_question($layout, $questionid, $shift) {
if (!$questionid || !($shift == 1 || $shift == -1)) {
return $layout;
}
$questionids = explode(',', $layout);
$key = array_search($questionid, $questionids);
if ($key === false) {
return $layout;
}
$otherkey = $key + $shift;
if ($otherkey < 0 || $otherkey >= count($questionids) - 1) {
return $layout;
}
$temp = $questionids[$otherkey];
$questionids[$otherkey] = $questionids[$key];
$questionids[$key] = $temp;
return implode(',', $questionids);
$slot->maxmark = $maxmark;
$DB->update_record('quiz_slots', $slot);
question_engine::set_max_mark_in_attempts(new qubaids_for_quiz($slot->quizid),
$slot->slot, $maxmark);
}
/**
* Move a particular question one space earlier in the $quiz->questions list.
* If that is not possible, do nothing.
* @param string $layout the existinng layout, $quiz->questions.
* @param int $questionid the id of a question.
* @return the updated layout
* Private function used by the following two.
* @param object $quiz the quiz settings.
* @param int $slotnumber the slot to move up.
* @param int $shift +1 means move down, -1 means move up.
*/
function quiz_move_question_up($layout, $questionid) {
return _quiz_move_question($layout, $questionid, -1);
function _quiz_move_question($quiz, $slotnumber, $shift) {
global $DB;
if (!$slotnumber || !($shift == 1 || $shift == -1)) {
return;
}
$slot = $DB->get_record('quiz_slots',
array('quizid' => $quiz->id, 'slot' => $slotnumber));
if (!$slot) {
return;
}
$otherslot = $DB->get_record('quiz_slots',
array('quizid' => $quiz->id, 'slot' => $slotnumber + $shift));
if (!$otherslot) {
return;
}
if ($otherslot->page != $slot->page) {
$DB->set_field('quiz_slots', 'page', $slot->page + $shift, array('id' => $slot->id));
return;
}
$trans = $DB->start_delegated_transaction();
$DB->set_field('quiz_slots', 'slot', -1, array('id' => $slot->id));
$DB->set_field('quiz_slots', 'slot', $slot->slot, array('id' => $otherslot->id));
$DB->set_field('quiz_slots', 'slot', $otherslot->slot, array('id' => $slot->id));
$trans->allow_commit();
}
/**
* Move a particular question one space later in the $quiz->questions list.
* Move a particular question one space earlier in the quiz.
* If that is not possible, do nothing.
* @param string $layout the existinng layout, $quiz->questions.
* @param int $questionid the id of a question.
* @return the updated layout
* @param object $quiz the quiz settings.
* @param int $slot the slot to move up.
*/
function quiz_move_question_down($layout, $questionid) {
return _quiz_move_question($layout, $questionid, +1);
function quiz_move_question_up($quiz, $slot) {
_quiz_move_question($quiz, $slot, -1);
}
/**
* Move a particular question one space later in the quiz.
* If that is not possible, do nothing.
* @param object $quiz the quiz settings.
* @param int $slot the slot to move down.
*/
function quiz_move_question_down($quiz, $slot) {
return _quiz_move_question($quiz, $slot, 1);
}
/**
* Prints a list of quiz questions for the edit.php main view for edit
* ($reordertool = false) and order and paging ($reordertool = true) tabs
*
* @param object $quiz This is not the standard quiz object used elsewhere but
* it contains the quiz layout in $quiz->questions and the grades in
* $quiz->grades
* @param object $quiz The quiz settings.
* @param moodle_url $pageurl The url of the current page with the parameters required
* for links returning to the current page, as a moodle_url object
* @param bool $allowdelete Indicates whether the delete icons should be displayed
@@ -389,21 +373,14 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
$strtype = get_string('type', 'quiz');
$strpreview = get_string('preview', 'quiz');
if ($quiz->questions) {
list($usql, $params) = $DB->get_in_or_equal(explode(',', $quiz->questions));
$params[] = $quiz->id;
$questions = $DB->get_records_sql("SELECT q.*, qc.contextid, qqi.maxmark
FROM {question} q
JOIN {question_categories} qc ON qc.id = q.category
JOIN {quiz_question_instances} qqi ON qqi.questionid = q.id
WHERE q.id $usql AND qqi.quizid = ?", $params);
} else {
$questions = array();
}
$questions = $DB->get_records_sql("SELECT slot.slot, q.*, qc.contextid, slot.page, slot.maxmark
FROM {quiz_slots} slot
LEFT JOIN {question} q ON q.id = slot.questionid
LEFT JOIN {question_categories} qc ON qc.id = q.category
WHERE slot.quizid = ?
ORDER BY slot.slot", array($quiz->id));
$layout = quiz_clean_layout($quiz->questions);
$order = explode(',', $layout);
$lastindex = count($order) - 1;
$lastindex = count($questions) - 1;
$disabled = '';
$pagingdisabled = '';
@@ -468,6 +445,18 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
echo $reordercontrolstop;
}
// Build fake order for backwards compatibility.
$currentpage = 1;
$order = array();
foreach ($questions as $question) {
while ($question->page > $currentpage) {
$currentpage += 1;
$order[] = 0;
}
$order[] = $question->slot;
}
$order[] = 0;
// The current question ordinal (no descriptions).
$qno = 1;
// The current question (includes questions and descriptions).
@@ -476,28 +465,26 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
$pagecount = 0;
$pageopen = false;
$lastslot = 0;
$returnurl = $pageurl->out_as_local_url(false);
$questiontotalcount = count($order);
foreach ($order as $count => $qnum) {
foreach ($order as $count => $qnum) { // Note: $qnum is acutally slot number, if it is not 0.
$reordercheckbox = '';
$reordercheckboxlabel = '';
$reordercheckboxlabelclose = '';
// If the questiontype is missing change the question type.
if ($qnum && !array_key_exists($qnum, $questions)) {
$fakequestion = new stdClass();
$fakequestion->id = $qnum;
$fakequestion->category = 0;
$fakequestion->qtype = 'missingtype';
$fakequestion->name = get_string('missingquestion', 'quiz');
$fakequestion->questiontext = ' ';
$fakequestion->questiontextformat = FORMAT_HTML;
$fakequestion->length = 1;
$questions[$qnum] = $fakequestion;
$quiz->grades[$qnum] = 0;
if ($qnum && $questions[$qnum]->qtype === null) {
$questions[$qnum]->id = $qnum;
$questions[$qnum]->category = 0;
$questions[$qnum]->qtype = 'missingtype';
$questions[$qnum]->name = get_string('missingquestion', 'quiz');
$questions[$qnum]->questiontext = ' ';
$questions[$qnum]->questiontextformat = FORMAT_HTML;
$questions[$qnum]->length = 1;
} else if ($qnum && !question_bank::qtype_exists($questions[$qnum]->qtype)) {
$questions[$qnum]->qtype = 'missingtype';
@@ -521,7 +508,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
if ($allowdelete) {
echo '<div class="quizpagedelete">';
echo $OUTPUT->action_icon($pageurl->out(true,
array('deleteemptypage' => $count - 1, 'sesskey'=>sesskey())),
array('deleteemptypage' => $pagecount, 'sesskey' => sesskey())),
new pix_icon('t/delete', $strremove),
new component_action('click',
'M.core_scroll_manager.save_scroll_action'),
@@ -550,9 +537,9 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
$reordercheckboxlabel = '';
$reordercheckboxlabelclose = '';
if ($reordertool) {
$reordercheckbox = '<input type="checkbox" name="s' . $question->id .
'" id="s' . $question->id . '" />';
$reordercheckboxlabel = '<label for="s' . $question->id . '">';
$reordercheckbox = '<input type="checkbox" name="s' . $question->slot .
'" id="s' . $question->slot . '" />';
$reordercheckboxlabel = '<label for="s' . $question->slot . '">';
$reordercheckboxlabelclose = '</label>';
}
if ($question->length == 0) {
@@ -582,7 +569,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
$upbuttonclass = 'upwithoutdown';
}
echo $OUTPUT->action_icon($pageurl->out(true,
array('up' => $question->id, 'sesskey'=>sesskey())),
array('up' => $question->slot, 'sesskey' => sesskey())),
new pix_icon('t/up', $strmoveup),
new component_action('click',
'M.core_scroll_manager.save_scroll_action'),
@@ -590,10 +577,10 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
}
}
if ($count < $lastindex - 1) {
if ($count < $questiontotalcount - 2) {
if (!$hasattempts) {
echo $OUTPUT->action_icon($pageurl->out(true,
array('down' => $question->id, 'sesskey'=>sesskey())),
array('down' => $question->slot, 'sesskey' => sesskey())),
new pix_icon('t/down', $strmovedown),
new component_action('click',
'M.core_scroll_manager.save_scroll_action'),
@@ -605,7 +592,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
// Remove from quiz, not question delete.
if (!$hasattempts) {
echo $OUTPUT->action_icon($pageurl->out(true,
array('remove' => $question->id, 'sesskey'=>sesskey())),
array('remove' => $question->slot, 'sesskey' => sesskey())),
new pix_icon('t/delete', $strremove),
new component_action('click',
'M.core_scroll_manager.save_scroll_action'),
@@ -619,15 +606,15 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
<div class="points">
<form method="post" action="edit.php" class="quizsavegradesform"><div>
<fieldset class="invisiblefieldset" style="display: block;">
<label for="<?php echo "inputq$question->id" ?>"><?php echo $strmaxmark; ?></label>:<br />
<label for="<?php echo 'inputq' . $question->slot; ?>"><?php echo $strmaxmark; ?></label>:<br />
<input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
<?php echo html_writer::input_hidden_params($pageurl); ?>
<input type="hidden" name="savechanges" value="save" />
<?php
echo '<input type="text" name="g' . $question->id .
'" id="inputq' . $question->id .
echo '<input type="text" name="g' . $question->slot .
'" id="inputq' . $question->slot .
'" size="' . ($quiz->decimalpoints + 2) .
'" value="' . (0 + $quiz->grades[$qnum]) .
'" value="' . (0 + $question->maxmark) .
'" tabindex="' . ($lastindex + $qno) . '" />';
?>
<input type="submit" class="pointssubmitbutton" value="<?php echo $strsave; ?>" />
@@ -650,9 +637,9 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
?>
<div class="qorder">
<?php
echo '<label class="accesshide" for="o' . $question->id . '">' .
echo '<label class="accesshide" for="o' . $question->slot . '">' .
get_string('questionposition', 'quiz', $qnodisplay) . '</label>';
echo '<input type="text" name="o' . $question->id .
echo '<input type="text" name="o' . $question->slot .
'" id="o' . $question->id . '"' .
'" size="2" value="' . (10*$count + 10) .
'" tabindex="' . ($lastindex + $qno) . '" />';
@@ -705,7 +692,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
echo $OUTPUT->container_start('addpage');
$url = new moodle_url($pageurl->out_omit_querystring(),
array('cmid' => $quiz->cmid, 'courseid' => $quiz->course,
'addpage' => $count, 'sesskey' => sesskey()));
'addpage' => $pagecount, 'sesskey' => sesskey()));
echo $OUTPUT->single_button($url, get_string('addpagehere', 'quiz'), 'post',
array('disabled' => $hasattempts,
'actions' => array(new component_action('click',
@@ -728,9 +715,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
* Print all the controls for adding questions directly into the
* specific page in the edit tab of edit.php
*
* @param object $quiz This is not the standard quiz object used elsewhere but
* it contains the quiz layout in $quiz->questions and the grades in
* $quiz->grades
* @param object $quiz The quiz settings.
* @param moodle_url $pageurl The url of the current page with the parameters required
* for links returning to the current page, as a moodle_url object
* @param int $page the current page number.
@@ -934,7 +919,7 @@ function quiz_print_singlequestion_reordertool($question, $returnurl, $quiz) {
* @param object $questionurl The url of the question editing page as a moodle_url object
* @param object $quiz The quiz in the context of which the question is being displayed
*/
function quiz_print_randomquestion_reordertool(&$question, &$pageurl, &$quiz) {
function quiz_print_randomquestion_reordertool($question, $pageurl, $quiz) {
global $DB, $OUTPUT;
// Load the category, and the number of available questions in it.
@@ -1311,7 +1296,7 @@ function quiz_print_grading_form($quiz, $pageurl, $tabindex) {
* @param object $quiz The quiz object of the quiz in question
*/
function quiz_print_status_bar($quiz) {
global $CFG;
global $DB;
$bits = array();
@@ -1320,7 +1305,7 @@ function quiz_print_status_bar($quiz) {
array('class' => 'totalpoints'));
$bits[] = html_writer::tag('span',
get_string('numquestionsx', 'quiz', quiz_number_of_questions_in_quiz($quiz->questions)),
get_string('numquestionsx', 'quiz', $DB->count_records('quiz_slots', array('quizid' => $quiz->id))),
array('class' => 'numberofquestions'));
$timenow = time();
+7 -10
View File
@@ -81,7 +81,6 @@ function quiz_add_instance($quiz) {
// Process the options from the form.
$quiz->created = time();
$quiz->questions = '';
$result = quiz_process_options($quiz);
if ($result && is_string($result)) {
return $result;
@@ -122,13 +121,6 @@ function quiz_update_instance($quiz, $mform) {
$quiz->sumgrades = $oldquiz->sumgrades;
$quiz->grade = $oldquiz->grade;
// Repaginate, if asked to.
if (!$quiz->shufflequestions && !empty($quiz->repaginatenow)) {
$quiz->questions = quiz_repaginate(quiz_clean_layout($oldquiz->questions, true),
$quiz->questionsperpage);
}
unset($quiz->repaginatenow);
// Update the database.
$quiz->id = $quiz->instance;
$DB->update_record('quiz', $quiz);
@@ -151,6 +143,11 @@ function quiz_update_instance($quiz, $mform) {
// Delete any previous preview attempts.
quiz_delete_previews($quiz);
// Repaginate, if asked to.
if (!$quiz->shufflequestions && !empty($quiz->repaginatenow)) {
quiz_repaginate_questions($quiz->id, $quiz->questionsperpage);
}
return true;
}
@@ -170,7 +167,7 @@ function quiz_delete_instance($id) {
quiz_delete_all_attempts($quiz);
quiz_delete_all_overrides($quiz);
$DB->delete_records('quiz_question_instances', array('quizid' => $quiz->id));
$DB->delete_records('quiz_slots', array('quizid' => $quiz->id));
$DB->delete_records('quiz_feedback', array('quizid' => $quiz->id));
$events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id));
@@ -1288,7 +1285,7 @@ function quiz_questions_in_use($questionids) {
global $DB, $CFG;
require_once($CFG->libdir . '/questionlib.php');
list($test, $params) = $DB->get_in_or_equal($questionids);
return $DB->record_exists_select('quiz_question_instances',
return $DB->record_exists_select('quiz_slots',
'questionid ' . $test, $params) || question_engine::questions_in_use(
$questionids, new qubaid_join('{quiz_attempts} quiza',
'quiza.uniqueid', 'quiza.preview = 0'));
+51 -194
View File
@@ -110,10 +110,7 @@ function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timen
$attempt->quiz = $quiz->id;
$attempt->userid = $userid;
$attempt->preview = 0;
$attempt->layout = quiz_clean_layout($quiz->questions, true);
if ($quiz->shufflequestions) {
$attempt->layout = quiz_repaginate($attempt->layout, $quiz->questionsperpage, true);
}
$attempt->layout = '';
} else {
// Build on last attempt.
if (empty($lastattempt)) {
@@ -165,9 +162,8 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
$quizobj->load_questions();
// Add them all to the $quba.
$idstoslots = array();
$questionsinuse = array_keys($quizobj->get_questions());
foreach ($quizobj->get_questions() as $i => $questiondata) {
foreach ($quizobj->get_questions() as $questiondata) {
if ($questiondata->qtype != 'random') {
if (!$quizobj->get_quiz()->shuffleanswers) {
$questiondata->options->shuffleanswers = false;
@@ -189,7 +185,7 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
}
}
$idstoslots[$i] = $quba->add_question($question, $questiondata->maxmark);
$quba->add_question($question, $questiondata->maxmark);
$questionsinuse[] = $question->id;
}
@@ -211,16 +207,36 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
$quba->start_all_questions($variantstrategy, $timenow);
// Update attempt layout.
$newlayout = array();
foreach (explode(',', $attempt->layout) as $qid) {
if ($qid != 0) {
$newlayout[] = $idstoslots[$qid];
} else {
$newlayout[] = 0;
// Work out the attempt layout.
$layout = array();
if ($quizobj->get_quiz()->shufflequestions) {
$slots = $quba->get_slots();
shuffle($slots);
$questionsonthispage = 0;
foreach ($slots as $slot) {
if ($questionsonthispage && $questionsonthispage == $quizobj->get_quiz()->questionsperpage) {
$layout[] = 0;
$questionsonthispage = 0;
}
$layout[] = $slot;
$questionsonthispage += 1;
}
} else {
$currentpage = null;
foreach ($quizobj->get_questions() as $slot) {
if ($currentpage !== null && $slot->page != $currentpage) {
$layout[] = 0;
}
$layout[] = $slot->slot;
$currentpage = $slot->page;
}
}
$attempt->layout = implode(',', $newlayout);
$layout[] = 0;
$attempt->layout = implode(',', $layout);
return $attempt;
}
@@ -386,127 +402,35 @@ function quiz_has_attempts($quizid) {
// Functions to do with quiz layout and pages //////////////////////////////////
/**
* Returns a comma separated list of question ids for the quiz
*
* @param string $layout The string representing the quiz layout. Each page is
* represented as a comma separated list of question ids and 0 indicating
* page breaks. So 5,2,0,3,0 means questions 5 and 2 on page 1 and question
* 3 on page 2
* @return string comma separated list of question ids, without page breaks.
* Repaginate the questions in a quiz
* @param int $quizid the id of the quiz to repaginate.
* @param int $slotsperpage number of items to put on each page. 0 means unlimited.
*/
function quiz_questions_in_quiz($layout) {
$questions = str_replace(',0', '', quiz_clean_layout($layout, true));
if ($questions === '0') {
return '';
} else {
return $questions;
}
}
function quiz_repaginate_questions($quizid, $slotsperpage) {
global $DB;
$trans = $DB->start_delegated_transaction();
/**
* Returns the number of pages in a quiz layout
*
* @param string $layout The string representing the quiz layout. Always ends in ,0
* @return int The number of pages in the quiz.
*/
function quiz_number_of_pages($layout) {
return substr_count(',' . $layout, ',0');
}
$slots = $DB->get_records('quiz_slots', array('quizid' => $quizid),
'slot');
/**
* Returns the number of questions in the quiz layout
*
* @param string $layout the string representing the quiz layout.
* @return int The number of questions in the quiz.
*/
function quiz_number_of_questions_in_quiz($layout) {
$layout = quiz_questions_in_quiz(quiz_clean_layout($layout));
$count = substr_count($layout, ',');
if ($layout !== '') {
$count++;
}
return $count;
}
/**
* Re-paginates the quiz layout
*
* @param string $layout The string representing the quiz layout. If there is
* if there is any doubt about the quality of the input data, call
* quiz_clean_layout before you call this function.
* @param int $perpage The number of questions per page
* @param bool $shuffle Should the questions be reordered randomly?
* @return string the new layout string
*/
function quiz_repaginate($layout, $perpage, $shuffle = false) {
$questions = quiz_questions_in_quiz($layout);
if (!$questions) {
return '0';
}
$questions = explode(',', quiz_questions_in_quiz($layout));
if ($shuffle) {
shuffle($questions);
}
$onthispage = 0;
$layout = array();
foreach ($questions as $question) {
if ($perpage and $onthispage >= $perpage) {
$layout[] = 0;
$onthispage = 0;
$currentpage = 1;
$slotsonthispage = 0;
foreach ($slots as $slot) {
if ($slotsonthispage && $slotsonthispage == $slotsperpage) {
$currentpage += 1;
$slotsonthispage = 0;
}
$layout[] = $question;
$onthispage += 1;
if ($slot->page != $currentpage) {
$DB->set_field('quiz_slots', 'page', $currentpage, array('id' => $slot->id));
}
$slotsonthispage += 1;
}
$layout[] = 0;
return implode(',', $layout);
$trans->allow_commit();
}
// Functions to do with quiz grades ////////////////////////////////////////////
/**
* Creates an array of maximum grades for a quiz
*
* The grades are extracted from the quiz_question_instances table.
* @param object $quiz The quiz settings.
* @return array of grades indexed by question id. These are the maximum
* possible grades that students can achieve for each of the questions.
*/
function quiz_get_all_question_grades($quiz) {
global $CFG, $DB;
$questionlist = quiz_questions_in_quiz($quiz->questions);
if (empty($questionlist)) {
return array();
}
$params = array($quiz->id);
$wheresql = '';
if (!is_null($questionlist)) {
list($usql, $question_params) = $DB->get_in_or_equal(explode(',', $questionlist));
$wheresql = ' AND questionid ' . $usql;
$params = array_merge($params, $question_params);
}
$instances = $DB->get_records_sql("SELECT questionid, maxmark, id
FROM {quiz_question_instances}
WHERE quizid = ?{$wheresql}", $params);
$list = explode(',', $questionlist);
$grades = array();
foreach ($list as $qid) {
if (isset($instances[$qid])) {
$grades[$qid] = $instances[$qid]->maxmark;
} else {
$grades[$qid] = 1;
}
}
return $grades;
}
/**
* Convert the raw grade stored in $attempt into a grade out of the maximum
* grade for this quiz.
@@ -602,7 +526,7 @@ function quiz_update_sumgrades($quiz) {
$sql = 'UPDATE {quiz}
SET sumgrades = COALESCE((
SELECT SUM(maxmark)
FROM {quiz_question_instances}
FROM {quiz_slots}
WHERE quizid = {quiz}.id
), 0)
WHERE id = ?';
@@ -1425,73 +1349,6 @@ function quiz_get_combined_reviewoptions($quiz, $attempts) {
return array($someoptions, $alloptions);
}
/**
* Clean the question layout from various possible anomalies:
* - Remove consecutive ","'s
* - Remove duplicate question id's
* - Remove extra "," from beginning and end
* - Finally, add a ",0" in the end if there is none
*
* @param $string $layout the quiz layout to clean up, usually from $quiz->questions.
* @param bool $removeemptypages If true, remove empty pages from the quiz. False by default.
* @return $string the cleaned-up layout
*/
function quiz_clean_layout($layout, $removeemptypages = false) {
// Remove repeated ','s. This can happen when a restore fails to find the right
// id to relink to.
$layout = preg_replace('/,{2,}/', ',', trim($layout, ','));
// Remove duplicate question ids.
$layout = explode(',', $layout);
$cleanerlayout = array();
$seen = array();
foreach ($layout as $item) {
if ($item == 0) {
$cleanerlayout[] = '0';
} else if (!in_array($item, $seen)) {
$cleanerlayout[] = $item;
$seen[] = $item;
}
}
if ($removeemptypages) {
// Avoid duplicate page breaks.
$layout = $cleanerlayout;
$cleanerlayout = array();
$stripfollowingbreaks = true; // Ensure breaks are stripped from the start.
foreach ($layout as $item) {
if ($stripfollowingbreaks && $item == 0) {
continue;
}
$cleanerlayout[] = $item;
$stripfollowingbreaks = $item == 0;
}
}
// Add a page break at the end if there is none.
if (end($cleanerlayout) !== '0') {
$cleanerlayout[] = '0';
}
return implode(',', $cleanerlayout);
}
/**
* Get the slot for a question with a particular id.
* @param object $quiz the quiz settings.
* @param int $questionid the of a question in the quiz.
* @return int the corresponding slot. Null if the question is not in the quiz.
*/
function quiz_get_slot_for_question($quiz, $questionid) {
$questionids = quiz_questions_in_quiz($quiz->questions);
foreach (explode(',', $questionids) as $key => $id) {
if ($id == $questionid) {
return $key + 1;
}
}
return null;
}
// Functions for sending notification messages /////////////////////////////////
/**
+3 -3
View File
@@ -123,9 +123,9 @@ class quiz_grading_report extends quiz_default_report {
$this->currentgroup, '', false);
}
$questionsinquiz = quiz_questions_in_quiz($quiz->questions);
$hasquestions = quiz_has_questions($quiz->id);
$counts = null;
if ($slot && $questionsinquiz) {
if ($slot && $hasquestions) {
// Make sure there is something to do.
$statecounts = $this->get_question_state_summary(array($slot));
foreach ($statecounts as $record) {
@@ -144,7 +144,7 @@ class quiz_grading_report extends quiz_default_report {
$this->print_header_and_tabs($cm, $course, $quiz, 'grading');
// What sort of page to display?
if (!$questionsinquiz) {
if (!$hasquestions) {
echo quiz_no_questions_message($quiz, $cm, $this->context);
} else if (!$slot) {
+1 -1
View File
@@ -102,7 +102,7 @@ class quiz_overview_report extends quiz_attempts_report {
}
}
$hasquestions = quiz_questions_in_quiz($quiz->questions);
$hasquestions = quiz_has_questions($quiz->id);
if (!$table->is_downloading()) {
if (!$hasquestions) {
echo quiz_no_questions_message($quiz, $cm, $this->context);
+20 -29
View File
@@ -77,6 +77,15 @@ function quiz_report_unindex($datum) {
return $datumunkeyed;
}
/**
* Are there any questions in this quiz?
* @param int $quizid the quiz id.
*/
function quiz_has_questions($quizid) {
global $DB;
return $DB->record_exists('quiz_slots', array('quizid' => $quizid));
}
/**
* Get the slots of real questions (not descriptions) in this quiz, in order.
* @param object $quiz the quiz.
@@ -86,41 +95,23 @@ function quiz_report_unindex($datum) {
function quiz_report_get_significant_questions($quiz) {
global $DB;
$questionids = quiz_questions_in_quiz($quiz->questions);
if (empty($questionids)) {
return array();
}
$qsbyslot = $DB->get_records_sql("
SELECT slot.slot,
q.id,
q.length,
slot.maxmark
list($usql, $params) = $DB->get_in_or_equal(explode(',', $questionids));
$params[] = $quiz->id;
$questions = $DB->get_records_sql("
SELECT
q.id,
q.length,
qqi.maxmark
FROM {question} q
JOIN {quiz_slots} slot ON slot.questionid = q.id
FROM {question} q
JOIN {quiz_question_instances} qqi ON qqi.questionid = q.id
WHERE slot.quizid = ?
AND q.length > 0
WHERE
q.id $usql AND
qqi.quizid = ? AND
q.length > 0", $params);
ORDER BY slot.slot", array($quiz->id));
$qsbyslot = array();
$number = 1;
foreach (explode(',', $questionids) as $key => $id) {
if (!array_key_exists($id, $questions)) {
continue;
}
$slot = $key + 1;
$question = $questions[$id];
$question->slot = $slot;
foreach ($qsbyslot as $question) {
$question->number = $number;
$qsbyslot[$slot] = $question;
$number += $question->length;
}
+1 -1
View File
@@ -115,7 +115,7 @@ class quiz_responses_report extends quiz_attempts_report {
}
}
$hasquestions = quiz_questions_in_quiz($quiz->questions);
$hasquestions = quiz_has_questions($quiz->id);
if (!$table->is_downloading()) {
if (!$hasquestions) {
echo quiz_no_questions_message($quiz, $cm, $this->context);
+1 -1
View File
@@ -59,7 +59,7 @@ class quiz_statistics_report extends quiz_default_report {
$this->context = context_module::instance($cm->id);
if (!quiz_questions_in_quiz($quiz->questions)) {
if (!quiz_has_questions($quiz->id)) {
$this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
echo quiz_no_questions_message($quiz, $cm, $this->context);
return true;
@@ -113,10 +113,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
$this->randqids = array();
foreach ($slots as $slotno => $slotquestion) {
if ($slotquestion['type'] !== 'random') {
quiz_add_quiz_question($slotquestion['id'], $this->quiz, 0);
// Setting default mark above does not affect the grade for multi-answer question type (and maybe others??).
// Set the mark again just to be sure.
quiz_update_question_instance($slotquestion['mark'], $slotquestion['id'], $this->quiz);
quiz_add_quiz_question($slotquestion['id'], $this->quiz, 0, $slotquestion['mark']);
} else {
quiz_add_random_questions($this->quiz, 0, $slotquestion['catid'], 1, 0);
$this->randqids[$slotno] = $qidsbycat[$slotquestion['catid']];
+5 -1
View File
@@ -79,6 +79,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user1->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
$this->assertEquals('1,2,0', $attempt->layout);
quiz_attempt_save_started($quizobj, $quba, $attempt);
@@ -132,7 +133,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
// Make a quiz.
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance(array('course'=>$SITE->id, 'questionsperpage' => 0, 'grade' => 100.0,
$quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 2, 'grade' => 100.0,
'sumgrades' => 4));
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
@@ -174,6 +175,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow, array(1 => $randomqidtoselect));
$this->assertEquals('1,2,0,3,4,0', $attempt->layout);
quiz_attempt_save_started($quizobj, $quba, $attempt);
@@ -266,8 +268,10 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
$timenow = time();
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow);
// Select variant.
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow, array(), array(1 => $variantno));
$this->assertEquals('1,0', $attempt->layout);
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Process some responses from the student.
-46
View File
@@ -37,52 +37,6 @@ require_once($CFG->dirroot . '/mod/quiz/editlib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_editlib_testcase extends basic_testcase {
public function test_quiz_move_question_up() {
$this->assertEquals(quiz_move_question_up('0', 123), '0');
$this->assertEquals(quiz_move_question_up('1,2,0', 1), '1,2,0');
$this->assertEquals(quiz_move_question_up('1,2,0', 0), '1,2,0');
$this->assertEquals(quiz_move_question_up('1,2,0', 2), '2,1,0');
$this->assertEquals(quiz_move_question_up('1,2,0,3,4,0', 3), '1,2,3,0,4,0');
$this->assertEquals(quiz_move_question_up('1,2,3,0,4,0', 4), '1,2,3,4,0,0');
}
public function test_quiz_move_question_down() {
$this->assertEquals(quiz_move_question_down('0', 123), '0');
$this->assertEquals(quiz_move_question_down('1,2,0', 2), '1,2,0');
$this->assertEquals(quiz_move_question_down('1,2,0', 0), '1,2,0');
$this->assertEquals(quiz_move_question_down('1,2,0', 1), '2,1,0');
$this->assertEquals(quiz_move_question_down('1,2,0,3,4,0', 2), '1,0,2,3,4,0');
$this->assertEquals(quiz_move_question_down('1,0,2,3,0,4,0', 1), '0,1,2,3,0,4,0');
}
public function test_quiz_delete_empty_page() {
$this->assertEquals(quiz_delete_empty_page('0', 0), '0');
$this->assertEquals(quiz_delete_empty_page('1,2,0', 2), '1,2,0');
$this->assertEquals(quiz_delete_empty_page('0,1,2,0', -1), '1,2,0');
$this->assertEquals(quiz_delete_empty_page('0,1,2,0', 0), '0,1,2,0');
$this->assertEquals(quiz_delete_empty_page('1,2,0', 3), '1,2,0');
$this->assertEquals(quiz_delete_empty_page('1,2,0', -1), '1,2,0');
$this->assertEquals(quiz_delete_empty_page('1,2,0,0', 2), '1,2,0');
$this->assertEquals(quiz_delete_empty_page('1,2,0,0', 1), '1,2,0,0');
$this->assertEquals(quiz_delete_empty_page('1,2,0,0,3,4,0', 2), '1,2,0,3,4,0');
$this->assertEquals(quiz_delete_empty_page('0,0,1,2,0', 0), '0,1,2,0');
}
public function test_quiz_add_page_break_after() {
$this->assertEquals(quiz_add_page_break_after('0', 1), '0');
$this->assertEquals(quiz_add_page_break_after('1,2,0', 1), '1,0,2,0');
$this->assertEquals(quiz_add_page_break_after('1,2,0', 2), '1,2,0,0');
$this->assertEquals(quiz_add_page_break_after('1,2,0', 0), '1,2,0');
}
public function test_quiz_add_page_break_at() {
$this->assertEquals(quiz_add_page_break_at('0', 0), '0,0');
$this->assertEquals(quiz_add_page_break_at('1,2,0', 0), '0,1,2,0');
$this->assertEquals(quiz_add_page_break_at('1,2,0', 1), '1,0,2,0');
$this->assertEquals(quiz_add_page_break_at('1,2,0', 2), '1,2,0,0');
$this->assertEquals(quiz_add_page_break_at('1,2,0', 3), '1,2,0');
}
public function test_quiz_question_tostring() {
$question = new stdClass();
$question->qtype = 'multichoice';
-1
View File
@@ -52,7 +52,6 @@ class mod_quiz_generator extends testing_module_generator {
'questionsperpage' => 1,
'shufflequestions' => 0,
'shuffleanswers' => 1,
'questions' => '',
'sumgrades' => 0,
'grade' => 0,
'timecreated' => time(),
-97
View File
@@ -37,96 +37,6 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_locallib_testcase extends basic_testcase {
public function test_quiz_questions_in_quiz() {
$this->assertEquals(quiz_questions_in_quiz(''), '');
$this->assertEquals(quiz_questions_in_quiz('0'), '');
$this->assertEquals(quiz_questions_in_quiz('0,0'), '');
$this->assertEquals(quiz_questions_in_quiz('0,0,0'), '');
$this->assertEquals(quiz_questions_in_quiz('1'), '1');
$this->assertEquals(quiz_questions_in_quiz('1,2'), '1,2');
$this->assertEquals(quiz_questions_in_quiz('1,0,2'), '1,2');
$this->assertEquals(quiz_questions_in_quiz('0,1,0,0,2,0'), '1,2');
}
public function test_quiz_number_of_pages() {
$this->assertEquals(quiz_number_of_pages('0'), 1);
$this->assertEquals(quiz_number_of_pages('0,0'), 2);
$this->assertEquals(quiz_number_of_pages('0,0,0'), 3);
$this->assertEquals(quiz_number_of_pages('1,0'), 1);
$this->assertEquals(quiz_number_of_pages('1,2,0'), 1);
$this->assertEquals(quiz_number_of_pages('1,0,2,0'), 2);
$this->assertEquals(quiz_number_of_pages('1,2,3,0'), 1);
$this->assertEquals(quiz_number_of_pages('1,2,3,0'), 1);
$this->assertEquals(quiz_number_of_pages('0,1,0,0,2,0'), 4);
}
public function test_quiz_number_of_questions_in_quiz() {
$this->assertEquals(quiz_number_of_questions_in_quiz('0'), 0);
$this->assertEquals(quiz_number_of_questions_in_quiz('0,0'), 0);
$this->assertEquals(quiz_number_of_questions_in_quiz('0,0,0'), 0);
$this->assertEquals(quiz_number_of_questions_in_quiz('1,0'), 1);
$this->assertEquals(quiz_number_of_questions_in_quiz('1,2,0'), 2);
$this->assertEquals(quiz_number_of_questions_in_quiz('1,0,2,0'), 2);
$this->assertEquals(quiz_number_of_questions_in_quiz('1,2,3,0'), 3);
$this->assertEquals(quiz_number_of_questions_in_quiz('1,2,3,0'), 3);
$this->assertEquals(quiz_number_of_questions_in_quiz('0,1,0,0,2,0'), 2);
$this->assertEquals(quiz_number_of_questions_in_quiz('10,,0,0'), 1);
}
public function test_quiz_clean_layout() {
// Without stripping empty pages.
$this->assertEquals(quiz_clean_layout(',,1,,,2,,'), '1,2,0');
$this->assertEquals(quiz_clean_layout(''), '0');
$this->assertEquals(quiz_clean_layout('0'), '0');
$this->assertEquals(quiz_clean_layout('0,0'), '0,0');
$this->assertEquals(quiz_clean_layout('0,0,0'), '0,0,0');
$this->assertEquals(quiz_clean_layout('1'), '1,0');
$this->assertEquals(quiz_clean_layout('1,2'), '1,2,0');
$this->assertEquals(quiz_clean_layout('1,0,2'), '1,0,2,0');
$this->assertEquals(quiz_clean_layout('0,1,0,0,2,0'), '0,1,0,0,2,0');
// With stripping empty pages.
$this->assertEquals(quiz_clean_layout('', true), '0');
$this->assertEquals(quiz_clean_layout('0', true), '0');
$this->assertEquals(quiz_clean_layout('0,0', true), '0');
$this->assertEquals(quiz_clean_layout('0,0,0', true), '0');
$this->assertEquals(quiz_clean_layout('1', true), '1,0');
$this->assertEquals(quiz_clean_layout('1,2', true), '1,2,0');
$this->assertEquals(quiz_clean_layout('1,0,2', true), '1,0,2,0');
$this->assertEquals(quiz_clean_layout('0,1,0,0,2,0', true), '1,0,2,0');
}
public function test_quiz_repaginate() {
// Test starting with 1 question per page.
$this->assertEquals(quiz_repaginate('1,0,2,0,3,0', 0), '1,2,3,0');
$this->assertEquals(quiz_repaginate('1,0,2,0,3,0', 3), '1,2,3,0');
$this->assertEquals(quiz_repaginate('1,0,2,0,3,0', 2), '1,2,0,3,0');
$this->assertEquals(quiz_repaginate('1,0,2,0,3,0', 1), '1,0,2,0,3,0');
// Test starting with all on one page page.
$this->assertEquals(quiz_repaginate('1,2,3,0', 0), '1,2,3,0');
$this->assertEquals(quiz_repaginate('1,2,3,0', 3), '1,2,3,0');
$this->assertEquals(quiz_repaginate('1,2,3,0', 2), '1,2,0,3,0');
$this->assertEquals(quiz_repaginate('1,2,3,0', 1), '1,0,2,0,3,0');
// Test single question case.
$this->assertEquals(quiz_repaginate('100,0', 0), '100,0');
$this->assertEquals(quiz_repaginate('100,0', 1), '100,0');
// No questions case.
$this->assertEquals(quiz_repaginate('0', 0), '0');
// Test empty pages are removed.
$this->assertEquals(quiz_repaginate('1,2,3,0,0,0', 0), '1,2,3,0');
$this->assertEquals(quiz_repaginate('1,0,0,0,2,3,0', 0), '1,2,3,0');
$this->assertEquals(quiz_repaginate('0,0,0,1,2,3,0', 0), '1,2,3,0');
// Test shuffle option.
$this->assertTrue(in_array(quiz_repaginate('1,2,0', 0, true),
array('1,2,0', '2,1,0')));
$this->assertTrue(in_array(quiz_repaginate('1,2,0', 1, true),
array('1,0,2,0', '2,0,1,0')));
}
public function test_quiz_rescale_grade() {
$quiz = new stdClass();
@@ -145,13 +55,6 @@ class mod_quiz_locallib_testcase extends basic_testcase {
format_float(0.247, 3));
}
public function test_quiz_get_slot_for_question() {
$quiz = new stdClass();
$quiz->questions = '1,2,0,7,0';
$this->assertEquals(1, quiz_get_slot_for_question($quiz, 1));
$this->assertEquals(3, quiz_get_slot_for_question($quiz, 7));
}
public function test_quiz_attempt_state_in_progress() {
$attempt = new stdClass();
$attempt->state = quiz_attempt::IN_PROGRESS;
-16
View File
@@ -42,7 +42,6 @@ class mod_quiz_class_testcase extends basic_testcase {
$quiz->reviewattempt = 0x10010;
$quiz->timeclose = 0;
$quiz->attempts = 0;
$quiz->questions = '1,2,0,3,4,0';
$cm = new stdClass();
$cm->id = 123;
@@ -65,19 +64,4 @@ class mod_quiz_class_testcase extends basic_testcase {
$this->assertEquals(get_string('noreviewuntil', 'quiz', userdate($closetime)),
$quizobj->cannot_review_message(mod_quiz_display_options::LATER_WHILE_OPEN));
}
public function test_empty_quiz() {
$quiz = new stdClass();
$quiz->reviewattempt = 0x10010;
$quiz->timeclose = 0;
$quiz->attempts = 0;
$quiz->questions = '0';
$cm = new stdClass();
$cm->id = 123;
$quizobj = new quiz($quiz, $cm, new stdClass(), false);
$this->assertFalse($quizobj->has_questions());
}
}
+24 -2
View File
@@ -2,14 +2,36 @@ This files describes API changes in the quiz code.
=== 2.7 ===
* The columns of the quiz_question_instances table have been renamed to match
the coding guidelines. Specifically
* The old quiz.questions database column (comma-separated list of question ids)
is gone, and instead the quiz_question_instances table has been renamed to
to quiz_slots. Some of the columns of that table have been renamed to match
the coding guidelines. Specifically:
quiz -> quizid
question -> questionid
grade -> maxmark
also there are two new columns:
slot - numbers the questions in the quiz in order, as on the edit quiz page.
page - new way to determine which question is on which page.
naturally, other parts of the code and APIs have been updated to reflect that
change.
* The following functions, which were part of the internal workings of the quiz,
have been removed.
quiz_get_slot_for_question
quiz_number_of_questions_in_quiz
quiz_repaginate (there is now a quiz_repaginate_questions with a different API).
quiz_add_page_break_at (see quiz_add_page_break_after_slot)
quiz_add_page_break_after (see quiz_add_page_break_after_slot)
quiz_number_of_pages
quiz_remove_question (see quiz_remove_slot)
quiz_update_question_instance (see quiz_update_slot_maxmark)
* The following internal functions have had their API changed.
quiz_delete_empty_page: has had its arguments changed to $quiz and $pagenumber.
quiz_has_question_use: now takes $quiz and $slot, not $questionid.
=== 2.6 ===
* As part of improving the page usability and accessibility, we updated the
heading levels for quiz module so it has a proper nesting. (MDL-41615)
+1 -1
View File
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2014021300; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2014022008; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2013110500; // Requires this Moodle version.
$plugin->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics).
$plugin->cron = 60;
+1 -1
View File
@@ -184,7 +184,7 @@ if ($quiz->attempts != 1) {
}
// Determine wheter a start attempt button should be displayed.
$viewobj->quizhasquestions = (bool) quiz_clean_layout($quiz->questions, true);
$viewobj->quizhasquestions = $quizobj->has_questions();
$viewobj->preventmessages = array();
if (!$viewobj->quizhasquestions) {
$viewobj->buttontext = '';
+3 -3
View File
@@ -286,10 +286,10 @@ class question_engine_upgrade_question_loader {
if ($quizid) {
$question = $DB->get_record_sql("
SELECT q.*, qqi.maxmark
SELECT q.*, slot.maxmark
FROM {question} q
JOIN {quiz_question_instances} qqi ON qqi.questionid = q.id
WHERE q.id = ? AND qqi.quizid = ?", array($questionid, $quizid));
JOIN {quiz_slots} slot ON slot.questionid = q.id
WHERE q.id = ? AND slot.quizid = ?", array($questionid, $quizid));
} else {
$question = $DB->get_record('question', array('id' => $questionid));
}
+4 -4
View File
@@ -1706,12 +1706,12 @@ class qtype_calculated extends question_type {
$line++;
$text .= "<td align=\"left\" style=\"white-space:nowrap;\">$qu->name</td>";
// TODO MDL-43779 should not have quiz-specific code here.
$nbofquiz = $DB->count_records('quiz_question_instances', array('questionid' => $qu->id));
$nbofquiz = $DB->count_records('quiz_slots', array('questionid' => $qu->id));
$nbofattempts = $DB->count_records_sql("
SELECT count(1)
FROM {quiz_question_instances} qqi
JOIN {quiz_attempts} quiza ON quiza.quiz = qqi.quizid
WHERE qqi.questionid = ?
FROM {quiz_slots} slot
JOIN {quiz_attempts} quiza ON quiza.quiz = slot.quizid
WHERE slot.questionid = ?
AND quiza.preview = 0", array($qu->id));
if ($nbofquiz > 0) {
$text .= "<td align=\"center\">$nbofquiz</td>";
@@ -55,7 +55,6 @@ class qtype_calculated_attempt_upgrader_test extends question_attempt_upgrader_t
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -254,7 +253,6 @@ Remember to type a unit.',
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -496,7 +494,6 @@ Remember to type a unit.',
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -55,7 +55,6 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -276,7 +275,6 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -517,7 +515,6 @@ class qtype_calculatedmulti_attempt_upgrader_test extends question_attempt_upgra
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -55,7 +55,6 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -241,7 +240,6 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -467,7 +465,6 @@ class qtype_calculatedsimple_attempt_upgrader_test extends question_attempt_upgr
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '16,0,17,0,18,0',
'sumgrades' => '3.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -60,7 +60,6 @@ class qtype_description_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '4940,0,5043,0,4945,0,4942,0,5566,0',
'sumgrades' => '5',
'grade' => '10',
'timecreated' => '0',
@@ -206,7 +205,6 @@ class qtype_description_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '8492,0,8487,8488,8489,8490,0,8441,0,8443,0,8486,0,8444,0,8445,0,8494,0,8446,8429,0,8447,8430,0,8448,8431,0,8449,8432,0,8450,8433,0,8451,8434,0,8452,8435,0,8453,8436,0,8454,8437,0,8493,0,8455,8456,8457,0,8458,8459,8460,0,8461,8462,8463,8438,0,8464,8465,8466,0,8467,8468,8469,0,8470,8471,8472,8439,0,8473,8440,0,8474,8475,8476,0,8477,8478,8479,0,8480,8481,8482,0,8483,8484,8485,8442,0',
'sumgrades' => '0',
'grade' => '10',
'timecreated' => '0',
@@ -355,7 +353,6 @@ class qtype_description_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '8691,0,8690,8692,8693,8694,0,8695,0,8696,0,8697,0,8698,0,8699,0,8700,0,8701,8702,0,8703,8704,0,8705,8706,0,8707,8708,0,8709,8710,0,8711,8712,0,8713,8714,0,8715,8716,0,8717,8718,0,8719,0,8720,8721,8722,0,8723,8724,8725,0,8726,8727,8728,8729,0,8730,8731,8732,0,8733,8734,8735,0,8736,8737,8738,8739,0,8740,8741,0,8742,8743,8744,0,8745,8746,8747,0,8748,8749,8750,0,8752,8751,8753,8754,0',
'sumgrades' => '0',
'grade' => '10',
'timecreated' => '0',
@@ -64,7 +64,6 @@ class qtype_essay_attempt_upgrader_test extends question_attempt_upgrader_test_b
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '90042,0,90043,0,90045,0,90052,0,90053,0,90054,0,90055,0,90056,0,90057,0,90058,0,90059,0,90046,0,90044,0,90047,0,90048,0,90049,0',
'sumgrades' => '100',
'grade' => '100',
'timecreated' => '0',
@@ -295,7 +294,6 @@ class qtype_essay_attempt_upgrader_test extends question_attempt_upgrader_test_b
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '3664,3716,0,3663,3717,0,3718,3719,0,3720,0,3733,3727,0,3728,3730,0,3731,3732,0,3726,3729,0',
'sumgrades' => '0',
'grade' => '0',
'timecreated' => '0',
@@ -475,7 +473,6 @@ class qtype_essay_attempt_upgrader_test extends question_attempt_upgrader_test_b
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '3664,3716,0,3663,3717,0,3718,3719,0,3720,0,3733,3727,0,3728,3730,0,3731,3732,0,3726,3729,0',
'sumgrades' => '0',
'grade' => '0',
'timecreated' => '0',
@@ -59,7 +59,6 @@ class qtype_match_attempt_upgrader_test extends question_attempt_upgrader_test_b
'questionsperpage' => '2',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '689,690,0,691,692,0,693,694,0,695,696,0,697,698,0',
'sumgrades' => '48',
'grade' => '48',
'timecreated' => '0',
@@ -317,7 +316,6 @@ class qtype_match_attempt_upgrader_test extends question_attempt_upgrader_test_b
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '509,0,510,0,511,0,738,0,514,0',
'sumgrades' => '5',
'grade' => '10',
'timecreated' => '0',
@@ -527,7 +525,6 @@ class qtype_match_attempt_upgrader_test extends question_attempt_upgrader_test_b
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '11163,0,11164,0,11165,0,11135,0,11166,0',
'sumgrades' => '5',
'grade' => '10',
'timecreated' => '0',
@@ -65,13 +65,13 @@ class qtype_multianswer_edit_form extends question_edit_form {
if (isset($question->id) && $question->id != 0) {
// TODO MDL-43779 should not have quiz-specific code here.
$this->savedquestiondisplay = fullclone($question);
$this->nb_of_quiz = $DB->count_records('quiz_question_instances', array('questionid' => $question->id));
$this->nb_of_quiz = $DB->count_records('quiz_slots', array('questionid' => $question->id));
$this->used_in_quiz = $this->nb_of_quiz > 0;
$this->nb_of_attempts = $DB->count_records_sql("
SELECT count(1)
FROM {quiz_question_instances} qqi
JOIN {quiz_attempts} quiza ON quiza.quiz = qqi.quizid
WHERE qqi.questionid = ?
FROM {quiz_slots} slot
JOIN {quiz_attempts} quiza ON quiza.quiz = slot.quizid
WHERE slot.questionid = ?
AND quiza.preview = 0", array($question->id));
}
@@ -56,7 +56,6 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '28,19,0',
'sumgrades' => '14.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -260,7 +259,6 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '28,19,0',
'sumgrades' => '14.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -443,7 +441,6 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '28,19,0',
'sumgrades' => '14.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -668,7 +665,6 @@ class qtype_multianswer_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '28,19,0',
'sumgrades' => '14.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -1354,7 +1350,6 @@ b) What grade would you give it? _____',
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '28,19,0',
'sumgrades' => '14.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -2012,7 +2007,6 @@ b) What grade would you give it? _____',
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '28,19,0',
'sumgrades' => '14.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -59,7 +59,6 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '0',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '1,0',
'timecreated' => '0',
'timemodified' => '1278603396',
'timelimit' => '0',
@@ -296,7 +295,6 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '0',
'shufflequestions' => '1',
'shuffleanswers' => '1',
'questions' => '71,72,73,89,74,75,76,92,77,78,79,93,80,90,81,91,94,95,82,83,84,85,86,87,88,0',
'sumgrades' => '25',
'grade' => '25',
'timecreated' => '0',
@@ -464,7 +462,6 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '0',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '218,221,219,220,223,224,222,0',
'sumgrades' => '7',
'grade' => '0',
'timecreated' => '0',
@@ -659,7 +656,6 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '2',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '2855,2856,0,2857,2858,0,2859,2860,0,2861,2862,0,2863,2864,0,2865,0',
'sumgrades' => '59',
'grade' => '59',
'timecreated' => '0',
@@ -898,7 +894,6 @@ class qtype_multichoice_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '0',
'shufflequestions' => '1',
'shuffleanswers' => '1',
'questions' => '71,72,73,89,74,75,76,92,77,78,79,93,80,90,81,91,94,95,82,83,84,85,86,87,88,0',
'sumgrades' => '25',
'grade' => '25',
'timecreated' => '0',
@@ -1032,7 +1027,6 @@ public function test_multichoice_deferredfeedback_qsession140() {
'questionsperpage' => '2',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '178,179,0,180,181,0,182,183,0,184,185,0,189,187,0',
'sumgrades' => '10',
'grade' => '10',
'timecreated' => '0',
@@ -1236,7 +1230,6 @@ public function test_multichoice_deferredfeedback_qsession140() {
'questionsperpage' => '2',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '26132,26128,0,26143,26140,0,26144,26141,0,26145,26142,0,26126,26127,0,26134,26135,0,26131,26133,0,26130,26129,0,26136,26137,0,26139,26138,0',
'sumgrades' => '0',
'grade' => '0',
'timecreated' => '0',
@@ -1712,7 +1705,6 @@ public function test_multichoice_deferredfeedback_qsession140() {
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '161,0,160,0,162,0,163,0,164,0,165,0,166,0,190,0,191,0,192,0,193,0,194,0,195,0,196,0,197,0,198,0,168,0,169,0,170,0,171,0,172,0,173,0,174,0,175,0,176,0,177,0,178,0,179,0,180,0,181,0',
'sumgrades' => '30',
'grade' => '10',
'timecreated' => '0',
@@ -1950,7 +1942,6 @@ public function test_multichoice_deferredfeedback_qsession140() {
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '89002,0,89040,0,89042,0,89043,0,89044,0,89045,0,89046,0,89047,0',
'sumgrades' => '8',
'grade' => '8',
'timecreated' => '0',
@@ -2154,7 +2145,6 @@ public function test_multichoice_deferredfeedback_qsession140() {
'questionsperpage' => '2',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '3859,3860,0,3861,3862,0,3863,3864,0,3865,3866,0,3867,3868,0',
'sumgrades' => '50',
'grade' => '50',
'timecreated' => '0',
@@ -2376,7 +2366,6 @@ public function test_multichoice_deferredfeedback_qsession140() {
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '242,0,243,0,244,0,245,0,246,0,247,0',
'sumgrades' => '0',
'grade' => '0',
'timecreated' => '0',
@@ -2585,7 +2574,6 @@ public function test_multichoice_deferredfeedback_qsession140() {
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '242,0,243,0,244,0,245,0,246,0,247,0',
'sumgrades' => '0',
'grade' => '0',
'timecreated' => '0',
@@ -60,7 +60,6 @@ class qtype_numerical_attempt_upgrader_test extends question_attempt_upgrader_te
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '4184,0,4185,0,4154,0,4186,0,4187,0,4188,0,4189,0,4190,0,4162,0,4191,0,4192,0,4193,0,4254,0,4195,0,4196,0,4163,0,4197,0,4198,0,4199,0,4164,0,4200,0,4165,0,4201,0,4202,0,4166,0,4203,0,4204,0,4205,0,4167,0,4155,0,4168,0,4206,0,4207,0,4208,0,4209,0,4210,0,4211,0,4212,0,4213,0,4214,0,4156,0,4215,0,4216,0,4217,0,4169,0,4170,0,4157,0,4218,0,4219,0,4220,0,4171,0,4221,0,4172,0,4222,0,4223,0,4224,0,4225,0,4226,0,4227,0,4228,0,4173,0,4229,0,4230,0,4231,0,4232,0,4174,0,4233,0,4234,0,4235,0,4236,0,4237,0,4238,0,4239,0,4240,0,4241,0,4242,0,4158,0,4243,0,4244,0,4245,0,4246,0,4159,0,4175,0,4247,0,4176,0,4248,0,4177,0,4160,0,4249,0,4178,0,4250,0,4161,0,4251,0,4179,0,4252,0,4180,0,4181,0,4182,0,4253,0,4183,0',
'sumgrades' => '100',
'grade' => '100',
'timecreated' => '0',
@@ -283,7 +282,6 @@ class qtype_numerical_attempt_upgrader_test extends question_attempt_upgrader_te
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '6,12,13,15,14,0',
'sumgrades' => '5.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -62,7 +62,6 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '28698,0,34245,0,34248,0,35005,0,35009,0,35013,0',
'sumgrades' => '5',
'grade' => '100',
'timecreated' => '0',
@@ -289,7 +288,6 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'questionsperpage' => '3',
'shufflequestions' => '1',
'shuffleanswers' => '1',
'questions' => '101983,101984,101985,0,101986,101987,101988,0,101989,101990,101991,0,101992,101993,101994,0,101995,101996,101997,0',
'sumgrades' => '15',
'grade' => '10',
'timecreated' => '0',
@@ -538,7 +536,6 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '68646,0,81245,0,81246,0,81247,0,81248,0,81249,0,81250,0,82795,0,82797,0,82798,0,82799,0,82800,0,82801,0,82802,0,82803,0,82804,0,82805,0,82806,0,82807,0',
'sumgrades' => '18',
'grade' => '18',
'timecreated' => '0',
@@ -805,7 +802,6 @@ class qtype_random_attempt_upgrader_test extends question_attempt_upgrader_test_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '6,12,13,15,14,0',
'sumgrades' => '5.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -60,7 +60,6 @@ class qtype_shortanswer_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '4184,0,4185,0,4154,0,4186,0,4187,0,4188,0,4189,0,4190,0,4162,0,4191,0,4192,0,4193,0,4254,0,4195,0,4196,0,4163,0,4197,0,4198,0,4199,0,4164,0,4200,0,4165,0,4201,0,4202,0,4166,0,4203,0,4204,0,4205,0,4167,0,4155,0,4168,0,4206,0,4207,0,4208,0,4209,0,4210,0,4211,0,4212,0,4213,0,4214,0,4156,0,4215,0,4216,0,4217,0,4169,0,4170,0,4157,0,4218,0,4219,0,4220,0,4171,0,4221,0,4172,0,4222,0,4223,0,4224,0,4225,0,4226,0,4227,0,4228,0,4173,0,4229,0,4230,0,4231,0,4232,0,4174,0,4233,0,4234,0,4235,0,4236,0,4237,0,4238,0,4239,0,4240,0,4241,0,4242,0,4158,0,4243,0,4244,0,4245,0,4246,0,4159,0,4175,0,4247,0,4176,0,4248,0,4177,0,4160,0,4249,0,4178,0,4250,0,4161,0,4251,0,4179,0,4252,0,4180,0,4181,0,4182,0,4253,0,4183,0',
'sumgrades' => '100',
'grade' => '100',
'timecreated' => '0',
@@ -271,7 +270,6 @@ class qtype_shortanswer_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '0',
'shufflequestions' => '1',
'shuffleanswers' => '1',
'questions' => '10218,10216,10219,10220,10217,0',
'sumgrades' => '5',
'grade' => '5',
'timecreated' => '0',
@@ -444,7 +442,6 @@ class qtype_shortanswer_attempt_upgrader_test extends question_attempt_upgrader_
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '4184,0,4185,0,4154,0,4186,0,4187,0,4188,0,4189,0,4190,0,4162,0,4191,0,4192,0,4193,0,4254,0,4195,0,4196,0,4163,0,4197,0,4198,0,4199,0,4164,0,4200,0,4165,0,4201,0,4202,0,4166,0,4203,0,4204,0,4205,0,4167,0,4155,0,4168,0,4206,0,4207,0,4208,0,4209,0,4210,0,4211,0,4212,0,4213,0,4214,0,4156,0,4215,0,4216,0,4217,0,4169,0,4170,0,4157,0,4218,0,4219,0,4220,0,4171,0,4221,0,4172,0,4222,0,4223,0,4224,0,4225,0,4226,0,4227,0,4228,0,4173,0,4229,0,4230,0,4231,0,4232,0,4174,0,4233,0,4234,0,4235,0,4236,0,4237,0,4238,0,4239,0,4240,0,4241,0,4242,0,4158,0,4243,0,4244,0,4245,0,4246,0,4159,0,4175,0,4247,0,4176,0,4248,0,4177,0,4160,0,4249,0,4178,0,4250,0,4161,0,4251,0,4179,0,4252,0,4180,0,4181,0,4182,0,4253,0,4183,0',
'sumgrades' => '100',
'grade' => '100',
'timecreated' => '0',
@@ -60,7 +60,6 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'showblocks' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '0',
'questions' => '3859,3860,0,3861,3862,0,3863,3864,0,3865,3866,0,3867,3868,0',
'sumgrades' => '50',
'grade' => '50',
'timecreated' => '0',
@@ -250,7 +249,6 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '9043,0,9057,0,9062,0,9241,0',
'sumgrades' => '4',
'grade' => '4',
'timecreated' => '0',
@@ -429,7 +427,6 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'questionsperpage' => '0',
'shufflequestions' => '1',
'shuffleanswers' => '1',
'questions' => '96,98,104,106,101,108,111,102,113,0',
'sumgrades' => '9',
'grade' => '9',
'timecreated' => '0',
@@ -603,7 +600,6 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '30,0',
'sumgrades' => '10.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -794,7 +790,6 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '30,0',
'sumgrades' => '10.00000',
'grade' => '10.00000',
'timecreated' => '0',
@@ -991,7 +986,6 @@ class qtype_truefalse_attempt_upgrader_test extends question_attempt_upgrader_te
'questionsperpage' => '1',
'shufflequestions' => '0',
'shuffleanswers' => '1',
'questions' => '1,0',
'sumgrades' => '1.00000',
'grade' => '10.00000',
'timecreated' => '0',