Browse files

quiz editing: MDL-17285 This is Olli Savolainen's new interface for e…

…diting quizzes.

This was started and usability tested as a Finnish Summer of Code project, and then Olli did further work on it in his own time to get it in shape for inclusion in Moodle 2.0. I reviewed all the code. There are a number of minor outstanding issues that will be fixed soon. See the subtasks of MDL-17284 for a list.

The goal of these changes is to:
* help teachers new to Moodle, so when they first see the quiz editing page, they don't go "Huh! What on earth am I supposed to do here?"
* help novice Moodle users understand and learn to use some of the more advanced quiz feature;
* but, without slowing down more experienced quiz users.

Naturally, with ambitous goals like that, we won't have managed to satisy everybody, but I think this change is a big step in the right direction.

There is extensive documentation on this project at
  • Loading branch information...
tjhunt committed Nov 20, 2008
1 parent b7cebc8 commit fa583f5f6eb3b4c9f624e77df0cfb9f9e705e6c3
@@ -0,0 +1,25 @@
<h1>Basic ideas of making quizzes</h1>
<p>The main concepts while managing quiz content (questions) are:</p>
<li>Quiz and pages</li>
<li>Question bank and its categories</li>
<li>Random question</li>
<p>You can think of a <strong>quiz</strong>, in essence, to be like a
traditional pen&amp;paper quiz (or an exam/test). It contains questions.
You can divide the questions in a quiz on several <strong>pages</strong>
or you can keep them all on one page.
With Moodle Quiz, you can also give the grading beforehand for the
questions in a quiz (as well as the total for the entire quiz).</p>
<p>When you create questions, they are stored in the <strong>Question
Bank</strong>. In the Question Bank you can create categories, which are similar
to folders. You can use them to create a hierarchy for organizing
questions, for example, by topic. When you create a question into
an exam, a copy of it is stored in the Question Bank. If you decide to
remove your question from an exam, it will still be intact in the
question bank until you delete it from there, too.</p>
<p>You can use <strong>Random Questions</strong>, if you want a question to
vary between different attempts students make at the quiz (for example,
to avoid cheating by means of students copying questions to each other).
Just create a random question in the quiz and add questions in the
category of the random question.</p>
@@ -678,5 +678,44 @@
$string['youneedtoenrol'] = 'You need to enrol in this course before you can attempt this quiz';
$string['yourfinalgradeis'] = 'Your final grade for this quiz is $a.';
$string['zerosignificantfiguresnotallowed'] = 'The correct answer cannot have zero significant figures!';
$string['totalquestionsinrandomqcategory'] = 'Total of $a questions in category.';
$string['selectquestiontype'] = ' -- Select question type -- ';
$string['adddescriptionlabel'] = 'Add description/label';
$string['addrandomquestion'] = 'Add random question';
$string['addrandomquestiontoquiz'] = 'Add random question to quiz $a';
$string['parentcategory'] = 'Parent category';
$string['selectcategory'] = 'Select category';
$string['createcategoryfornewrandomquestion'] = 'Create a question category for the new random question';
$string['qname'] = 'name';
$string['qtypename'] = 'type, name';
$string['age'] = 'age';
$string['sortquestionsbyx'] = 'Sort questions by: $a';
$string['addpagehere'] = 'Add page here';
$string['questionbankmanagement'] = 'Question Bank management';
$string['totalpoints'] = 'Total of grades';
$string['quizwillopen'] = 'This quiz will open $a';
$string['quizopenwillclose'] = 'This quiz is open, will close on $a at ';
$string['basicideasofquiz'] = 'Basic ideas of making quizzes';
$string['noquestionsinquiz'] = 'There are no questions in this quiz.';
$string['addquestion'] = 'Add question';
$string['questiontextisempty'] = '[Empty question text]';
$string['addrandomfromcategory'] = 'Add random questions from category';
$string['fromcategory'] = 'from category';
$string['questionbankcontents'] = 'Question Bank contents';
$string['quizorderrandom'] = '* Order of quiz is shuffled';
$string['quizordernotrandom'] = 'Order of quiz not shuffled';
$string['repaginatecommand'] = 'Repaginate';
$string['orderandpaging'] = 'Order and paging';
$string['noquestionsonpage'] = 'Empty page';
$string['orderingquiz'] = 'Order and paging';
$string['moveselectedonpage'] = 'Move selected questions to page';
$string['addnewpagesafterselected'] = 'Add new pages after selected questions';
$string['reorderquestions'] = 'Reorder questions';
$string['noquestionsnotinuse'] = 'This random question is not in use, since its category is empty.';
$string['addnewquestionsqbank'] = 'Add questions to the category $a in the \'Question bank contents\' tool >>';
$string['empty'] = 'Empty';
$string['quizopened'] = 'This quiz is open.';
$string['areyousuredeleteselected'] = 'Are you sure you want to delete the selected questions?';
$string['questionsperpageselected'] = 'Questions per page has been set so the paging is currently fixed. As a result, the paging controls have been disabled. You can change this in ';
$string['shufflequestionsselected'] = '* Shuffle questions has been set so question order is random. As a result, the button Reorder questions has been disabled. You can change this in ';
@@ -717,7 +717,7 @@ function close_this_window() {
* @param mixed $listbox if false, display as a dropdown menu. If true, display as a list box.
* By default, the list box will have a number of rows equal to min(10, count($options)), but if
* $listbox is an integer, that number is used for size instead.
* @param
* @param
function choose_from_menu ($options, $name, $selected='', $nothing='choose', $script='',
$nothingvalue='0', $return=false, $disabled=false, $tabindex=0,
@@ -990,26 +990,30 @@ function print_textfield ($name, $value, $alt = '',$size=50,$maxlength=0, $retur
* Implements a complete little popup form
* Implements a complete little form with a dropdown menu. When JavaScript is on
* selecting an option from the dropdown automatically submits the form (while
* avoiding the usual acessibility problems with this appoach). With JavaScript
* off, a 'Go' button is printed.
* @uses $CFG
* @param string $common The URL up to the point of the variable that changes
* @param array $options Alist of value-label pairs for the popup list
* @param string $formid Id must be unique on the page (originaly $formname)
* @param string $selected The option that is already selected
* @param string $baseurl The target URL up to the point of the variable that changes
* @param array $options A list of value-label pairs for the popup list
* @param string $formid id for the control. Must be unique on the page. Used in the HTML.
* @param string $selected The option that is initially selected
* @param string $nothing The label for the "no choice" option
* @param string $help The name of a help page if help is required
* @param string $helptext The name of the label for the help button
* @param boolean $return Indicates whether the function should return the text
* @param boolean $return Indicates whether the function should return the HTML
* as a string or echo it directly to the page being rendered
* @param string $targetwindow The name of the target page to open the linked page in.
* @param string $selectlabel Text to place in a [label] element - preferred for accessibility.
* @param array $optionsextra TODO, an array?
* @param array $optionsextra an array with the same keys as $options. The values are added within the corresponding <option ...> tag.
* @param string $submitvalue Optional label for the 'Go' button. Defaults to get_string('go').
* @param boolean $disabled If true, the menu will be displayed disabled.
* @return string If $return is true then the entire form is returned as a string.
* @todo Finish documenting this function<br>
function popup_form($common, $options, $formid, $selected='', $nothing='choose', $help='', $helptext='', $return=false,
$targetwindow='self', $selectlabel='', $optionsextra=NULL) {
function popup_form($baseurl, $options, $formid, $selected='', $nothing='choose', $help='', $helptext='', $return=false,
$targetwindow='self', $selectlabel='', $optionsextra=NULL, $submitvalue='', $disabled=false) {
global $CFG, $SESSION;
static $go, $choose; /// Locally cached, in case there's lots on a page
@@ -1018,16 +1022,23 @@ function popup_form($common, $options, $formid, $selected='', $nothing='choose',
return '';
if (!isset($go)) {
$go = get_string('go');
if (empty($submitvalue)){
if (!isset($go)) {
$go = get_string('go');
if ($nothing == 'choose') {
if (!isset($choose)) {
$choose = get_string('choose');
$nothing = $choose.'...';
if ($disabled) {
$disabled = 'disabled="disabled"';
} else {
$disabled = '';
// changed reference to document.getElementById('id_abc') instead of
// MDL-7861
@@ -1046,16 +1057,16 @@ function popup_form($common, $options, $formid, $selected='', $nothing='choose',
$selectlabel = '<label for="'.$formid.'_jump">'.$selectlabel.'</label>';
//IE and Opera fire the onchange when ever you move into a dropdwown list with the keyboard.
//IE and Opera fire the onchange when ever you move into a dropdown list with the keyboard.
//onfocus will call a function inside dropdown.js. It fixes this IE/Opera behavior.
//Note: There is a bug on Opera+Linux with the javascript code (first mouse selection is inactive),
//so we do not fix the Opera behavior on Linux
if (check_browser_version('MSIE') || (check_browser_version('Opera') && !check_browser_operating_system("Linux"))) {
$output .= '<div>'.$selectlabel.$button.'<select id="'.$formid.'_jump" onfocus="initSelect(\''.$formid.'\','.$targetwindow.')" name="jump">'."\n";
$output .= '<div>'.$selectlabel.$button.'<select id="'.$formid.'_jump" onfocus="initSelect(\''.$formid.'\','.$targetwindow.')" name="jump" '.$disabled.'>'."\n";
//Other browser
else {
$output .= '<div>'.$selectlabel.$button.'<select id="'.$formid.'_jump" name="jump" onchange="'.$targetwindow.'.location=document.getElementById(\''.$formid.'\').jump.options[document.getElementById(\''.$formid.'\').jump.selectedIndex].value;">'."\n";
$output .= '<div>'.$selectlabel.$button.'<select id="'.$formid.'_jump" name="jump" onchange="'.$targetwindow.'.location=document.getElementById(\''.$formid.'\').jump.options[document.getElementById(\''.$formid.'\').jump.selectedIndex].value;" '.$disabled.'>'."\n";
if ($nothing != '') {
@@ -1105,10 +1116,10 @@ function popup_form($common, $options, $formid, $selected='', $nothing='choose',
} else {
if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()]))
$url = $SESSION->sid_process_url( $common . $value );
$url = $SESSION->sid_process_url( $baseurl . $value );
} else
$url=$common . $value;
$url=$baseurl . $value;
$optstr = ' <option value="' . $url . '"';
@@ -1144,7 +1155,7 @@ function popup_form($common, $options, $formid, $selected='', $nothing='choose',
$output .= '</select>';
$output .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
$output .= '<div id="noscript'.$formid.'" style="display: inline;">';
$output .= '<input type="submit" value="'.$go.'" /></div>';
$output .= '<input type="submit" value="'.$submitvalue.'" '.$disabled.' /></div>';
$output .= '<script type="text/javascript">'.
'document.getElementById("noscript'.$formid.'").style.display = "none";'.
@@ -2068,7 +2079,7 @@ function get_emoticons_list_for_help_file(){
$formname = 'theform';
$fieldname = 'message';
$output .= print_js_call('emoticons_help.init', array($formname, $fieldname, 'emoticonlist'), true);
return $output;
@@ -2596,7 +2607,7 @@ function print_header ($title='', $heading='', $navigation='', $focus='',
* require_once in PHP.
* There are two special-case calls to this function from print_header which are
* internal to weblib and use the second $extracthtmlparameter:
* internal to weblib and use the second $extracthtml parameter:
* $extracthtml = 1: this is used before printing the header.
* It returns the script tag code that should go inside the <head>.
* $extracthtml = 2: this is used after printing the header and handles any
@@ -2754,7 +2765,7 @@ function print_delayed_js_call($delay, $function, $args = array(), $return = fal
* variable, and then just output the configuration variables from PHP using
* this function.
* For example, look at the code in question_init_qenginejs_script() in
* For example, look at the code in question_init_qenginejs_script() in
* lib/questionlib.php. It writes out a bunch of $settings like
* 'pixpath' => $CFG->pixpath, with $prefix = 'qengine_config'. This gets output
* in print_header, then the code in question/qengine.js can access these variables
@@ -0,0 +1,73 @@
<?php // $Id$
* Fallback page of /mod/quiz/edit.php add random question dialog,
* for users who do not use javascript.
* @author Olli Savolainen, as a part of the Quiz UI Redesign project in Summer 2008
* {@link}.
* @license GNU Public License
* @package quiz
list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) = question_edit_setup('editq', true);
$defaultcategoryobj = question_make_default_categories($contexts->all());
$qcobject = new question_category_object(
//setting the second parameter of process_randomquestion_formdata to true causes it to redirect on success
//TODO: process if returns false?
quiz_process_randomquestion_formdata($qcobject,true, $cmid);
//these params are only passed from page request to request while we stay on this page
//otherwise they would go in question_edit_setup
$quiz_page = optional_param('quiz_page', 0, PARAM_SEQUENCE);
$returnurl = optional_param('returnurl', 0, PARAM_LOCALURL);
$strquizzes = get_string('modulenameplural', 'quiz');
$strquiz = get_string('modulename', 'quiz');
$streditingquestions = get_string('editquestions', "quiz");
$streditingquiz = get_string('editinga', 'moodle', $strquiz);
// Get the course object and related bits.
if (! $course = $DB->get_record("course", array("id"=> $quiz->course))) {
error("This course doesn't exist");
// TODO: Log this visit.
add_to_log($cm->course, 'quiz', 'editquestions',
"view.php?id=$cm->id", "$quiz->id", $cm->id);
//you need mod/quiz:manage in addition to question capabilities to access this page.
require_capability('mod/quiz:manage', $contexts->lowest());
// Print basic page layout.
$strupdatemodule = has_capability('moodle/course:manageactivities', $contexts->lowest())
? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz'))
: "";
$navigation = build_navigation($streditingquiz, $cm);
print_header_simple($streditingquiz, '', $navigation, "", "", true, $strupdatemodule);
if (!$quizname = $DB->get_field($cm->modname, 'name', array('id'=> $cm->instance))) {
error('Cannot get the module name in build navigation.');
print_heading(get_string("addrandomquestiontoquiz","quiz",$quizname), 'left', 2);
Oops, something went wrong.

0 comments on commit fa583f5

Please sign in to comment.