However, all this needs more testing.
Also, a bit of a purge of training whitespace and global .
$found = false;
$textlib = textlib_get_instance();
- require_once($CFG->libdir . '/questionlib.php');
- global $QTYPES;
- foreach ($QTYPES as $qtype) {
+ require_once($CFG->dirroot . '/question/engine/bank.php');
+ foreach (question_bank::get_all_qtypes() as $qtype) {
if (strpos($textlib->strtolower($qtype->local_name()), $query) !== false) {
$found = true;
break;
* Sort an array of question type names according to the question type sort order stored in
* config_plugins. Entries for which there is no xxx_sortorder defined will go
* at the end, sorted according to textlib_get_instance()->asort($inarray).
- * @param $inarray an array $qtype => $QTYPES[$qtype]->local_name().
+ * @param $inarray an array $qtypename => $qtype->local_name().
* @param $config get_config('question'), if you happen to have it around, to save one DB query.
* @return array the sorted version of $inarray.
*/
* @param object $question The question being deleted
*/
function question_delete_question($questionid) {
- global $QTYPES, $DB;
+ global $DB;
$question = $DB->get_record_sql('
SELECT q.*, qc.contextid
<?php
function xmldb_local_qedatabase_upgrade($oldversion) {
- global $CFG, $DB, $QTYPES;
+ global $CFG, $DB;
$dbman = $DB->get_manager();
* @param boolean $unfinished whether the button is to continue an existing attempt,
* or start a new one. This affects whether a javascript alert is shown.
*/
- public function print_start_attempt_button($canpreview, $buttontext, $unfinished) {
+ public function print_start_attempt_button($canpreview, $buttontext, $unfinished) {
global $OUTPUT;
$url = $this->_quizobj->start_attempt_url();
-/** JavaScript for /mod/quiz/edit.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/>.
+
+/**
+ * JavaScript library for the quiz module editing interface.
+ *
+ * @package mod
+ * @subpackage quiz
+ * @copyright 2008 Olli Savolainen
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
// Initialise everything on the quiz edit/order and paging page.
var quiz_edit = {};
<?php
-///////////////////////////////////////////////////////////////////////////
-// //
-// NOTICE OF COPYRIGHT //
-// //
-// Moodle - Modular Object-Oriented Dynamic Learning Environment //
-// http://moodle.org //
-// //
-// Copyright (C) 1999 onwards Martin Dougiamas and others //
-// //
-// This program 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 2 of the License, or //
-// (at your option) any later version. //
-// //
-// This program 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: //
-// //
-// http://www.gnu.org/copyleft/gpl.html //
-// //
-///////////////////////////////////////////////////////////////////////////
+// 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/>.
+
/**
* Page to edit quizzes
* delete Removes a question from the quiz
* savechanges Saves the order and grades for questions in the quiz
*
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package quiz
- *//** */
+ * @package mod
+ * @subpackage quiz
+ * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
require_once('../../config.php');
require_once($CFG->dirroot . '/mod/quiz/editlib.php');
require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
require_once($CFG->dirroot . '/question/category_class.php');
+
/**
* Callback function called from question_list() function
* (which is called from showbank())
* (which is called from showbank())
*/
function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmoptions) {
- global $QTYPES, $OUTPUT;
+ global $OUTPUT;
$out = '';
$catcontext = get_context_instance_by_id($category->contextid);
if (has_capability('moodle/question:useall', $catcontext)) {
$disabled = '';
}
$randomusablequestions =
- $QTYPES['random']->get_usable_questions_from_category($category->id, $recurse, '0');
+ question_bank::get_qtype('random')->get_available_questions_from_category(
+ $category->id, $recurse);
$maxrand = count($randomusablequestions);
if ($maxrand > 0) {
for ($i = 1; $i <= min(10, $maxrand); $i++) {
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;
// 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)) { // Construct an array to hold all the grades.
+if (empty($quiz->grades)) {
$quiz->grades = quiz_get_all_question_grades($quiz);
}
if (($up = optional_param('up', false, PARAM_INT)) && confirm_sesskey()) {
$quiz->questions = quiz_move_question_up($quiz->questions, $up);
- quiz_save_new_layout($quiz);
+ $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+ quiz_delete_previews($quiz);
redirect($thispageurl);
}
if (($down = optional_param('down', false, PARAM_INT)) && confirm_sesskey()) {
$quiz->questions = quiz_move_question_down($quiz->questions, $down);
- quiz_save_new_layout($quiz);
+ $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+ quiz_delete_previews($quiz);
redirect($thispageurl);
}
// Re-paginate the quiz
$questionsperpage = optional_param('questionsperpage', $quiz->questionsperpage, PARAM_INT);
$quiz->questions = quiz_repaginate($quiz->questions, $questionsperpage );
- quiz_save_new_layout($quiz);
+ $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+ quiz_delete_previews($quiz);
+ redirect($thispageurl);
}
if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) {
/// Add a single question to the current quiz
$addonpage = optional_param('addonpage', 0, PARAM_INT);
quiz_add_quiz_question($addquestion, $quiz, $addonpage);
- quiz_update_sumgrades($quiz);
quiz_delete_previews($quiz);
+ quiz_update_sumgrades($quiz);
$thispageurl->param('lastchanged', $addquestion);
redirect($thispageurl);
}
if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
/// Add selected questions to the current quiz
$rawdata = (array) data_submitted();
- foreach ($rawdata as $key => $value) { // Parse input for question ids
+ foreach ($rawdata as $key => $value) { // Parse input for question ids
if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
$key = $matches[1];
quiz_add_quiz_question($key, $quiz);
}
}
- quiz_update_sumgrades($quiz);
quiz_delete_previews($quiz);
+ quiz_update_sumgrades($quiz);
redirect($thispageurl);
}
-$qcobject = new question_category_object($pagevars['cpage'], $thispageurl,
- $contexts->having_one_edit_tab_cap('categories'), $defaultcategoryobj->id,
- $defaultcategory, null, $contexts->having_cap('moodle/question:add'));
-
if ((optional_param('addrandom', false, PARAM_BOOL)) && confirm_sesskey()) {
// Add random questions to the quiz
$recurse = optional_param('recurse', 0, PARAM_BOOL);
$randomcount = required_param('randomcount', PARAM_INT);
quiz_add_random_questions($quiz, $addonpage, $categoryid, $randomcount, $recurse);
- quiz_update_sumgrades($quiz);
quiz_delete_previews($quiz);
+ quiz_update_sumgrades($quiz);
redirect($thispageurl);
}
foreach ($selectedquestionids as $questionid) {
$quiz->questions = quiz_add_page_break_after($quiz->questions, $questionid);
}
- quiz_save_new_layout($quiz);
+ $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+ quiz_delete_previews($quiz);
redirect($thispageurl);
}
$addpage = optional_param('addpage', false, PARAM_INT);
if ($addpage !== false && confirm_sesskey()) {
$quiz->questions = quiz_add_page_break_at($quiz->questions, $addpage);
- quiz_save_new_layout($quiz);
+ $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+ quiz_delete_previews($quiz);
redirect($thispageurl);
}
$deleteemptypage = optional_param('deleteemptypage', false, PARAM_INT);
if (($deleteemptypage !== false) && confirm_sesskey()) {
$quiz->questions = quiz_delete_empty_page($quiz->questions, $deleteemptypage);
- quiz_save_new_layout($quiz);
+ $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+ quiz_delete_previews($quiz);
redirect($thispageurl);
}
foreach ($selectedquestionids as $questionid) {
quiz_remove_question($quiz, $questionid);
}
- quiz_update_sumgrades($quiz);
quiz_delete_previews($quiz);
+ quiz_update_sumgrades($quiz);
redirect($thispageurl);
}
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();
/// Parse input for question -> grades
$questionid = $matches[1];
$quiz->grades[$questionid] = clean_param($value, PARAM_FLOAT);
- quiz_update_question_instance($quiz->grades[$questionid], $questionid, $quiz->id);
- quiz_delete_previews($quiz);
- quiz_update_sumgrades($quiz);
+ quiz_update_question_instance($quiz->grades[$questionid], $questionid, $quiz);
+ $deletepreviews = true;
+ $recomputesummarks = true;
} else if (preg_match('!^o(pg)?([0-9]+)$!', $key, $matches)) {
/// Parse input for ordering info
} else {
$questions[$value] = $questionid;
}
+ $deletepreviews = true;
}
}
ksort($questions);
$questions[] = 0;
$quiz->questions = implode(',', $questions);
- quiz_save_new_layout($quiz);
- quiz_delete_previews($quiz);
+ $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
$moveselectedpos = $pagebreakpositions[$moveselectedonpage - 1];
array_splice($questions, $moveselectedpos, 0, $selectedquestionids);
$quiz->questions = implode(',', $questions);
- quiz_save_new_layout($quiz);
- quiz_delete_previews($quiz);
+ $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+ $deletepreviews = true;
}
// If rescaling is required save the new maximum
quiz_set_grade($maxgrade, $quiz);
}
+ if ($deletepreviews) {
+ quiz_delete_previews($quiz);
+ }
+ if ($recomputesummarks) {
+ quiz_update_sumgrades($quiz);
+ quiz_update_all_attempt_sumgrades($quiz);
+ quiz_update_all_final_grades($quiz);
+ quiz_update_grades($quiz, 0, true);
+ }
redirect($thispageurl);
}
<?php
-///////////////////////////////////////////////////////////////////////////
-// //
-// NOTICE OF COPYRIGHT //
-// //
-// Moodle - Modular Object-Oriented Dynamic Learning Environment //
-// http://moodle.org //
-// //
-// Copyright (C) 1999 onwards Martin Dougiamas and others //
-// //
-// This program 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 2 of the License, or //
-// (at your option) any later version. //
-// //
-// This program 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: //
-// //
-// http://www.gnu.org/copyleft/gpl.html //
-// //
-///////////////////////////////////////////////////////////////////////////
+// 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/>.
+
/**
- * Functions used by edit.php to edit quizzes
+ * This contains functions that are called from within the quiz module only
+ * Functions that are also called by core Moodle are in {@link lib.php}
+ * This script also loads the code in {@link questionlib.php} which holds
+ * the module-indpendent code for handling questions and which in turn
+ * initialises all the questiontype classes.
*
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package quiz
- *//** */
+ * @package mod
+ * @subpackage quiz
+ * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+
define('NUM_QS_TO_SHOW_IN_RANDOM', 3);
/**
$quiz->questions = implode(',', $questions);
$DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
- // update question grades
- $quiz->grades[$id] = $DB->get_field('question', 'defaultgrade', array('id' => $id));
- quiz_update_question_instance($quiz->grades[$id], $id, $quiz->instance);
-
- return true;
+ // Add the new question instance.
+ $instance = new stdClass;
+ $instance->quiz = $quiz->id;
+ $instance->question = $id;
+ $instance->grade = $DB->get_field('question', 'defaultmark', array('id' => $id));
+ $DB->insert_record('quiz_question_instances', $instance);
}
function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, $includesubcategories) {
- global $DB, $QTYPES;
+ global $DB;
$category = $DB->get_record('question_categories', array('id' => $categoryid));
if (!$category) {
// not used by any quiz.
if ($existingquestions = $DB->get_records_sql(
"SELECT q.id,q.qtype FROM {question} q
- WHERE qtype = '" . RANDOM . "'
+ WHERE qtype = 'random'
AND category = ?
AND " . $DB->sql_compare_text('questiontext') . " = ?
AND NOT EXISTS (SELECT * FROM {quiz_question_instances} WHERE question = q.id)
// More random questions are needed, create them.
$form->questiontext = array('text' => $includesubcategories, 'format' => 0);
- $form->defaultgrade = 1;
+ $form->defaultmark = 1;
$form->hidden = 1;
for ($i = 0; $i < $number; $i += 1) {
$form->category = $category->id . ',' . $category->contextid;
$form->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
$question = new stdClass;
- $question->qtype = RANDOM;
- $question = $QTYPES[RANDOM]->save_question($question, $form);
+ $question->qtype = 'random';
+ $question = question_bank::get_qtype('random')->save_question($question, $form);
if (!isset($question->id)) {
print_error('cannotinsertrandomquestion', 'quiz');
}
*
* Saves changes to the question grades in the quiz_question_instances table.
* It does not update 'sumgrades' in the quiz table.
- * @return boolean Indicates success or failure.
+ *
* @param integer grade The maximal grade for the question
* @param integer $questionid The id of the question
* @param integer $quizid The id of the quiz to update / add the instances for.
*/
-function quiz_update_question_instance($grade, $questionid, $quizid) {
- global $DB;
- if ($instance = $DB->get_record('quiz_question_instances', array('quiz' => $quizid, 'question' => $questionid))) {
- $instance->grade = $grade;
- return $DB->update_record('quiz_question_instances', $instance);
- } else {
- unset($instance);
- $instance->quiz = $quizid;
- $instance->question = $questionid;
- $instance->grade = $grade;
- return $DB->insert_record('quiz_question_instances', $instance);
+function quiz_update_question_instance($grade, $questionid, $quiz) {
+ $instance = $DB->get_record('quiz_question_instances', array('quiz' => $quizid,
+ 'question' => $questionid));
+ $slot = quiz_get_slot_for_question($quiz, $questionid);
+
+ if (!$instance || !$slot) {
+ throw new coding_exception('Attempt to change the grade of a quesion not in the quiz.');
}
+
+ if (abs($grade - $instance->grade) < 1e-7) {
+ // Grade has not changed. Nothing to do.
+ return;
+ }
+
+ $instance->grade = $grade;
+ $DB->update_record('quiz_question_instances', $instance);
+ question_engine::set_max_mark_in_attempts(new quibaid_for_quiz($quiz->id),
+ $slot, $grade);
}
// Private function used by the following two.
*/
function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
$quiz_qbanktool, $hasattempts, $defaultcategoryobj) {
- global $USER, $CFG, $QTYPES, $DB, $OUTPUT;
+ global $USER, $CFG, $DB, $OUTPUT;
$strorder = get_string('order');
$strquestionname = get_string('questionname', 'quiz');
$strgrade = get_string('grade');
if ($quiz->questions) {
list($usql, $params) = $DB->get_in_or_equal(explode(',', $quiz->questions));
- $questions = $DB->get_records_sql("SELECT q.*,c.contextid
- FROM {question} q,
- {question_categories} c
- WHERE q.id $usql
- AND q.category = c.id", $params);
+ $params[] = $quiz->id;
+ $questions = $DB->get_records_sql("SELECT q.*, qc.contextid, qqi.grade as maxmark
+ FROM {question} q
+ JOIN {question_categories} qc ON qc.id = q.category
+ JOIN {quiz_question_instances} qqi ON qqi.question = q.id
+ WHERE q.id $usql AND qqi.quiz = ?", $params);
} else {
$questions = array();
}
$qno = 1;
//the current question (includes questions and descriptions)
$questioncount = 0;
- //the ordinal of current element in the layout
- //(includes page breaks, questions and descriptions)
- $count = 0;
//the current page number in iteration
$pagecount = 0;
- $sumgrade = 0;
-
$pageopen = false;
$returnurl = str_replace($CFG->wwwroot, '', $pageurl->out(false));
$questiontotalcount = count($order);
- foreach ($order as $i => $qnum) {
+ foreach ($order as $count => $qnum) {
$reordercheckbox = '';
$reordercheckboxlabel = '';
if ($qnum && empty($questions[$qnum])) {
continue;
}
+
// If the questiontype is missing change the question type
- if ($qnum && !array_key_exists($questions[$qnum]->qtype, $QTYPES)) {
+ if ($qnum && !array_key_exists($qnum, $questions)) {
+ $fakequestion = new stdClass();
+ $fakequestion->id = 0;
+ $fakequestion->qtype = 'missingtype';
+ $fakequestion->name = get_string('deletedquestion', 'qtype_missingtype');
+ $fakequestion->questiontext = '<p>' . get_string('deletedquestion', 'qtype_missing') . '</p>';
+ $fakequestion->length = 0;
+ $questions[$qnum] = $fakequestion;
+ $quiz->grades[$qnum] = 0;
+
+ } else if ($qnum and question_bank::qtype_exists($questions[$qnum]->qtype)) {
$questions[$qnum]->qtype = 'missingtype';
}
+
if ($qnum != 0 || ($qnum == 0 && !$pageopen)) {
//this is either a question or a page break after another
// (no page is currently open)
'</span><div class="pagecontent">';
$pageopen = true;
}
- if ($qnum == 0 && $i < $questiontotalcount) {
+ if ($qnum == 0 && $count < $questiontotalcount) {
// This is the second successive page break. Tell the user the page is empty.
echo '<div class="pagestatus">';
print_string('noquestionsonpage', 'quiz');
if ($allowdelete) {
echo '<div class="quizpagedelete">';
echo '<a title="' . get_string('removeemptypage', 'quiz') . '" href="' .
- $pageurl->out(true, array('deleteemptypage' => $i - 1, 'sesskey'=>sesskey())) .
+ $pageurl->out(true, array('deleteemptypage' => $count - 1, 'sesskey'=>sesskey())) .
'"><img src="' . $OUTPUT->pix_url('t/delete') . '" ' .
'class="iconsmall" alt="' . $strremove . '" /></a>';
echo '</div>';
" alt=\"$strmovedown\" /></a>";
}
}
- if ($allowdelete && question_has_capability_on($question, 'use', $question->category)) {
+ if ($allowdelete && (empty($question->id) || question_has_capability_on($question, 'use', $question->category))) {
// remove from quiz, not question delete.
if (!$hasattempts) {
echo "<a title=\"$strremove\" href=\"" .
</div>
<?php
- /* Display question end */
- $count++;
- $sumgrade += $quiz->grades[$qnum];
-
}
}
//a page break: end the existing page.
if ($qnum == 0) {
if ($pageopen) {
- if (!$reordertool && !($quiz->shufflequestions && $i < $questiontotalcount - 1)) {
+ if (!$reordertool && !($quiz->shufflequestions && $count < $questiontotalcount - 1)) {
quiz_print_pagecontrols($quiz, $pageurl, $pagecount,
$hasattempts, $defaultcategoryobj);
- } else if ($i < $questiontotalcount - 1) {
+ } else if ($count < $questiontotalcount - 1) {
//do not include the last page break for reordering
//to avoid creating a new extra page in the end
echo '<input type="hidden" name="opg' . $pagecount . '" size="2" value="' .
echo $reordercontrolsbottom;
echo '</div></form>';
}
-
- return $sumgrade;
}
/**
* @param object $quiz The quiz in the context of which the question is being displayed
*/
function quiz_print_singlequestion($question, $returnurl, $quiz) {
- global $QTYPES;
echo '<div class="singlequestion">';
echo quiz_question_edit_button($quiz->cmid, $question, $returnurl, quiz_question_tostring($question) . ' ');
echo '<span class="questiontype">';
- $namestr = $QTYPES[$question->qtype]->local_name();
print_question_icon($question);
- echo " $namestr</span>";
+ echo ' ' . question_bank::get_qtype_name($question->qtype) . '</span>';
echo '<span class="questionpreview">' . quiz_question_preview_button($quiz, $question, true) . '</span>';
echo "</div>\n";
}
* @param boolean $quiz_qbanktool Indicate to this function if the question bank window open
*/
function quiz_print_randomquestion(&$question, &$pageurl, &$quiz, $quiz_qbanktool) {
- global $DB, $QTYPES, $OUTPUT;
+ global $DB, $OUTPUT;
echo '<div class="quiz_randomquestion">';
if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
echo '<span class="questionpreview">' . quiz_question_preview_button($quiz, $question, true) . '</span>';
echo '</div>';
- $questionids = $QTYPES['random']->get_usable_questions_from_category(
+ $questionids = question_bank::get_qtype('random')->get_usable_questions_from_category(
$category->id, $question->questiontext == '1', '0');
$questioncount = count($questionids);
echo '<div class="randomquestioncategorycount">';
echo '</div>';
echo '</div>';
-
}
/**
* @param object $quiz The quiz in the context of which the question is being displayed
*/
function quiz_print_randomquestion_reordertool(&$question, &$pageurl, &$quiz) {
- global $DB, $QTYPES, $OUTPUT;
+ global $DB, $OUTPUT;
// Load the category, and the number of available questions in it.
if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
echo $OUTPUT->notification('Random question category not found!');
return;
}
- $questioncount = count($QTYPES['random']->get_usable_questions_from_category(
+ $questioncount = count(question_bank::get_qtype('random')->get_usable_questions_from_category(
$category->id, $question->questiontext == '1', '0'));
$reordercheckboxlabel = '<label for="s' . $question->id . '">';
echo '<div class="randomquestioncategorycount">';
echo '</div>';
echo '</div>';
-
}
/**
}
echo '<img src="' . $OUTPUT->pix_url('i/' . $icon) . '" alt="' .
$tooltip . '" title="' . $tooltip . '" class="uihint" />';
-
}
/**
* If false, show only question name.
* @param boolean $return If true (default), return the output. If false, print it.
*/
-
function quiz_question_tostring(&$question, $showicon = false, $showquestiontext = true, $return = true) {
- global $COURSE;
- $result = '';
- $result .= '<span class="questionname">';
- if ($showicon) {
- $result .= print_question_icon($question, true);
- echo " ";
- }
- $result .= shorten_text(format_string($question->name), 200) . '</span>';
- if ($showquestiontext) {
- $formatoptions = new stdClass;
- $formatoptions->noclean = true;
- $formatoptions->para = false;
- $questiontext = strip_tags(format_text($question->questiontext,
- $question->questiontextformat,
- $formatoptions, $COURSE->id));
- $questiontext = shorten_text($questiontext, 200);
- $result .= '<span class="questiontext">';
- if (!empty($questiontext)) {
- $result .= $questiontext;
- } else {
- $result .= '<span class="error">';
- $result .= get_string('questiontextisempty', 'quiz');
- $result .= '</span>';
- }
- $result .= '</span>';
- }
- if ($return) {
- return $result;
+ global $COURSE;
+ $result = '';
+ $result .= '<span class="questionname">';
+ if ($showicon) {
+ $result .= print_question_icon($question, true);
+ echo ' ';
+ }
+ $result .= shorten_text(format_string($question->name), 200) . '</span>';
+ if ($showquestiontext) {
+ $formatoptions = new stdClass;
+ $formatoptions->noclean = true;
+ $formatoptions->para = false;
+ $questiontext = strip_tags(format_text($question->questiontext,
+ $question->questiontextformat,
+ $formatoptions, $COURSE->id));
+ $questiontext = shorten_text($questiontext, 200);
+ $result .= '<span class="questiontext">';
+ if (!empty($questiontext)) {
+ $result .= $questiontext;
} else {
- echo $result;
+ $result .= '<span class="error">';
+ $result .= get_string('questiontextisempty', 'quiz');
+ $result .= '</span>';
}
+ $result .= '</span>';
+ }
+ if ($return) {
+ return $result;
+ } else {
+ echo $result;
+ }
}
/**
</div>
<?php
}
-
-?>
// What sort of icon should we show?
$action = '';
- if (question_has_capability_on($question, 'edit', $question->category) ||
- question_has_capability_on($question, 'move', $question->category)) {
+ if (!empty($question->id) && (question_has_capability_on($question, 'edit', $question->category) ||
+ question_has_capability_on($question, 'move', $question->category))) {
$action = $stredit;
$icon = '/t/edit';
- } else if (question_has_capability_on($question, 'view', $question->category)) {
+ } else if (!empty($question->id) && question_has_capability_on($question, 'view', $question->category)) {
$action = $strview;
$icon = '/i/info';
}
}
// Build the icon.
+ $strpreviewquestion = get_string('previewquestion', 'quiz');
$image = $OUTPUT->pix_icon('t/preview', $strpreviewquestion);
parse_str(QUESTION_PREVIEW_POPUP_OPTIONS, $options);
$PAGE->navbar->add(get_string('editinga', 'moodle', get_string('modulename', $cm->modname)),$returnurl);
}
$PAGE->navbar->add($chooseqtype);
- $PAGE->set_title($chooseqtype);
+ $PAGE->set_title($chooseqtype);
echo $OUTPUT->header();
} else {
$PAGE->navbar->add(get_string('questionbank', 'question'),$returnurl);
$PAGE->navbar->add($chooseqtype);
- $PAGE->set_title($chooseqtype);
+ $PAGE->set_title($chooseqtype);
echo $OUTPUT->header();
}
/**
* @return array subpartid => object with fields
- * ->responseclassid the
+ * ->responseclassid matches one of the values returned from quetion_type::get_possible_responses.
* ->response the actual response the student gave to this part, as a string.
* ->fraction the credit awarded for this subpart, may be null.
* returns an empty array if no analysis is possible.
$this->get_does_not_contain_feedback_expectation(),
$this->get_contains_validation_error_expectation());
$this->assertNull($this->quba->get_response_summary($this->slot));
-
+
// Finish the attempt.
$this->quba->finish_all_questions();
$this->check_current_state(question_state::$gradedpartial);
// TODO I don't think 1 is the right fraction here. However, it is what
// you get attempting a question like this without regrading being involved,
- // and I am currently interested in testing regrading here.
+ // and I am currently interested in testing regrading here.
$this->check_current_mark(1);
}
}
html_writer::tag('div', $error . get_string('xoutofmax', 'question', $a) .
$markrange, array('class' => 'felement ftext' . $errorclass)
), array('class' => 'fitem'));
-
}
return html_writer::tag('fieldset', html_writer::tag('div', $comment . $mark,
* Updates an existing category with given params
*/
public function update_category($updateid, $newparent, $newname, $newinfo) {
- global $CFG, $QTYPES, $DB;
+ global $CFG, $DB;
if (empty($newname)) {
print_error('categorynamecantbeblank', 'quiz');
}
if ($oldcat->name != $cat->name) {
$where = "qtype = 'random' AND category = ? AND " . $DB->sql_compare_text('questiontext') . " = ?";
- $randomqname = $QTYPES[RANDOM]->question_name($cat, false);
+ $randomqtype = question_bank::get_qtype('random');
+ $randomqname = $randomqtype->question_name($cat, false);
$DB->set_field_select('question', 'name', $randomqname, $where, array($cat->id, '0'));
- $randomqname = $QTYPES[RANDOM]->question_name($cat, true);
+ $randomqname = $randomqtype->question_name($cat, true);
$DB->set_field_select('question', 'name', $randomqname, $where, array($cat->id, '1'));
}
* @author added by Howard Miller June 2004
*/
function get_questions_category( $category, $noparent=false, $recurse=true, $export=true ) {
-
- global $QTYPES, $DB;
+ global $DB;
// questions will be added to an array
$qresults = array();
// iterate through questions, getting stuff we need
foreach($questions as $question) {
- $questiontype = $QTYPES[$question->qtype];
$question->export_process = $export;
- $questiontype->get_question_options($question);
+ question_bank::get_qtype($question->qtype)->get_question_options($question);
$qresults[] = $question;
}
}
* the qtype radio buttons.
*/
function print_choose_qtype_to_add_form($hiddenparams) {
- global $CFG, $QTYPES, $PAGE, $OUTPUT;
+ global $CFG, $PAGE, $OUTPUT;
$PAGE->requires->js('/question/qbank.js');
echo '<div id="chooseqtypehead" class="hd">' . "\n";
echo $OUTPUT->heading(get_string('chooseqtypetoadd', 'question'), 3);
echo '<div class="qtypes">' . "\n";
echo '<div class="instruction">' . get_string('selectaqtypefordescription', 'question') . "</div>\n";
echo '<div class="realqtypes">' . "\n";
- $types = question_type_menu();
$fakeqtypes = array();
- foreach ($types as $qtype => $localizedname) {
- if ($QTYPES[$qtype]->is_real_question_type()) {
- print_qtype_to_add_option($qtype, $localizedname);
+ foreach (question_bank::get_creatable_qtypes() as $qtype) {
+ if (question_bank::get_qtype($qtype)->is_real_question_type()) {
+ print_qtype_to_add_option($qtype);
} else {
- $fakeqtypes[$qtype] = $localizedname;
+ $fakeqtypes[] = $qtype;
}
}
echo "</div>\n";
echo '<div class="fakeqtypes">' . "\n";
foreach ($fakeqtypes as $qtype => $localizedname) {
- print_qtype_to_add_option($qtype, $localizedname);
+ print_qtype_to_add_option($qtype);
}
echo "</div>\n";
echo "</div>\n";
/**
* Private function used by the preceding one.
* @param $qtype the question type.
- * @param $localizedname the localized name of this question type.
*/
-function print_qtype_to_add_option($qtype, $localizedname) {
- global $QTYPES;
+function print_qtype_to_add_option($qtype) {
echo '<div class="qtypeoption">' . "\n";
- echo '<label for="qtype_' . $qtype . '">';
- echo '<input type="radio" name="qtype" id="qtype_' . $qtype . '" value="' . $qtype . '" />';
+ echo '<label for="qtype_' . $qtype->name() . '">';
+ echo '<input type="radio" name="qtype" id="qtype_' . $qtype->name() . '" value="' . $qtype->name() . '" />';
echo '<span class="qtypename">';
$fakequestion = new stdClass;
- $fakequestion->qtype = $qtype;
+ $fakequestion->qtype = $qtype->name();
print_question_icon($fakequestion);
- echo $localizedname . '</span><span class="qtypesummary">' . get_string($qtype . 'summary', 'qtype_' . $qtype);
+ echo $qtype->menu_name() . '</span><span class="qtypesummary">' .
+ get_string($qtype->name() . 'summary', 'qtype_' . $qtype->name());
echo "</span></label>\n";
echo "</div>\n";
}
private static $testmode = false;
private static $testdata = array();
+ private static $questionconfig = null;
+
/**
* Get the question type class for a particular question type.
* @param string $qtypename the question type name. For example 'multichoice' or 'shortanswer'.
return self::$questiontypes[$qtypename];
}
+ /**
+ * Load the question configuration data from config_plugins.
+ * @return object get_config('question') with caching.
+ */
+ protected static function get_config() {
+ if (is_null(self::$questionconfig)) {
+ $questionconfig = get_config('question');
+ }
+ return $questionconfig;
+ }
+
/**
* @param string $qtypename the internal name of a question type. For example multichoice.
* @return boolean whether users are allowed to create questions of this type.
*/
public static function qtype_enabled($qtypename) {
- return true; // TODO
+ $config = self::get_config();
+ $enabledvar = $qtypename . '_disabled';
+ return self::qtype_exists($qtypename) && empty($config->$enabledvar) &&
+ self::get_qtype($qtypename)->menu_name() != '';
+ }
+
+ /**
+ * @param string $qtypename the internal name of a question type. For example multichoice.
+ * @return boolean whether this question type exists.
+ */
+ public static function qtype_exists($qtypename) {
+ return array_key_exists($qtypename, get_plugin_list('qtype'));
}
/**
* @return string the human_readable name of this question type, from the language pack.
*/
public static function get_qtype_name($qtypename) {
- return self::get_qtype($qtypename)->menu_name();
+ return self::get_qtype($qtypename)->local_name();
}
/**
return $qtypes;
}
+ /**
+ * @return array all the question types that users are allowed to create,
+ * sorted into the preferred order set on the admin screen.
+ */
+ public static function get_creatable_qtypes() {
+ $config = self::get_config();
+ $allqtypes = self::get_all_qtypes();
+
+ $sortorder = array();
+ $otherqtypes = array();
+ foreach ($allqtypes as $name => $qtype) {
+ if (!self::qtype_enabled($name)) {
+ unset($allqtypes[$name]);
+ continue;
+ }
+ $sortvar = $name . '_sortorder';
+ if (isset($config->$sortvar)) {
+ $sortorder[$config->$sortvar] = $name;
+ } else {
+ $otherqtypes[$name] = $qtype->local_name();
+ }
+ }
+
+ ksort($sortorder);
+ textlib_get_instance()->asort($otherqtypes);
+
+ $creatableqtypes = array();
+ foreach ($sortorder as $name) {
+ $creatableqtypes[$name] = $allqtypes[$name];
+ }
+ foreach ($otherqtypes as $name => $notused) {
+ $creatableqtypes[$name] = $allqtypes[$name];
+ }
+ return $qtypes;
+ }
+
/**
* Load the question definition class(es) belonging to a question type. That is,
* include_once('/question/type/' . $qtypename . '/question.php'), with a bit
* @return array questionid => questionid.
*/
public function get_questions_from_categories($categoryids, $extraconditions) {
+ global $DB;
+
if (is_array($categoryids)) {
$categoryids = implode(',', $categoryids);
}
"category IN ($categoryids)
AND parent = 0
AND hidden = 0
- $extraconditions", '', 'id,id AS id2');
- if (!$questionids) {
- $questionids = array();
- }
+ $extraconditions", array(), '', 'id,id AS id2');
return $questionids;
}
}
q.id,
summarystate
-ORDER BY
+ORDER BY
qa.slot,
qa.questionid,
q.name,
* @param boolean $newstate the new state of the flag. true = flagged.
*/
public function update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate) {
- if (!$this->db->record_exists('question_attempts', array('id' => $qaid,
+ if (!$this->db->record_exists('question_attempts', array('id' => $qaid,
'questionusageid' => $qubaid, 'questionid' => $questionid, 'slot' => $slot))) {
throw new Exception('invalid ids');
}
*/
public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate) {
// Check the checksum - it is very hard to know who a question session belongs
- // to, so we require that checksum parameter is matches an md5 hash of the
+ // to, so we require that checksum parameter is matches an md5 hash of the
// three ids and the users username. Since we are only updating a flag, that
// probably makes it sufficiently difficult for malicious users to toggle
// other users flags.
$qa->set_flagged($flagged);
}
}
-
}
/**
/**
* @return array subpartid => object with fields
- * ->responseclassid the
+ * ->responseclassid matches one of the values returned from quetion_type::get_possible_responses.
* ->response the actual response the student gave to this part, as a string.
* ->fraction the credit awarded for this subpart, may be null.
* returns an empty array if no analysis is possible.
$this->logger->set_current_attempt_id(null);
if (empty($qas)) {
- $this->logger->log_assumption("All the question attempts for
+ $this->logger->log_assumption("All the question attempts for
attempt {$attempt->id} at quiz {$attempt->quiz} were missing.
Deleting this attempt", $attempt->id);
// Somehow, all the question attempt data for this quiz attempt
continue;
}
if (!array_key_exists($questionid, $qas)) {
- $this->logger->log_assumption("Supplying minimal open state for
+ $this->logger->log_assumption("Supplying minimal open state for
question {$questionid} in attempt {$attempt->id} at quiz
{$attempt->quiz}, since the session was missing.", $attempt->id);
try {
// We store the reponses by turning the associative array $state->responses
// into a string as follows. For example, array('f2' => 'No, never - ever', 'f1' => '10')
// becomes 'f1-10,f2-No\, never - ever'. That is, comma separated pairs, sorted by key,
- // key and value linked with a '-', commas in vales escaped with '\'.
+ // key and value linked with a '-', commas in vales escaped with '\'.
// Deal with special case: no responses at all.
if (empty($answer)) {
$questiontext = array();
$questiontext['text'] = implode('', $lines);
$questiontext['format'] = 0 ;
- $questiontext['itemid'] = '';
+ $questiontext['itemid'] = '';
$question = qtype_multianswer_extract_question($questiontext);
$question->questiontext = $question->questiontext['text'] ;
$question->questiontextformat = 0 ;
$questiontext = array();
$questiontext['text'] = $this->import_text($questions['#']['questiontext'][0]['#']['text']);
$questiontext['format'] = '1';
- $questiontext['itemid'] = '';
+ $questiontext['itemid'] = '';
$qo = qtype_multianswer_extract_question($questiontext);
// 'header' parts particular to multianswer
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 +-
+DONE mod/quiz/edit.js | 131 +
+DONE mod/quiz/edit.php | 118 +-
+DONE mod/quiz/editlib.php | 79 +-
DONE mod/quiz/index.php | 6 +-
DONE mod/quiz/jstimer.php | 58 -
DONE mod/quiz/lib.php | 454 ++--
DONE question/type/oumultiresponse/styles.css | 14 +
DONE question/type/oumultiresponse/version.php | 29 +
- question/type/random/edit_random_form.php | 7 +-
- question/type/random/lang/en_utf8/qtype_random.php | 9 +
- question/type/random/questiontype.php | 430 +---
- question/type/random/simpletest/testquestiontype.php | 61 +
+DONE question/type/random/edit_random_form.php | 7 +-
+DONE question/type/random/lang/en_utf8/qtype_random.php | 9 +
+DONE question/type/random/questiontype.php | 430 +---
+DONE question/type/random/simpletest/testquestiontype.php | 61 +
question/type/randomsamatch/db/upgrade.php | 2 -
question/type/randomsamatch/edit_randomsamatch_form.php | 3 +-
$data = array();
$j = (($this->noofitems) * count($this->datasetdefs))+1;
foreach ($this->datasetdefs as $defkey => $datasetdef){
- if($datasetdef->category |= 0 ) {
+ if($datasetdef->category |= 0 ) {
$name = get_string('sharedwildcardname', 'qtype_calculated',$datasetdef->name) ;
}else {
$name = get_string('wildcard', 'qtype_calculated', $datasetdef->name);
$qtype = $this->qtype();
$langfile = "qtype_$qtype";
- $mform = $this->_form;
+ $mform = $this->_form;
// Standard fields at the start of the form.
$mform->addElement('header', 'generalheader', get_string("general", 'form'));
}
public function set_data($question) {
- global $QTYPES;
- $QTYPES[$question->qtype]->set_default_options($question);
+ question_bank::get_qtype($question->qtype)->set_default_options($question);
// prepare question text
$draftid = file_get_submitted_draft_itemid('questiontext');
}
// Set any options.
- $extra_question_fields = $QTYPES[$question->qtype]->extra_question_fields();
+ $extra_question_fields = question_bank::get_qtype($question->qtype)->extra_question_fields();
if (is_array($extra_question_fields) && !empty($question->options)) {
array_shift($extra_question_fields);
foreach ($extra_question_fields as $field) {
array('class' => 'qtext'));
$result .= html_writer::start_tag('div', array('class' => 'ablock clearfix'));
- $result .= html_writer::tag('div', get_string('answer', 'question'),
+ $result .= html_writer::tag('div', get_string('answer', 'question'),
array('class' => 'prompt'));
$result .= html_writer::tag('div', $answer, array('class' => 'answer'));
$result .= html_writer::end_tag('div');
private $htmltclosetags = '~<\s*/\s*\w\s*.*?>|<\s*br\s*>~';
/** @var string regex to select text like [[cat]] (including the square brackets). */
- private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';
+ private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';
private function get_html_tags($text) {
$textarray = array();
// before any action that may take longer time to finish.
function xmldb_qtype_match_upgrade($oldversion) {
- global $CFG, $DB, $QTYPES;
+ global $CFG, $DB;
$dbman = $DB->get_manager();
// Save a partial response.
$this->process_submission(array('sub0' => $orderforchoice[1],
'sub1' => $orderforchoice[2], 'sub2' => '0', 'sub3' => '0'));
-
// Verify.
$this->check_current_state(question_state::$todo);
* @param object $mform the form being built.
*/
protected function definition_inner($mform) {
- global $QTYPES;
-
$menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice'));
$mform->addElement('select', 'single', get_string('answerhowmany', 'qtype_multichoice'), $menu);
$mform->setDefault('single', 1);
if (!empty($right)) {
return get_string('correctansweris', 'qtype_multichoice',
implode(', ', $right));
-
}
return '';
}
$string['unitmandatory'] = 'Mandatory';
$string['unitmandatory_help'] = '
-* The response will be graded using the unit written.
+* The response will be graded using the unit written.
* The unit penalty will be applied if the unit field is empty
$string['unitoptional_help'] = '
* If the unit field is not empty, the response will be graded using this unit.
-* If the unit is badly written or unknown, the response will be considered as non valid.
+* If the unit is badly written or unknown, the response will be considered as non valid.
';
$string['unitused'] = '<STRONG>UNIT USED</STRONG>';
$string['unituses'] = 'Unit uses';
$string['validnumberformats_help'] = '
* regular numbers 13500.67 : 13 500.67 : 13500,67: 13 500,67
-* if you use , as thousand separator *always* put the decimal . as in
+* if you use , as thousand separator *always* put the decimal . as in
13,500.67 : 13,500.
-
-* for exponent form, say 1.350067 * 10<sup>4</sup>, use
+
+* for exponent form, say 1.350067 * 10<sup>4</sup>, use
1.350067 E4 : 1.350067 E04 ';
$string['validnumbers'] = ' 13500.67 : 13 500.67 : 13,500.67 : 13500,67: 13 500,67 : 1.350067 E4 : 1.350067 E04 ';
$mform->setType('engineid', PARAM_INT);
$mform->addRule('engineid', null, 'required', null, 'client');
$mform->addHelpButton('engineid', 'questionengine', 'qtype_opaque');
-
+
$mform->addElement('text', 'remoteid', get_string('questionid', 'qtype_opaque'), array('size' => 50));
$mform->setType('remoteid', PARAM_RAW);
$mform->addRule('remoteid', null, 'required', null, 'client');
$engine2->questionengines = array(
'http://ltsweb2.open.ac.uk/om-qe/services/Om');
$this->assertFalse($manager->is_same_engine($engine1, $engine2));
-
}
}
class qtype_oumultiresponse_edit_form extends question_edit_form {
public function definition_inner($mform) {
- global $QTYPES;
-
$mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0,1));
$mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice');
$mform->setDefault('shuffleanswers', 1);
/**
* This method should return all the possible types of response that are
- * recognised for this question.
+ * recognised for this question.
*
* The question is modelled as comprising one or more subparts. For each
* subpart, there are one or more classes that that students response
<?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/>.
+
+
/**
* Defines the editing form for the random question type.
*
* If your question type does not support all these fields, then you can
* override this method and remove the ones you don't want with $mform->removeElement().
*/
- function definition() {
+ public function definition() {
global $COURSE, $CFG;
$qtype = $this->qtype();
$mform->closeHeaderBefore('buttonar');
}
- function set_data($question) {
+ public function set_data($question) {
$question->questiontext = array('text' => $question->questiontext);
// We don't want the complex stuff in the base class to run.
moodleform::set_data($question);
}
- function validation($fromform, $files) {
+ public function validation($fromform, $files) {
//validation of category
//is not relevant for this question type
return array();
}
- function qtype() {
+ public function qtype() {
return 'random';
}
}
/**
* Strings for component 'qtype_random', language 'en', branch 'MOODLE_20_STABLE'
*
- * @package qtype_random
- * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package qtype
+ * @subpackage random
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['configselectmanualquestions'] = 'Can the random question type select a manually graded question when it is making its random choice of a question from a category?';
<?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/>.
+
+
/**
- * Class for the random question type.
+ * Question type class for the random question type.
*
- * The random question type does not have any options. When the question is
- * attempted, it picks a question at random from the category it is in (and
- * optionally its subcategories). For details see create_session_and_responses.
- * Then all other method calls as delegated to that other question.
+ * @package qtype_random
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * The random question type.
+ *
+ * This question type does not have a question definition class, nor any
+ * renderers. When you load a question of this type, it actually loads a
+ * question chosen randomly from a particular category in the question bank.
*
- * @package questionbank
- * @subpackage questiontypes
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class random_qtype extends default_questiontype {
+class qtype_random extends question_type {
+ /** @var string comma-separated list of qytpe names not to select, can be used in SQL. */
protected $excludedqtypes = null;
- protected $manualqtypes = null;
- // Caches questions available as randoms sorted by category
- // This is a 2-d array. The first key is question category, and the
- // second is whether to include subcategories.
- private $catrandoms = array();
+ /** @var string comma-separated list of manually graded qytpe names, can be used in SQL. */
+ protected $manualqtypes = null;
- function name() {
- return 'random';
- }
+ /**
+ * Cache of availabe question ids from a particular category.
+ * @var array two-dimensional array. The first key is a category id, the
+ * second key is wether subcategories should be included.
+ */
+ private $availablequestionsbycategory = array();
- function menu_name() {
+ public function menu_name() {
// Don't include this question type in the 'add new question' menu.
return false;
}
- function show_analysis_of_responses() {
+ public function is_manual_graded() {
return true;
}
- function is_manual_graded() {
- return true;
+ public function is_usable_by_random() {
+ return false;
}
- function is_question_manual_graded($question, $otherquestionsinuse) {
+ public function is_question_manual_graded($question, $otherquestionsinuse) {
global $DB;
// We take our best shot at working whether a particular question is manually
// graded follows: We look to see if any of the questions that this random
AND qtype IN ($this->manualqtypes)");
}
- function is_usable_by_random() {
- return false;
- }
-
/**
* This method needs to be called before the ->excludedqtypes and
* ->manualqtypes fields can be used.
*/
- function init_qtype_lists() {
- global $QTYPES;
- if (is_null($this->excludedqtypes)) {
- $excludedqtypes = array();
- $manualqtypes = array();
- foreach ($QTYPES as $qtype) {
- $quotedname = "'" . $qtype->name() . "'";
- if (!$qtype->is_usable_by_random()) {
- $excludedqtypes[] = $quotedname;
- } else if ($qtype->is_manual_graded()) {
- $manualqtypes[] = $quotedname;
- }
+ protected function init_qtype_lists() {
+ if (!is_null($this->excludedqtypes)) {
+ return; // Already done.
+ }
+ $excludedqtypes = array();
+ $manualqtypes = array();
+ foreach (question_bank::get_all_qtypes() as $qtype) {
+ $quotedname = "'" . $qtype->name() . "'";
+ if (!$qtype->is_usable_by_random()) {
+ $excludedqtypes[] = $quotedname;
+ } else if ($qtype->is_manual_graded()) {
+ $manualqtypes[] = $quotedname;
}
- $this->excludedqtypes = implode(',', $excludedqtypes);
- $this->manualqtypes = implode(',', $manualqtypes);
}
+ $this->excludedqtypes = implode(',', $excludedqtypes);
+ $this->manualqtypes = implode(',', $manualqtypes);
}
function display_question_editing_page(&$mform, $question, $wizardnow){
$mform->display();
}
- function get_question_options(&$question) {
- // Don't do anything here, because the random question has no options.
- // Everything is handled by the create- or restore_session_and_responses
- // functions.
+ public function get_question_options($question) {
return true;
}
* @param boolean $includesubcategories whether this question also picks from subcategories.
* @return string the name this question should have.
*/
- function question_name($category, $includesubcategories) {
+ public function question_name($category, $includesubcategories) {
if ($includesubcategories) {
$string = 'randomqplusname';
} else {
return get_string($string, 'qtype_random', $category->name);
}
- function save_question($question, $form) {
- $form->name = '';
- // Name is not a required field for random questions, but parent::save_question
- // Assumes that it is.
- return parent::save_question($question, $form);
+ protected function set_selected_question_name($question, $randomname) {
+ $a = new stdClass;
+ $a->randomname = $randomname;
+ $a->questionname = $question->name;
+ $question->name = get_string('selectedby', 'qtype_random', $a);
}
- function save_question_options($question) {
- global $DB;
+ public function save_question($question, $form, $course) {
+ $form->name = '';
+ // Name is not a required field for random questions, but
+ // parent::save_question Assumes that it is.
+ return parent::save_question($question, $form, $course);
+ }
+ public function save_question_options($question) {
// No options, as such, but we set the parent field to the question's
// own id. Setting the parent field has the effect of hiding this
// question in various places.
$updateobject->parent = $question->id;
// We also force the question name to be 'Random (categoryname)'.
- if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
- print_error('cannotretrieveqcat', 'question');
- }
+ $category = $DB->get_record('question_categories', array('id' => $question->category), '*', MUST_EXIST);
$updateobject->name = $this->question_name($category, !empty($question->questiontext));
return $DB->update_record('question', $updateobject);
}
* @param string $questionsinuse comma-separated list of question ids to exclude from consideration.
* @return array of question records.
*/
- function get_usable_questions_from_category($categoryid, $subcategories, $questionsinuse) {
- global $DB;
+ public function get_available_questions_from_category($categoryid, $subcategories) {
+ if (isset($this->availablequestionsbycategory[$categoryid][$subcategories])) {
+ return $this->availablequestionsbycategory[$categoryid][$subcategories];
+ }
+
$this->init_qtype_lists();
if ($subcategories) {
- $categorylist = question_categorylist($categoryid);
+ $categoryids = question_categorylist($categoryid);
} else {
- $categorylist = $categoryid;
- }
- if (!$catrandoms = $DB->get_records_select('question',
- "category IN ($categorylist)
- AND parent = 0
- AND hidden = 0
- AND id NOT IN ($questionsinuse)
- AND qtype NOT IN ($this->excludedqtypes)", null, '', 'id')) {
- $catrandoms = array();
- }
- return $catrandoms;
- }
-
- function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
- global $QTYPES, $DB;
- // Choose a random question from the category:
- // We need to make sure that no question is used more than once in the
- // quiz. Therfore the following need to be excluded:
- // 1. All questions that are explicitly assigned to the quiz
- // 2. All random questions
- // 3. All questions that are already chosen by an other random question
- // 4. Deleted questions
- if (!isset($cmoptions->questionsinuse)) {
- $cmoptions->questionsinuse = $attempt->layout;
- }
-
- if (!isset($this->catrandoms[$question->category][$question->questiontext])) {
- $catrandoms = $this->get_usable_questions_from_category($question->category,
- $question->questiontext == "1", $cmoptions->questionsinuse);
- $this->catrandoms[$question->category][$question->questiontext] = swapshuffle_assoc($catrandoms);
+ $categoryids = $categoryid;
}
- while ($wrappedquestion = array_pop(
- $this->catrandoms[$question->category][$question->questiontext])) {
- if (!preg_match("~(^|,)$wrappedquestion->id(,|$)~", $cmoptions->questionsinuse)) {
- /// $randomquestion is not in use and will therefore be used
- /// as the randomquestion here...
- $wrappedquestion = $DB->get_record('question', array('id' => $wrappedquestion->id));
- global $QTYPES;
- $QTYPES[$wrappedquestion->qtype]
- ->get_question_options($wrappedquestion);
- $QTYPES[$wrappedquestion->qtype]
- ->create_session_and_responses($wrappedquestion,
- $state, $cmoptions, $attempt);
- $wrappedquestion->name_prefix = $question->name_prefix;
- $wrappedquestion->maxgrade = $question->maxgrade;
- $cmoptions->questionsinuse .= ",$wrappedquestion->id";
- $state->options->question = &$wrappedquestion;
- return true;
- }
- }
- $question->questiontext = '<span class="notifyproblem">'.
- get_string('toomanyrandom', 'quiz'). '</span>';
- $question->qtype = 'description';
- $state->responses = array('' => '');
- return true;
+ $questionids = question_bank::get_finder()->get_questions_from_categories(
+ $categoryids, 'qtype NOT IN (' . $this->excludedqtypes . ')');
+ $this->availablequestionsbycategory[$categoryid][$subcategories] = $questionids;
+ return $questionids;
}
- function restore_session_and_responses(&$question, &$state) {
- /// The raw response records for random questions come in two flavours:
- /// ---- 1 ----
- /// For responses stored by Moodle version 1.5 and later the answer
- /// field has the pattern random#-* where the # part is the numeric
- /// question id of the actual question shown in the quiz attempt
- /// and * represents the student response to that actual question.
- /// ---- 2 ----
- /// For responses stored by older Moodle versions - the answer field is
- /// simply the question id of the actual question. The student response
- /// to the actual question is stored in a separate response record.
- /// -----------------------
- /// This means that prior to Moodle version 1.5, random questions needed
- /// two response records for storing the response to a single question.
- /// From version 1.5 and later the question type random works like all
- /// the other question types in that it now only needs one response
- /// record per question.
- global $QTYPES, $DB, $OUTPUT;
- if (!preg_match('~^random([0-9]+)-(.*)$~', $state->responses[''], $answerregs)) {
- if (empty($state->responses[''])) {
- // This is the case if there weren't enough questions available in the category.
- $question->questiontext = '<span class="notifyproblem">'.
- get_string('toomanyrandom', 'quiz'). '</span>';
- $question->qtype = 'description';
- return true;
- }
- // this must be an old-style state which stores only the id for the wrapped question
- if (!$wrappedquestion = $DB->get_record('question', array('id' => $state->responses['']))) {
- echo $OUTPUT->notification("Can not find wrapped question {$state->responses['']}");
- }
- // In the old model the actual response was stored in a separate entry in
- // the state table and fortunately there was only a single state per question
- if (!$state->responses[''] = $DB->get_field('question_states', 'answer', array('attempt' => $state->attempt, 'question' => $wrappedquestion->id))) {
- echo $OUTPUT->notification("Wrapped state missing");
- }
- } else {
- if (!$wrappedquestion = $DB->get_record('question', array('id' => $answerregs[1]))) {
- // The teacher must have deleted this question by mistake
- // Convert it into a description type question with an explanation to the student
- $wrappedquestion = clone($question);
- $wrappedquestion->id = $answerregs[1];
- $wrappedquestion->questiontext = get_string('questiondeleted', 'quiz');
- $wrappedquestion->qtype = 'missingtype';
- }
- $state->responses[''] = (false === $answerregs[2]) ? '' : $answerregs[2];
- }
-
- if (!$QTYPES[$wrappedquestion->qtype]
- ->get_question_options($wrappedquestion)) {
- return false;
- }
-
- if (!$QTYPES[$wrappedquestion->qtype]
- ->restore_session_and_responses($wrappedquestion, $state)) {
- return false;
- }
- $wrappedquestion->name_prefix = $question->name_prefix;
- $wrappedquestion->maxgrade = $question->maxgrade;
- $state->options->question = &$wrappedquestion;
- return true;
+ public function make_question($questiondata) {
+ return $this->choose_other_question($questiondata, array());
}
- function save_session_and_responses(&$question, &$state) {
- global $QTYPES, $DB;
- $wrappedquestion = &$state->options->question;
-
- // Trick the wrapped question into pretending to be the random one.
- $realqid = $wrappedquestion->id;
- $wrappedquestion->id = $question->id;
- $QTYPES[$wrappedquestion->qtype]
- ->save_session_and_responses($wrappedquestion, $state);
+ /**
+ * Load the definition of another question picked randomly by this question.
+ * @param object $questiondata the data defining a random question.
+ * @param array $excludedquestions of question ids. We will no pick any question whose id is in this list.
+ * @param boolean $allowshuffle if false, then any shuffle option on the selected quetsion is disabled.
+ * @return question_definition|null the definition of the question that was
+ * selected, or null if no suitable question could be found.
+ */
+ public function choose_other_question($questiondata, $excludedquestions, $allowshuffle = true) {
+ $available = $this->get_available_questions_from_category($questiondata->category,
+ !empty($questiondata->questiontext));
+ shuffle($available);
+
+ foreach ($available as $questionid) {
+ if (in_array($questionid, $excludedquestions)) {
+ continue;
+ }
- // Read what the wrapped question has just set the answer field to
- // (if anything)
- $response = $DB->get_field('question_states', 'answer', array('id' => $state->id));
- if(false === $response) {
- return false;
+ $question = question_bank::load_question($questionid, $allowshuffle);
+ $this->set_selected_question_name($question, $questiondata->name);
+ return $question;
}
-
- // Prefix the answer field...
- $response = "random$realqid-$response";
-
- // ... and save it again.
- $DB->set_field('question_states', 'answer', $response, array('id' => $state->id));
-
- // Restore the real id
- $wrappedquestion->id = $realqid;
- return true;
+ return null;
}
- function get_correct_responses(&$question, &$state) {
- global $QTYPES;
- $wrappedquestion = &$state->options->question;
- return $QTYPES[$wrappedquestion->qtype]
- ->get_correct_responses($wrappedquestion, $state);
+ function get_random_guess_score($questiondata) {
+ return null;
}
-
- // ULPGC ecastro
- function get_all_responses(&$question, &$state){
- global $QTYPES;
- $wrappedquestion = &$state->options->question;
- return $QTYPES[$wrappedquestion->qtype]
- ->get_all_responses($wrappedquestion, $state);
- }
-
- // ULPGC ecastro
- function get_actual_response(&$question, &$state){
- global $QTYPES;
- $wrappedquestion = &$state->options->question;
- return $QTYPES[$wrappedquestion->qtype]
- ->get_actual_response($wrappedquestion, $state);
- }
-
- function get_html_head_contributions(&$question, &$state) {
- global $QTYPES;
- $wrappedquestion = &$state->options->question;
- return $QTYPES[$wrappedquestion->qtype]
- ->get_html_head_contributions($wrappedquestion, $state);
- }
-
- function print_question(&$question, &$state, &$number, $cmoptions, $options) {
- global $QTYPES;
- $wrappedquestion = &$state->options->question;
- $wrappedquestion->randomquestionid = $question->id;
- $QTYPES[$wrappedquestion->qtype]
- ->print_question($wrappedquestion, $state, $number, $cmoptions, $options);
- }
-
- function grade_responses(&$question, &$state, $cmoptions) {
- global $QTYPES;
- $wrappedquestion = &$state->options->question;
- return $QTYPES[$wrappedquestion->qtype]
- ->grade_responses($wrappedquestion, $state, $cmoptions);
- }
-
- function get_texsource(&$question, &$state, $cmoptions, $type) {
- global $QTYPES;
- $wrappedquestion = &$state->options->question;
- return $QTYPES[$wrappedquestion->qtype]
- ->get_texsource($wrappedquestion, $state, $cmoptions, $type);
- }
-
- function compare_responses(&$question, $state, $teststate) {
- global $QTYPES;
- $wrappedquestion = &$teststate->options->question;
- return $QTYPES[$wrappedquestion->qtype]
- ->compare_responses($wrappedquestion, $state, $teststate);
- }
-
- /**
- * For random question type return empty string which means won't calculate.
- * @param object $question
- * @return mixed either a integer score out of 1 that the average random
- * guess by a student might give or an empty string which means will not
- * calculate.
- */
- function get_random_guess_score($question) {
- return '';
- }
-
}
-//// END OF CLASS ////
-
-//////////////////////////////////////////////////////////////////////////
-//// INITIATION - Without this line the question type is not in use... ///
-//////////////////////////////////////////////////////////////////////////
-question_register_questiontype(new random_qtype());
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Unit tests for the random question type class.
+ *
+ * @package qtype_random
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/random/questiontype.php');
+
+/**
+ * Unit tests for the random question type class.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_random_test extends UnitTestCase {
+ var $qtype;
+
+ public function setUp() {
+ $this->qtype = new qtype_random();
+ }
+
+ public function tearDown() {
+ $this->qtype = null;
+ }
+
+ public function test_name() {
+ $this->assertEqual($this->qtype->name(), 'random');
+ }
+
+ public function test_can_analyse_responses() {
+ $this->assertFalse($this->qtype->can_analyse_responses());
+ }
+
+ public function test_get_random_guess_score() {
+ $this->assertNull($this->qtype->get_random_guess_score(null));
+ }
+
+ public function test_get_possible_responses() {
+ $this->assertEqual(array(), $this->qtype->get_possible_responses(null));
+ }
+}
$this->assertTrue($hint->shownumcorrect);
$this->assertTrue($hint->clearwrong);
}
-
}