$string['answersaved'] = 'Answer saved';
$string['attemptfinished'] = 'Attempt finished';
$string['attemptfinishedsubmitting'] = 'Attempt finished submitting: ';
-$string['behaviourbeingused'] = 'behaviour being used: $a';
+$string['behaviourbeingused'] = 'behaviour being used: {$a}';
$string['cannotloadquestion'] = 'Could not load question';
$string['cannotpreview'] = 'You can\'t preview these questions!';
$string['category'] = 'Category';
$string['comment'] = 'Comment';
$string['commentormark'] = 'Make comment or override mark';
$string['comments'] = 'Comments';
-$string['commentx'] = 'Comment: $a';
+$string['commentx'] = 'Comment: {$a}';
$string['complete'] = 'Complete';
$string['contexterror'] = 'You shouldn\'t have got here if you\'re not moving a category to another context.';
$string['correct'] = 'Correct';
$string['hintn'] = 'Hint {no}';
$string['hinttext'] = 'Hint text';
$string['howquestionsbehave'] = 'How questions behave';
+$string['howquestionsbehave_help'] = 'Students can interact with the questions in the quiz in various different ways. For example, you may wish the students to enter an answer to each question and then submit the entire quiz, before anything is graded or they get any feedback. That would be \'Deferred feedback\' mode. Alternatively, you may wish for students to submit each question as they go along to get immediate feedback, and if they do not get it right immediately, have another try for fewer marks. That would be \'Interactive with multiple tries\' mode.';
$string['importfromcoursefiles'] = '... or choose a course file to import.';
$string['importfromupload'] = 'Select a file to upload ...';
$string['incorrect'] = 'Incorrect';
$string['manuallygraded'] = 'Manually graded {$a->mark} with comment: {$a->comment}';
$string['mark'] = 'Mark';
$string['markedoutof'] = 'Marked out of';
-$string['markedoutofmax'] = 'Marked out of $a';
-$string['markoutofmax'] = 'Mark $a->mark out of $a->max';
+$string['markedoutofmax'] = 'Marked out of {$a}';
+$string['markoutofmax'] = 'Mark {$a->mark} out of {$a->max}';
$string['marks'] = 'Marks';
$string['noresponse'] = '[No response]';
$string['notanswered'] = 'Not answered';
$string['penaltyforeachincorrecttry_help'] = 'When you run your questions using the \'Interactive with multiple tries\' or \'Adaptive mode\' behaviour, so that the the student will have several tries to get the question right, then this option controls how much they are penalised for each incorrect try.
The penalty is a proportion of the total question grade, so if the question is worth three marks, and the penalty is 0.3333333, then the student will score 3 if they get the question right first time, 2 if they get it right second try, and 1 of they get it right on the third try.';
-$string['previewquestion'] = 'Preview question: $a';
+$string['previewquestion'] = 'Preview question: {$a}';
$string['questionbehaviouradminsetting'] = 'Question behaviour settings';
$string['questionbehavioursdisabled'] = 'Question behaviours to disable';
$string['questionbehavioursdisabledexplained'] = 'Enter a comma separated list of behaviours you do not want to appear in dropdown menu';
$string['questionbehavioursorderexplained'] = 'Enter a comma separated list of behaviours in the order you want them to appear in dropdown menu';
$string['questionidmismatch'] = 'Question ids mismatch';
$string['questionname'] = 'Question name';
-$string['questionx'] = 'Question $a';
+$string['questionx'] = 'Question {$a}';
$string['questiontext'] = 'Question text';
$string['requiresgrading'] = 'Requires grading';
$string['responsehistory'] = 'Response history';
$string['submit'] = 'Submit';
$string['submitandfinish'] = 'Submit and finish';
$string['submitted'] = 'Submit: {$a}';
-$string['unknownquestion'] = 'Unknown question: $a.';
-$string['unknownquestioncatregory'] = 'Unknown question category: $a.';
+$string['unknownquestion'] = 'Unknown question: {$a}.';
+$string['unknownquestioncatregory'] = 'Unknown question category: {$a}.';
$string['whethercorrect'] = 'Whether correct';
-$string['xoutofmax'] = '$a->mark out of $a->max';
+$string['xoutofmax'] = '{$a->mark} out of {$a->max}';
$string['yougotnright'] = 'You have correctly selected {$a->num}.';
* @param boolean $loadtags load the question tags from the tags table. Optional, default false.
* @return boolean true if successful, else false.
*/
-function _tidy_question(&$question, $loadtags = false) {
+function _tidy_question($question, $loadtags = false) {
global $CFG, $QTYPES;
if (!array_key_exists($question->qtype, $QTYPES)) {
$question->qtype = 'missingtype';
global $DB;
$dbman = $DB->get_manager();
+ // TODO quiz default settings are now in config_plugins.
+
// Bit of a hack to prevent errors like "Cannot downgrade local_qedatabase from ... to ...".
$oldversion = 2008000000;
$DB->set_field('config_plugins', 'value', $oldversion,
* @return qbehaviour_renderer get the appropriate renderer to use for this model.
*/
public function get_renderer() {
- return renderer_factory::get_renderer(get_class($this));
+ global $PAGE;
+ return $PAGE->get_renderer(get_class($this));
}
/**
* @return question_definition loaded from the database.
*/
public static function load_question($questionid) {
+ global $DB;
+
if (self::$testmode) {
// Evil, test code in production, but now way round it.
return self::return_test_question_data($questionid);
}
- $questiondata = get_record('question', 'id', $questionid);
- if (empty($questiondata)) {
- throw new Exception('Unknown question id ' . $questionid);
- }
+ $questiondata = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
get_question_options($questiondata);
return self::make_question($questiondata);
}
if ($extraconditions) {
$extraconditions = ' AND (' . $extraconditions . ')';
}
- $questionids = get_records_select_menu('question',
+ // TODO switch to using $DB->in_or_equal.
+ $questionids = $DB->get_records_select_menu('question',
"category IN ($categoryids)
AND parent = 0
AND hidden = 0
public function __construct($db = null) {
if (is_null($db)) {
global $DB;
- new moodle_database;
$this->db = $DB;
} else {
$this->db = $db;
ORDER BY
qa.slot,
qas.sequencenumber
- ", array('qubaid', $qubaid));
+ ", array('qubaid' => $qubaid));
if (!$records) {
throw new Exception('Failed to load questions_usage_by_activity ' . $qubaid);
* @return array of records. See the SQL in this function to see the fields available.
*/
public function load_questions_usages_latest_steps(qubaid_condition $qubaids, $slots) {
- list($slottest, $params) = get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');
+ list($slottest, $params) = $this->db->get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');
$records = get_records_sql("
SELECT
$record->component = addslashes($quba->get_owning_component());
$record->preferredbehaviour = addslashes($quba->get_preferred_behaviour());
- if (!update_record('question_usages', $record)) {
+ if (!$this->db->update_record('question_usages', $record)) {
throw new Exception('Failed to update question_usage_by_activity ' . $record->id);
}
}
* @param question_attempt $qa the question attempt that has changed.
*/
public function update_question_attempt(question_attempt $qa) {
+ global $DB;
+
$record = new stdClass;
$record->id = $qa->get_database_id();
$record->maxmark = $qa->get_max_mark();
$record->responsesummary = addslashes($qa->get_response_summary());
$record->timemodified = time();
- if (!update_record('question_attempts', $record)) {
+ if (!$this->db->update_record('question_attempts', $record)) {
throw new Exception('Failed to update question_attempt ' . $record->id);
}
}
* database.
* @param string $where a where clause. Becuase of MySQL limitations, you
* must refer to {$CFG->prefix}question_usages.id in full like that.
+ * @param array $params values to substitute for placeholders in $where.
*/
- public function delete_questions_usage_by_activities($where) {
+ public function delete_questions_usage_by_activities($where, $params) {
global $CFG;
- delete_records_select('question_attempt_step_data', "attemptstepid IN (
+ $this->db->delete_records_select('question_attempt_step_data', "attemptstepid IN (
SELECT qas.id
- FROM {$CFG->prefix}question_attempts qa
- JOIN {$CFG->prefix}question_attempt_steps qas ON qas.questionattemptid = qa.id
- JOIN {$CFG->prefix}question_usages ON qa.questionusageid = {$CFG->prefix}question_usages.id
- WHERE $where)");
- delete_records_select('question_attempt_steps', "questionattemptid IN (
+ FROM {question_attempts} qa
+ JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
+ JOIN {question_usages} ON qa.questionusageid = {question_usages}.id
+ WHERE $where)", $params);
+ $this->db->delete_records_select('question_attempt_steps', "questionattemptid IN (
SELECT qa.id
- FROM {$CFG->prefix}question_attempts qa
- JOIN {$CFG->prefix}question_usages ON qa.questionusageid = {$CFG->prefix}question_usages.id
- WHERE $where)");
- delete_records_select('question_attempts', "questionusageid IN (
+ FROM {question_attempts} qa
+ JOIN {question_usages} ON qa.questionusageid = {question_usages}.id
+ WHERE $where)", $params);
+ $this->db->delete_records_select('question_attempts', "questionusageid IN (
SELECT id
- FROM {$CFG->prefix}question_usages
- WHERE $where)");
- delete_records_select('question_usages', $where);
+ FROM {question_usages}
+ WHERE $where)", $params);
+ $this->db->delete_records_select('question_usages', $where, $params);
}
/**
return;
}
list($test, $params) = get_in_or_equal($qaids);
- delete_records_select('question_attempt_step_data', "attemptstepid IN (
+ $this->db->delete_records_select('question_attempt_step_data', "attemptstepid IN (
SELECT qas.id
- FROM {$CFG->prefix}question_attempt_steps qas
- WHERE questionattemptid $test)");
- delete_records_select('question_attempt_steps', 'questionattemptid ' . $test);
+ FROM {question_attempt_steps} qas
+ WHERE questionattemptid $test)", $params);
+ $this->db->delete_records_select('question_attempt_steps', 'questionattemptid ' . $test, $params);
}
/**
*/
public function delete_previews($questionid) {
global $CFG;
- $previews = get_records_sql_menu("
+ $previews = $this->db->get_records_sql_menu("
SELECT DISTINCT quba.id, 1
- FROM {$CFG->prefix}question_usages quba
- JOIN {$CFG->prefix}question_attempts qa ON qa.questionusageid = quba.id
+ FROM {question_usages} quba
+ JOIN {question_attempts} qa ON qa.questionusageid = quba.id
WHERE quba.component = 'core_question_preview' AND
- qa.questionid = '$questionid'");
+ qa.questionid = ?", array($questionid));
if (empty($previews)) {
return;
}
- $this->delete_questions_usage_by_activities(
- "{$CFG->prefix}question_usages.id IN (" .
- implode(',', array_keys($previews)) . ')');
+ list($test, $params) = $this->db->get_in_or_equal(array_keys($previews));
+ $this->delete_questions_usage_by_activities('question_usages.id ' . $test, $params);
}
/**
*/
public function in_summary_state_test($summarystate, $equal = true) {
$states = question_state::get_all_for_summary_state($summarystate);
- list($sql, $params) = get_in_or_equal($states, SQL_PARAMS_QM, 'param0000', $equal);
+ list($sql, $params) = $this->db->get_in_or_equal($states, SQL_PARAMS_QM, 'param0000', $equal);
+ // TODO return $params;
return $sql;
}
* @param number $newmaxmark the new max mark to set.
*/
public function set_max_mark_in_attempts(qubaid_condition $qubaids, $slot, $newmaxmark) {
- set_field_select('question_attempts', 'maxmark', $newmaxmark,
- "questionusageid {$qubaids->usage_id_in()} AND slot = $slot");
+ $this->db->set_field_select('question_attempts', 'maxmark', $newmaxmark,
+ "questionusageid {$qubaids->usage_id_in()} AND slot = :slot",
+ $qubaids->usage_id_in_params() + array('slot' => $slot));
}
/**
* @return string SQL code for the subquery.
*/
public function sum_usage_marks_subquery($qubaid) {
- global $CFG;
return "SELECT SUM(qa.maxmark * qas.fraction)
- FROM {$CFG->prefix}question_attempts qa
+ FROM {question_attempts} qa
JOIN (
SELECT summarks_qa.id AS questionattemptid, MAX(summarks_qas.id) AS latestid
- FROM {$CFG->prefix}question_attempt_steps summarks_qas
- JOIN {$CFG->prefix}question_attempts summarks_qa ON summarks_qa.id = summarks_qas.questionattemptid
+ FROM {question_attempt_steps} summarks_qas
+ JOIN {question_attempts} summarks_qa ON summarks_qa.id = summarks_qas.questionattemptid
WHERE summarks_qa.questionusageid = $qubaid
GROUP BY summarks_qa.id
) lateststepid ON lateststepid.questionattemptid = qa.id
- JOIN {$CFG->prefix}question_attempt_steps qas ON qas.id = lateststepid.latestid
+ JOIN {question_attempt_steps} qas ON qas.id = lateststepid.latestid
WHERE qa.questionusageid = $qubaid
HAVING COUNT(CASE WHEN qas.state = 'needsgrading' THEN 1 ELSE NULL END) = 0";
+ // TODO handle $qubaid with placeholders.
}
public function question_attempt_latest_state_view($alias) {
- global $CFG;
return "(
SELECT
{$alias}qa.id AS questionattemptid,
global $CFG;
return "(
SELECT MAX(id)
- FROM {$CFG->prefix}question_attempt_steps
+ FROM {question_attempt_steps}
WHERE questionattemptid = $questionattemptid
)";
}
* @return boolean whether any of these questions are being used by the question engine.
*/
public static function questions_in_use(array $questionids) {
- return record_exists_select('question_attempts', 'questionid IN (' .
- implode(',', $questionids) . ')');
+ list($test, $params) = $this->db->get_in_or_equal($questionids);
+ return $this->db->record_exists_select('question_attempts',
+ 'questionid ' . $test, $params);
}
}
*/
public static function delete_questions_usage_by_activity($qubaid) {
global $CFG;
- self::delete_questions_usage_by_activities($CFG->prefix . 'question_usages.id = ' . $qubaid);
+ self::delete_questions_usage_by_activities('{question_usages}.id = :qubaid', array('qubaid' => $qubaid));
}
/**
* Delete a {@link question_usage_by_activity} from the database, based on its id.
* @param integer $qubaid the id of the usage to delete.
*/
- public static function delete_questions_usage_by_activities($where) {
+ public static function delete_questions_usage_by_activities($where, $params) {
$dm = new question_engine_data_mapper();
- $dm->delete_questions_usage_by_activities($where);
+ $dm->delete_questions_usage_by_activities($where, $params);
}
/**
$archetypes = self::get_archetypal_behaviours();
// If no admin setting return all behavious
- if (!$CFG->questionbehavioursdisabled && !$CFG->questionbehavioursorder) {
+ if (empty($CFG->questionbehavioursdisabled) && empty($CFG->questionbehavioursorder)) {
return $archetypes;
}
* @return string HTML fragment representing the question.
*/
public function render($options, $number) {
- $qoutput = renderer_factory::get_renderer('core', 'question');
+ global $PAGE;
+ $qoutput = $PAGE->get_renderer('core', 'question');
$qtoutput = $this->question->get_renderer();
return $this->behaviour->render($options, $number, $qoutput, $qtoutput);
}
<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+
/**
* This page displays a preview of a question
*
* information is stored in the session as an array of subsequent states rather
* than in the database.
*
- * TODO: make this work with activities other than quiz
- *
- * @author Alex Smith as part of the Serving Mathematics project
- * {@link http://maths.york.ac.uk/serving_maths}
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questionbank
+ * @package moodlecore
+ * @subpackage questionbank
+ * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} and
+ * numerous contributors.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
- require_once("../config.php");
- require_once($CFG->libdir.'/questionlib.php');
- require_once($CFG->dirroot.'/mod/quiz/locallib.php'); // We really want to get rid of this
-
- $id = required_param('id', PARAM_INT); // question id
- // if no quiz id is specified then a dummy quiz with default options is used
- $quizid = optional_param('quizid', 0, PARAM_INT);
- // if no quiz id is specified then tell us the course
-
- $pageurl = new moodle_url('/question/preview.php', array('id' => $id, 'continue' => 1));
- if (empty($quizid)) {
- $courseid = required_param('courseid', PARAM_INT);
- $pageurl->param('courseid', $courseid);
- } else {
- $pageurl->param('quizid', $quizid);
- }
- $PAGE->set_url($pageurl);
- $PAGE->set_pagelayout('popup');
-
- // Test if we are continuing an attempt at a question
- $continue = optional_param('continue', 0, PARAM_BOOL);
- // Check for any of the submit buttons
- $fillcorrect = optional_param('fillcorrect', 0, PARAM_BOOL);
- $markall = optional_param('markall', 0, PARAM_BOOL);
- $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
- $back = optional_param('back', 0, PARAM_BOOL);
- $startagain = optional_param('startagain', 0, PARAM_BOOL);
- // We are always continuing an attempt if a submit button was pressed with the
- // exception of the start again button
- if ($fillcorrect || $markall || $finishattempt || $back) {
- $continue = true;
- } else if ($startagain) {
- $continue = false;
- }
-
- $url = new moodle_url('/question/preview.php');
- $url->param('id', $id);
- if ($quizid) {
- $url->param('quizid', $quizid);
- } else {
- $url->param('courseid', $courseid);
- }
- $url->param('continue', 1);
- if (!$continue) {
- // Start a new attempt; delete the old session
- unset($SESSION->quizpreview);
- // Redirect to ourselves but with continue=1; prevents refreshing the page
- // from restarting an attempt (needed so that random questions don't change)
- redirect($url);
- }
- // Load the question information
- if (!$questions = $DB->get_records('question', array('id' => $id))) {
- print_error('cannotloadquestion', 'question');
- }
- if (empty($quizid)) {
- $quiz = new cmoptions;
- $quiz->id = 0;
- $quiz->review = get_config('quiz', 'review');
- require_login($courseid, false);
- $quiz->course = $courseid;
- $quiz->decimalpoints = get_config('quiz', 'decimalpoints');
- $context = get_context_instance(CONTEXT_COURSE, $courseid);
- } else if (!$quiz = $DB->get_record('quiz', array('id' => $quizid))) {
- print_error('invalidquizid', 'quiz', '', $quizid);
- } else {
- $cm = get_coursemodule_from_instance('quiz', $quizid, $quiz->course);
- require_login($quiz->course, false, $cm);
- $context = get_context_instance(CONTEXT_MODULE, $cm->id);
- }
-
- if ($maxgrade = $DB->get_field('quiz_question_instances', 'grade', array('quiz' => $quiz->id, 'question' => $id))) {
- $questions[$id]->maxgrade = $maxgrade;
- } else {
- $questions[$id]->maxgrade = $questions[$id]->defaultgrade;
- }
-
- $quiz->id = 0; // just for safety
- $quiz->questions = $id;
-
- if (!$category = $DB->get_record("question_categories", array("id" => $questions[$id]->category))) {
- print_error('invalidquestionid', 'quiz');
- }
-
- if (!question_has_capability_on($questions[$id], 'use', $questions[$id]->category)){
- print_error('cannotpreview', 'question');
- }
- if (isset($COURSE)){
- $quiz->course = $COURSE->id;
- }
+require_once(dirname(__FILE__) . '/../config.php');
+require_once($CFG->libdir . '/questionlib.php');
+require_once($CFG->libdir . '/formslib.php');
+require_once(dirname(__FILE__) . '/previewlib.php');
+
+// Get and validate question id.
+$id = required_param('id', PARAM_INT);
+$question = question_bank::load_question($id);
+require_login();
+$category = $DB->get_record('question_categories', array('id' => $question->category), '*', MUST_EXIST);
+question_require_capability_on($question, 'use');
+$PAGE->set_pagelayout('popup');
+$PAGE->set_context(get_context_instance_by_id($category->contextid));
+
+// Get and validate display options.
+$options = new question_preview_options($question);
+$options->load_user_defaults();
+$options->set_from_request();
+$PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, $options));
+
+// Get and validate exitsing preview, or start a new one.
+$previewid = optional_param('previewid', 0, PARAM_ALPHANUM);
+if ($previewid) {
+ if (!isset($SESSION->question_previews[$previewid])) {
+ print_error('notyourpreview', 'question');
+ }
+ try {
+ $quba = question_engine::load_questions_usage_by_activity($previewid);
+ } catch (Exception $e){
+ print_error('submissionoutofsequencefriendlymessage', 'question',
+ question_preview_url($question->id, $options->behaviour,
+ $options->maxmark, $options), null, $e);
+ }
+ $slot = $quba->get_first_question_number();
+ $usedquestion = $quba->get_question($slot);
+ if ($usedquestion->id != $question->id) {
+ print_error('questionidmismatch', 'question');
+ }
+ $question = $usedquestion;
+
+} else {
+ $quba = question_engine::make_questions_usage_by_activity('core_question_preview',
+ get_context_instance_by_id($category->contextid));
+ $quba->set_preferred_behaviour($options->behaviour);
+ $slot = $quba->add_question($question, $options->maxmark);
+ $quba->start_all_questions();
+
+ $transaction = $DB->start_delegated_transaction();
+ question_engine::save_questions_usage_by_activity($quba);
+ $transaction->allow_commit();
+
+ $SESSION->question_previews[$quba->get_id()] = true;
+}
+$options->behaviour = $quba->get_preferred_behaviour();
+$options->maxmark = $quba->get_question_max_mark($slot);
+
+// Create the settings form, and initialise the fields.
+$optionsform = new preview_options_form($CFG->wwwroot . '/question/preview.php?id=' . $question->id, $quba);
+$optionsform->set_data($options);
+
+// Process change of settings, if that was requested.
+if ($newoptions = $optionsform->get_submitted_data()) {
+ // Set user preferences
+ $options->save_user_preview_options($newoptions);
+ restart_preview($previewid, $question->id, $newoptions);
+}
+
+// Prepare a URL that is used in various places.
+$actionurl = question_preview_action_url($question->id, $quba->get_id(), $options);
+
+// Process any actions from the buttons at the bottom of the form.
+if (data_submitted() && confirm_sesskey()) {
+ if (optional_param('restart', false, PARAM_BOOL)) {
+ restart_preview($previewid, $question->id, $options);
+
+ } else if (optional_param('fill', null, PARAM_BOOL)) {
+ $correctresponse = $quba->get_correct_response($slot);
+ $quba->process_action($slot, $correctresponse);
+
+ $transaction = $DB->start_delegated_transaction();
+ question_engine::save_questions_usage_by_activity($quba);
+ $transaction->allow_commit();
+
+ redirect($actionurl);
+
+ } else if (optional_param('finish', null, PARAM_BOOL)) {
+ try {
+ $quba->process_all_actions();
+ } catch (question_out_of_sequence_exception $e){
+ print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
+ }
+ $quba->finish_all_questions();
- // Load the question type specific information
- if (!get_question_options($questions)) {
- print_error('newattemptfail', 'quiz');
- }
+ $transaction = $DB->start_delegated_transaction();
+ question_engine::save_questions_usage_by_activity($quba);
+ $transaction->allow_commit();
+ redirect($actionurl);
- // Create a dummy quiz attempt
- // TODO: find out what of the following we really need. What is $attempt
- // really used for?
- $timenow = time();
- $attempt->quiz = $quiz->id;
- $attempt->userid = $USER->id;
- $attempt->attempt = 0;
- $attempt->sumgrades = 0;
- $attempt->timestart = $timenow;
- $attempt->timefinish = 0;
- $attempt->timemodified = $timenow;
- $attempt->uniqueid = 0;
- $attempt->id = 0;
- $attempt->layout = $id;
-
- // Restore the history of question sessions from the moodle session or create
- // new sessions. Make $states a reference to the states array in the moodle
- // session.
- if (isset($SESSION->quizpreview->states) and $SESSION->quizpreview->questionid == $id) {
- // Reload the question session history from the moodle session
- $states =& $SESSION->quizpreview->states;
- $historylength = count($states) - 1;
- if ($back && $historylength > 0) {
- // Go back one step in the history
- unset($states[$historylength]);
- $historylength--;
- }
} else {
- // Record the question id in the moodle session
- $SESSION->quizpreview->questionid = $id;
- // Create an empty session for the question
- if (!$newstates = get_question_states($questions, $quiz, $attempt)) {
- print_error('newattemptfail', 'quiz');
- }
- $newstates[$id]->questionsessionid = 0;
- $SESSION->quizpreview->states = array($newstates);
- $states =& $SESSION->quizpreview->states;
- $historylength = 0;
- }
-
- if (!$fillcorrect && !$back && ($form = data_submitted())) {
- $form = (array)$form;
- $submitted = true;
-
- // Create a new item in the history of question states (don't simplify!)
- $states[$historylength + 1] = array();
- $states[$historylength + 1][$id] = clone($states[$historylength][$id]);
- $historylength++;
- $curstate =& $states[$historylength][$id];
- $curstate->changed = false;
-
- // Process the responses
- unset($form['id']);
- unset($form['quizid']);
- unset($form['continue']);
- unset($form['markall']);
- unset($form['finishattempt']);
- unset($form['back']);
- unset($form['startagain']);
-
- if ($finishattempt) {
- $event = QUESTION_EVENTCLOSE;
- } else if ($markall) {
- $event = QUESTION_EVENTSUBMIT;
- } else {
- $event = QUESTION_EVENTSAVE;
- }
- if ($actions = question_extract_responses($questions, $form, $event)) {
- $actions[$id]->timestamp = 0; // We do not care about timelimits here
- if (!question_process_responses($questions[$id], $curstate, $actions[$id], $quiz, $attempt)) {
- unset($SESSION->quizpreview);
- print_error('errorprocessingresponses', 'question', $url->out());
- }
- if (!$curstate->changed) {
- // Update the current state rather than creating a new one
- $historylength--;
- unset($states[$historylength]);
- $states = array_values($states);
- $curstate =& $states[$historylength][$id];
- }
+ try {
+ $quba->process_all_actions();
+ } catch (question_out_of_sequence_exception $e){
+ print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
}
- } else {
- $submitted = false;
- $curstate =& $states[$historylength][$id];
- }
-
- // TODO: should not use quiz-specific function here
- $options = quiz_get_renderoptions($quiz, $attempt, $context, $curstate);
- $options->noeditlink = true;
- // Fill in the correct responses (unless the question is in readonly mode)
- if ($fillcorrect && !$options->readonly) {
- $curstate->responses = $QTYPES[$questions[$id]->qtype]->get_correct_responses($questions[$id], $curstate);
- }
+ $transaction = $DB->start_delegated_transaction();
+ question_engine::save_questions_usage_by_activity($quba);
+ $transaction->allow_commit();
- $strpreview = get_string('preview', 'quiz').' '.format_string($questions[$id]->name);
- $questionlist = array($id);
- question_get_html_head_contributions($questionlist, $questions, $states[$historylength]);
- $PAGE->set_title($strpreview);
- $PAGE->set_heading($COURSE->fullname);
- echo $OUTPUT->header();
-
- if (!empty($quizid)) {
- echo '<p class="quemodname">'.get_string('modulename', 'quiz') . ': ';
- p(format_string($quiz->name));
- echo "</p>\n";
- }
- $number = 1;
- echo '<form method="post" action="'.$url->out_omit_querystring().'" enctype="multipart/form-data" id="responseform">', "\n";
- $PAGE->requires->js_init_call('M.core_question_engine.init_form', array('#responseform'));
-
- print_question($questions[$id], $curstate, $number, $quiz, $options, $context);
-
- echo '<div class="controls">';
- echo html_writer::input_hidden_params($url);
-
- // Print the mark and finish attempt buttons
- echo '<input name="markall" type="submit" value="' . get_string('markall',
- 'quiz') . "\" />\n";
- echo '<input name="finishattempt" type="submit" value="' .
- get_string('submitallandfinish', 'quiz') . "\" />\n";
- echo '<br />';
- echo '<br />';
- // Print the fill correct button (unless the question is in readonly mode)
- if (!$options->readonly) {
- echo '<input name="fillcorrect" type="submit" value="' .
- get_string('fillcorrect', 'quiz') . "\" />\n";
- }
- // Print the navigation buttons
- if ($historylength > 0) {
- echo '<input name="back" type="submit" value="' . get_string('previous',
- 'quiz') . "\" />\n";
- }
- // Print the start again button
- echo '<input name="startagain" type="submit" value="' .
- get_string('startagain', 'quiz') . "\" />\n";
- // Print the close window button
- echo '<input type="button" onclick="window.close()" value="' .
- get_string('closepreview', 'quiz') . "\" />";
- echo '</div>';
- echo '</form>';
- echo $OUTPUT->footer();
+ $scrollpos = optional_param('scrollpos', '', PARAM_RAW);
+ if ($scrollpos !== '') {
+ $actionurl .= '&scrollpos=' . ((int) $scrollpos);
+ }
+ redirect($actionurl);
+ }
+}
+
+if ($question->length) {
+ $displaynumber = '1';
+} else {
+ $displaynumber = 'i';
+}
+$restartdisabled = '';
+$finishdisabled = '';
+$filldisabled = '';
+if ($quba->get_question_state($slot)->is_finished()) {
+ $finishdisabled = ' disabled="disabled"';
+ $filldisabled = ' disabled="disabled"';
+}
+if (!$previewid) {
+ $restartdisabled = ' disabled="disabled"';
+}
+
+$PAGE->requires->js_init_call('M.core_question_preview.init', null, array(
+ 'name' => 'core_question_preview',
+ 'fullpath' => '/question/preview.js',
+ 'requires' => array('base', 'dom', 'event-delegate', 'event-key', 'core_question_engine'),
+ 'strings' => array(
+ array('question', 'closepreview'),
+ )));
+
+// Output
+$title = get_string('previewquestion', 'question', format_string($question->name));
+$headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot);
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+echo $OUTPUT->header();
+
+// Start the question form.
+echo '<form method="post" action="' . s($actionurl) .
+ '" enctype="multipart/form-data" id="responseform">', "\n";
+echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />', "\n";
+echo '<input type="hidden" name="slots" value="' . $slot . '" />', "\n";
+
+// Output the question.
+echo $quba->render_question($slot, $options, $displaynumber);
+
+echo '<p class="notifytiny">' . get_string('behaviourbeingused', 'question',
+ question_engine::get_behaviour_name(
+ $quba->get_question_attempt($slot)->get_behaviour_name())) . '</p>';
+// Finish the question form.
+echo '<div id="previewcontrols" class="controls">';
+echo '<input type="submit" name="restart"' . $restartdisabled .
+ ' value="' . get_string('restart', 'question') . '" />', "\n";
+echo '<input type="submit" name="fill"' . $filldisabled .
+ ' value="' . get_string('fillincorrect', 'question') . '" />', "\n";
+echo '<input type="submit" name="finish"' . $finishdisabled .
+ ' value="' . get_string('submitandfinish', 'question') . '" />', "\n";
+echo '<input type="hidden" name="scrollpos" id="scrollpos" value="" />';
+echo '</div>';
+echo '</form>';
+
+// Display the settings form.
+$optionsform->display();
+
+echo '<script type="text/javascript">question_preview_init("' .
+ get_string('closepreview', 'question') . '", "previewcontrols");</script>', "\n";
+
+echo $OUTPUT->footer();
$behaviours = question_engine::get_behaviour_options($this->_customdata->get_preferred_behaviour());
$mform->addElement('select', 'behaviour', get_string('howquestionsbehave', 'question'), $behaviours);
- $mform->setHelpButton('behaviour', array('howquestionsbehave', get_string('howquestionsbehave', 'question'), 'question'));
+ $mform->addHelpButton('behaviour', 'howquestionsbehave', 'question');
$mform->addElement('text', 'maxmark', get_string('markedoutof', 'question'), array('size' => '5'));
$mform->setType('maxmark', PARAM_NUMBER);
$this->maxmark = $question->defaultmark;
$this->correctness = self::VISIBLE;
$this->marks = self::MARK_AND_MAX;
- $this->markdp = $CFG->quiz_decimalpoints;
+ $this->markdp = get_config('quiz', 'decimalpoints');
$this->feedback = self::VISIBLE;
$this->numpartscorrect = $this->feedback;
$this->generalfeedback = self::VISIBLE;
* @param object $displayoptions
*/
function restart_preview($previewid, $questionid, $displayoptions) {
+ global $DB;
+
if ($previewid) {
- begin_sql();
+ $transaction = $DB->start_delegated_transaction();
question_engine::delete_questions_usage_by_activity($previewid);
- commit_sql();
+ $transaction->allow_commit();
}
redirect(question_preview_url($questionid, $displayoptions->behaviour, $displayoptions->maxmark, $displayoptions));
}
mod/quiz/config.html | 304 ++-
mod/quiz/db/access.php | 19 +-
mod/quiz/db/install.xml | 191 +-
- mod/quiz/db/mysql.php | 1163 --------
- mod/quiz/db/postgres7.php | 1497 ----------
- mod/quiz/db/upgrade.php | 1204 ++++++++-
- mod/quiz/defaults.php | 12 +-
+DONE mod/quiz/db/mysql.php | 1163 --------
+DONE mod/quiz/db/postgres7.php | 1497 ----------
+DONE mod/quiz/db/upgrade.php | 1204 ++++++++-
+DONE mod/quiz/defaults.php | 12 +-
mod/quiz/edit.js | 131 +
mod/quiz/edit.php | 118 +-
mod/quiz/editlib.php | 79 +-
question/import_form.php | 19 + | the change is to add validation that a file has been uploaded.
DONE question/move_form.php | 32 +-
DONE question/preview.js | 47 +
- question/preview.php | 408 ++--
+DONE question/preview.php | 408 ++--
DONE question/previewlib.php | 214 ++
DONE question/qengine.js | 181 ++
DONE question/question.php | 3 +-
question/restorelib.php | 88 +-
DONE question/toggleflag.php | 49 +
+DONE question/engine/bank.php | 221 ++
+DONE question/engine/compatibility.php | 958 +++++++
+DONE question/engine/datalib.php | 1069 ++++++++
+DONE question/engine/lib.php | 2873 ++++++++++++++++++++
+DONE question/engine/renderer.php | 362 +++
+DONE question/engine/simpletest/helpers.php | 629 +++++
+DONE question/engine/simpletest/testdatalib.php | 125 +
+DONE question/engine/simpletest/testhtmlwriter.php | 94 +
+DONE question/engine/simpletest/testquestionattempt.php | 346 +++
+DONE question/engine/simpletest/testquestionattemptiterator.php | 108 +
+DONE question/engine/simpletest/testquestionattemptstep.php | 170 ++
+DONE question/engine/simpletest/testquestionattemptstepiterator.php | 127 +
+DONE question/engine/simpletest/testquestioncbm.php | 40 +
+DONE question/engine/simpletest/testquestionengine.php | 94 +
+DONE question/engine/simpletest/testquestionstate.php | 154 ++
+DONE question/engine/simpletest/testquestionusagebyactivity.php | 153 ++
+DONE question/engine/simpletest/testquestionutils.php | 186 ++
+DONE question/engine/states.php | 460 ++++
+DONE question/engine/todo.txt | 235 ++
+ question/engine/upgradefromoldqe/upgrade.php | 1963 +++++++++++++
+
DONE question/type/edit_question_form.php | 264 ++-
DONE question/type/question.html | 46 -
DONE question/type/questionbase.php | 787 ++++++
DONE question/type/truefalse/edit_truefalse_form.php | 53 +-
DONE question/type/truefalse/lang/en_utf8/qtype_truefalse.php | 14 +
DONE question/type/truefalse/question.php | 97 +
- question/type/truefalse/questiontype.php | 212 +-
+DONE question/type/truefalse/questiontype.php | 212 +-
DONE question/type/truefalse/renderer.php | 142 +
DONE question/type/truefalse/simpletest/testquestion.php | 99 +
DONE question/type/truefalse/simpletest/testquestiontype.php | 73 +
DONE question/type/truefalse/version.php | 4 +-
- question/behaviour/behaviourbase.php | 627 +++++
- question/behaviour/rendererbase.php | 200 ++
+DONE question/behaviour/behaviourbase.php | 627 +++++
+DONE question/behaviour/rendererbase.php | 200 ++
question/behaviour/adaptive/behaviour.php | 181 ++
question/behaviour/adaptive/lang/en_utf8/qbehaviour_adaptive.php | 6 +
question/behaviour/opaque/renderer.php | 65 +
question/behaviour/opaque/simpletest/testopaquebehaviour.php | 227 ++
- question/engine/bank.php | 221 ++
- question/engine/compatibility.php | 958 +++++++
- question/engine/datalib.php | 1069 ++++++++
- question/engine/lib.php | 2873 ++++++++++++++++++++
- question/engine/renderer.php | 362 +++
- question/engine/simpletest/helpers.php | 629 +++++
- question/engine/simpletest/testdatalib.php | 125 +
- question/engine/simpletest/testhtmlwriter.php | 94 +
- question/engine/simpletest/testquestionattempt.php | 346 +++
- question/engine/simpletest/testquestionattemptiterator.php | 108 +
- question/engine/simpletest/testquestionattemptstep.php | 170 ++
- question/engine/simpletest/testquestionattemptstepiterator.php | 127 +
- question/engine/simpletest/testquestioncbm.php | 40 +
- question/engine/simpletest/testquestionengine.php | 94 +
- question/engine/simpletest/testquestionstate.php | 154 ++
- question/engine/simpletest/testquestionusagebyactivity.php | 153 ++
- question/engine/simpletest/testquestionutils.php | 186 ++
- question/engine/states.php | 460 ++++
- question/engine/todo.txt | 235 ++
- question/engine/upgradefromoldqe/upgrade.php | 1963 +++++++++++++
-
question/format.php | 15 +-
question/format/blackboard/format.php | 2 +-
question/format/gift/format.php | 6 +-
* @return qtype_renderer the renderer to use for outputting this question.
*/
public function get_renderer() {
- return renderer_factory::get_renderer('qtype_' . $this->qtype->name());
+ global $PAGE;
+ return $PAGE->get_renderer('qtype_' . $this->qtype->name());
}
/**
* script.js or script.php that exist in the plugin folder and ensures they
* get included.
*/
- protected function find_standard_scripts() {
+ public function find_standard_scripts() {
global $PAGE;
$plugindir = $this->plugin_dir();
* @return string HTML fragment.
*/
public function head_code(question_attempt $qa) {
- return implode("\n", $qa->get_question()->qtype->find_standard_scripts_and_css());
+ // TODO I think we can get rid of this, but what about Opaque?
+ $qa->get_question()->qtype->find_standard_scripts();
}
protected function feedback_class($fraction) {
array('for' => $falseattributes['id']));
$result = '';
- $result .= html_writer::tag('div', $question->format_questiontext(),
+ $result .= html_writer::tag('div', $question->format_questiontext($qa),
array('class' => 'qtext'));
$result .= html_writer::start_tag('div', array('class' => 'ablock'));