From fa583f5f6eb3b4c9f624e77df0cfb9f9e705e6c3 Mon Sep 17 00:00:00 2001
From: tjhunt
Date: Thu, 20 Nov 2008 06:59:11 +0000
Subject: [PATCH] quiz editing: MDL-17285 This is Olli Savolainen's new
interface for editing 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 http://docs.moodle.org/en/Development:Quiz_UI_redesign.
---
lang/en_utf8/help/quiz/editconcepts.html | 25 +
lang/en_utf8/quiz.php | 41 +-
lib/weblib.php | 57 +-
mod/quiz/addrandom.php | 73 ++
mod/quiz/edit.js | 130 ++
mod/quiz/edit.php | 1074 ++++++++++-----
mod/quiz/editcss.js | 7 +
mod/quiz/editlib.php | 1433 ++++++++++++++++++---
mod/quiz/locallib.php | 125 +-
mod/quiz/tabs.php | 13 +-
question/category_class.php | 36 +-
question/category_form_randomquestion.php | 49 +
question/editlib.php | 25 +-
question/question.php | 9 +-
question/type/edit_question_form.php | 11 +-
theme/standard/styles_color.css | 68 +
theme/standard/styles_fonts.css | 55 +-
theme/standard/styles_ie6.css | 19 +
theme/standard/styles_layout.css | 561 +++++++-
theme/standard/styles_moz.css | 33 +-
20 files changed, 3287 insertions(+), 557 deletions(-)
create mode 100644 lang/en_utf8/help/quiz/editconcepts.html
create mode 100644 mod/quiz/addrandom.php
create mode 100644 mod/quiz/edit.js
create mode 100644 mod/quiz/editcss.js
create mode 100644 question/category_form_randomquestion.php
diff --git a/lang/en_utf8/help/quiz/editconcepts.html b/lang/en_utf8/help/quiz/editconcepts.html
new file mode 100644
index 0000000000..e48d378738
--- /dev/null
+++ b/lang/en_utf8/help/quiz/editconcepts.html
@@ -0,0 +1,25 @@
+
Basic ideas of making quizzes
+
The main concepts while managing quiz content (questions) are:
+
+
Quiz and pages
+
Question bank and its categories
+
Random question
+
+
You can think of a quiz, in essence, to be like a
+traditional pen&paper quiz (or an exam/test). It contains questions.
+You can divide the questions in a quiz on several pages
+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).
+
When you create questions, they are stored in the Question
+Bank. 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.
+
You can use Random Questions, 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.
diff --git a/lang/en_utf8/quiz.php b/lang/en_utf8/quiz.php
index de2b29fb84..41748e573c 100644
--- a/lang/en_utf8/quiz.php
+++ b/lang/en_utf8/quiz.php
@@ -678,5 +678,44 @@ $string['youcannotwait'] = 'This quiz closes before you will be allowed to start
$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 ';
?>
diff --git a/lib/weblib.php b/lib/weblib.php
index 050e79c0ca..d0f99e07b1 100644
--- a/lib/weblib.php
+++ b/lib/weblib.php
@@ -717,7 +717,7 @@ function close_window($delay=0) {
* @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
";
+ }
+ if ($quiz->timeopen) {
+ echo get_string('quizopens', 'quiz'), ': ', userdate($quiz->timeopen);
+ }
+ if ($quiz->timeclose) {
+ echo get_string('quizcloses', 'quiz'), ': ', userdate($quiz->timeclose);
+ }
+ if($showopenstatus){
+ if(!$quiz->timelimit && !$quiz->timeopen && !$quiz->timeclose){
+ echo get_string('quizopened', 'quiz');
+ }
+ }
+ } else if ($timenow < $quiz->timeopen) {
+ echo get_string("quiznotavailable", "quiz", userdate($quiz->timeopen));
+ } else {
+ echo get_string("quizclosed", "quiz", userdate($quiz->timeclose));
+ }
+ return $available;
+
+}
+function quiz_clean_layout($layout,$removeemptypages=false){
+ //remove duplicate "," (or triple, or...)
+ while(strstr($layout,",,")){
+ $layout = str_replace(',,', ',', $layout);
+ }
+ //remove duplicate question ids
+ $layout_arr=explode(",",$layout);
+ $new_arr=array();
+ $seen=array();
+ //remove duplicate questions
+ foreach($layout_arr as $key=>$value){
+ if(!in_array($value,$seen) && $value!=0){
+ $new_arr[]=$value;
+ $seen[]=$value;
+ }else if($value==0){
+ $new_arr[]=0;
+ }
+ }
+ if($removeemptypages){
+ // Avoid duplicate page breaks
+ // Go through all the array keys
+ for($i=0;$i1 AND
+ !($layout[$last-1] == ',' AND $layout[$last] == '0')){
+ $layout .= ',0';
+ }
+
+ return $layout;
+}
/**
* Print a quiz error message. This is a thin wrapper around print_error, for convinience.
*
diff --git a/mod/quiz/tabs.php b/mod/quiz/tabs.php
index aa6b1b961e..1f49a514d9 100644
--- a/mod/quiz/tabs.php
+++ b/mod/quiz/tabs.php
@@ -31,7 +31,7 @@ $tabs = array();
$row = array();
$inactive = array();
$activated = array();
-
+$stredit=get_string('edit');
if (has_capability('mod/quiz:view', $context)) {
$row[] = new tabobject('info', "$CFG->wwwroot/mod/quiz/view.php?id=$cm->id", get_string('info', 'quiz'));
}
@@ -39,10 +39,11 @@ if (has_capability('mod/quiz:viewreports', $context)) {
$row[] = new tabobject('reports', "$CFG->wwwroot/mod/quiz/report.php?q=$quiz->id", get_string('results', 'quiz'));
}
if (has_capability('mod/quiz:preview', $context)) {
- $row[] = new tabobject('preview', "$CFG->wwwroot/mod/quiz/startattempt.php?cmid=$cm->id&sesskey=" . sesskey(), get_string('preview', 'quiz'));
+ $strpreview = get_string('preview', 'quiz');
+ $row[] = new tabobject('preview', "$CFG->wwwroot/mod/quiz/startattempt.php?cmid=$cm->id&sesskey=" . sesskey(), "pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" /> $strpreview", $strpreview);
}
if (has_capability('mod/quiz:manage', $context)) {
- $row[] = new tabobject('edit', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", get_string('edit'));
+ $row[] = new tabobject('edit', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", "pixpath/t/edit.gif\" class=\"iconsmall\" alt=\"$stredit\" /> $stredit",$stredit);
}
if ($currenttab == 'info' && count($row) == 1) {
@@ -101,9 +102,11 @@ if ($currenttab == 'edit' and isset($mode)) {
$streditingquiz = get_string("editinga", "moodle", $strquiz);
if (has_capability('mod/quiz:manage', $context) && $contexts->have_one_edit_tab_cap('editq')) {
- $row[] = new tabobject('editq', "$CFG->wwwroot/mod/quiz/edit.php?".$thispageurl->get_query_string(), $strquiz, $streditingquiz);
+ //TODO: ensure that removing get_query_string here won't hurt
+ $row[] = new tabobject('editq', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", $stredit, $streditingquiz);
+ $row[] = new tabobject('reorder', "$CFG->wwwroot/mod/quiz/edit.php?reordertool=1&cmid=$cm->id", get_string('orderandpaging','quiz'), $streditingquiz);
}
- questionbank_navigation_tabs($row, $contexts, $thispageurl->get_query_string());
+ //questionbank_navigation_tabs($row, $contexts, $thispageurl->get_query_string());
$tabs[] = $row;
}
diff --git a/question/category_class.php b/question/category_class.php
index c6ef91ddbf..f927702463 100644
--- a/question/category_class.php
+++ b/question/category_class.php
@@ -12,6 +12,7 @@ define("QUESTION_PAGE_LENGTH", 25);
require_once("$CFG->libdir/listlib.php");
require_once("$CFG->dirroot/question/category_form.php");
+require_once("$CFG->dirroot/question/category_form_randomquestion.php");
require_once('move_form.php');
class question_category_list extends moodle_list {
@@ -135,6 +136,10 @@ class question_category_object {
* @var question_category_edit_form Object representing form for adding / editing categories.
*/
var $catform;
+ /**
+ * @var question_category_edit_form_randomquestion Object representing simplified form for adding a category in order to add it into a quiz as a random question.
+ */
+ var $catform_rand;
/**
* Constructor
@@ -197,8 +202,10 @@ class question_category_object {
list($paged, $count) = $this->editlists[$key]->list_from_records($paged, $count);
}
$this->catform = new question_category_edit_form($this->pageurl, compact('contexts', 'currentcat'));
+ $this->catform_rand = new question_category_edit_form_randomquestion($this->pageurl, compact('contexts', 'currentcat'));
if (!$currentcat){
$this->catform->set_data(array('parent'=>$defaultcategory));
+ $this->catform_rand->set_data(array('parent'=>$defaultcategory));
}
}
/**
@@ -216,6 +223,16 @@ class question_category_object {
$this->output_new_table();
echo ' ';
+ }
+ /**
+ * Displays the user interface
+ *
+ */
+ function display_randomquestion_user_interface() {
+
+ /// Interface for adding a new category:
+ $this->output_new_randomquestion_table();
+
}
/**
@@ -225,6 +242,13 @@ class question_category_object {
$this->catform->display();
}
+ /**
+ * Outputs a table to allow entry of a new category
+ */
+ function output_new_randomquestion_table() {
+ $this->catform_rand->display();
+ }
+
/**
* Outputs a list to allow editing/rearranging of existing categories
@@ -278,6 +302,8 @@ class question_category_object {
$category->categoryheader = $this->str->edit;
$this->catform->set_data($category);
$this->catform->display();
+ $this->catform_rand->set_data($category);
+ $this->catform_rand->display();
} else {
print_error('invalidcategory', '', '', $categoryid);
}
@@ -369,7 +395,7 @@ class question_category_object {
/**
* Creates a new category with given params
*/
- public function add_category($newparent, $newcategory, $newinfo) {
+ public function add_category($newparent, $newcategory, $newinfo, $return=false) {
global $DB;
if (empty($newcategory)) {
print_error('categorynamecantbeblank', 'quiz');
@@ -391,10 +417,14 @@ class question_category_object {
$cat->info = $newinfo;
$cat->sortorder = 999;
$cat->stamp = make_unique_id_code();
- if (!$DB->insert_record("question_categories", $cat)) {
+ if (!$categoryid=$DB->insert_record("question_categories", $cat)) {
print_error('cannotinsertquestioncate', 'question', '', $newcategory);
} else {
- redirect($this->pageurl->out());//always redirect after successful action
+ if($return){
+ return $categoryid;
+ }else{
+ redirect($this->pageurl->out());//always redirect after successful action
+ }
}
}
diff --git a/question/category_form_randomquestion.php b/question/category_form_randomquestion.php
new file mode 100644
index 0000000000..cde516caa2
--- /dev/null
+++ b/question/category_form_randomquestion.php
@@ -0,0 +1,49 @@
+libdir.'/formslib.php');
+
+class question_category_edit_form_randomquestion extends moodleform {
+
+ function definition() {
+ global $CFG, $DB;
+ $mform =& $this->_form;
+
+ $contexts = $this->_customdata['contexts'];
+ $currentcat = $this->_customdata['currentcat'];
+//--------------------------------------------------------------------------------
+ $mform->addElement('header', 'categoryheader', get_string('createcategoryfornewrandomquestion', 'quiz'));
+
+ $questioncategoryel = $mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'quiz'),
+ array('contexts'=>$contexts, 'top'=>true, 'currentcat'=>$currentcat, 'nochildrenof'=>$currentcat));
+ $mform->setType('parent', PARAM_SEQUENCE);
+ if (1 == $DB->count_records_sql("SELECT count(*)
+ FROM {question_categories} c1,
+ {question_categories} c2
+ WHERE c2.id = ?
+ AND c1.contextid = c2.contextid", array($currentcat))){
+ //TODO: Tim? why does the above evaluate true, breaking the form?
+ // and more importantly, if this is a valid situation, how should we react,
+ // that is, what does this mean?
+ //$mform->hardFreeze('parent');
+ }
+ $mform->setHelpButton('parent', array('categoryparent', get_string('parent', 'quiz'), 'question'));
+
+ $mform->addElement('text','name', get_string('name'),'maxlength="254" size="50"');
+ $mform->setDefault('name', '');
+ $mform->addRule('name', get_string('categorynamecantbeblank', 'quiz'), 'required', null, 'client');
+ $mform->setType('name', PARAM_MULTILANG);
+
+ $mform->addElement('hidden', 'info', '');
+ $mform->setType('info', PARAM_MULTILANG);
+
+//--------------------------------------------------------------------------------
+ $this->add_action_buttons(false, get_string('addrandomquestion', 'quiz'));
+//--------------------------------------------------------------------------------
+ $mform->addElement('hidden', 'id', 0);
+ $mform->setType('id', PARAM_INT);
+ //TODO: Tim: is this questionable? to add a hidden field right from a get parameter?
+ $mform->addElement('hidden', 'addonpage', optional_param("addonpage_form", 0, PARAM_SEQUENCE), 'id="rform_qpage"');
+ $mform->setType('addonpage', PARAM_SEQUENCE);
+ }
+}
+?>
diff --git a/question/editlib.php b/question/editlib.php
index 14d5907df7..50b58d551c 100644
--- a/question/editlib.php
+++ b/question/editlib.php
@@ -158,7 +158,7 @@ function question_list($contexts, $pageurl, $categoryandcontext, $cm = null,
$straddquestions = get_string("addquestions", "quiz");
$strimportquestions = get_string("importquestions", "quiz");
$strexportquestions = get_string("exportquestions", "quiz");
- $strnoquestions = get_string("noquestions", "quiz");
+ $strnoquestions = get_string("noquestionsincategory", "quiz");
$strselect = get_string("select", "quiz");
$strselectall = get_string("selectall", "quiz");
$strselectnone = get_string("selectnone", "quiz");
@@ -244,7 +244,7 @@ function question_list($contexts, $pageurl, $categoryandcontext, $cm = null,
list($usql, $params) = $DB->get_in_or_equal($categorylist_array);
if (!$totalnumber = $DB->count_records_select('question', "category $usql AND parent = '0' $showhidden", $params)) {
echo "
";
return;
}
@@ -255,14 +255,13 @@ function question_list($contexts, $pageurl, $categoryandcontext, $cm = null,
if (!$questions = $DB->get_records_select('question', "category $usql AND parent = '0' $showhidden", $params, $sortorderdecoded, '*', 0, $perpage)) {
// There are no questions at all
echo "
";
+ // POST method should only be used for parameters that change data
+ // or if POST method has to be used, the user must be redirected immediately to
+ // non-POSTed page to not break the back button
+ $html .= '