Merge branch 'MDL-25801' of git://github.com/timhunt/moodle
[moodle.git] / mod / quiz / editlib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * This contains functions that are called from within the quiz module only
20  * Functions that are also called by core Moodle are in {@link lib.php}
21  * This script also loads the code in {@link questionlib.php} which holds
22  * the module-indpendent code for handling questions and which in turn
23  * initialises all the questiontype classes.
24  *
25  * @package    mod
26  * @subpackage quiz
27  * @copyright  1999 onwards Martin Dougiamas and others {@link http://moodle.com}
28  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29  */
32 defined('MOODLE_INTERNAL') || die();
34 require_once($CFG->dirroot . '/mod/quiz/locallib.php');
36 define('NUM_QS_TO_SHOW_IN_RANDOM', 3);
38 /**
39  * Verify that the question exists, and the user has permission to use it.
40  * Does not return. Throws an exception if the question cannot be used.
41  * @param int $questionid The id of the question.
42  */
43 function quiz_require_question_use($questionid) {
44     global $DB;
45     $question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
46     question_require_capability_on($question, 'use');
47 }
49 /**
50  * Verify that the question exists, and the user has permission to use it.
51  * @param int $questionid The id of the question.
52  * @return bool whether the user can use this question.
53  */
54 function quiz_has_question_use($questionid) {
55     global $DB;
56     $question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
57     return question_has_capability_on($question, 'use');
58 }
60 /**
61  * Remove a question from a quiz
62  * @param object $quiz the quiz object.
63  * @param int $questionid The id of the question to be deleted.
64  */
65 function quiz_remove_question($quiz, $questionid) {
66     global $DB;
68     $questionids = explode(',', $quiz->questions);
69     $key = array_search($questionid, $questionids);
70     if ($key === false) {
71         return;
72     }
74     unset($questionids[$key]);
75     $quiz->questions = implode(',', $questionids);
76     $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
77     $DB->delete_records('quiz_question_instances',
78             array('quiz' => $quiz->instance, 'question' => $questionid));
79 }
81 /**
82  * Remove an empty page from the quiz layout. If that is not possible, do nothing.
83  * @param string $layout the existinng layout, $quiz->questions.
84  * @param int $index the position into $layout where the empty page should be removed.
85  * @return the updated layout
86  */
87 function quiz_delete_empty_page($layout, $index) {
88     $questionids = explode(',', $layout);
90     if ($index < -1 || $index >= count($questionids) - 1) {
91         return $layout;
92     }
94     if (($index >= 0 && $questionids[$index] != 0) || $questionids[$index + 1] != 0) {
95         return $layout; // This was not an empty page.
96     }
98     unset($questionids[$index + 1]);
100     return implode(',', $questionids);
103 /**
104  * Add a question to a quiz
105  *
106  * Adds a question to a quiz by updating $quiz as well as the
107  * quiz and quiz_question_instances tables. It also adds a page break
108  * if required.
109  * @param int $id The id of the question to be added
110  * @param object $quiz The extended quiz object as used by edit.php
111  *      This is updated by this function
112  * @param int $page Which page in quiz to add the question on. If 0 (default),
113  *      add at the end
114  * @return bool false if the question was already in the quiz
115  */
116 function quiz_add_quiz_question($id, $quiz, $page = 0) {
117     global $DB;
118     $questions = explode(',', quiz_clean_layout($quiz->questions));
119     if (in_array($id, $questions)) {
120         return false;
121     }
123     // remove ending page break if it is not needed
124     if ($breaks = array_keys($questions, 0)) {
125         // determine location of the last two page breaks
126         $end = end($breaks);
127         $last = prev($breaks);
128         $last = $last ? $last : -1;
129         if (!$quiz->questionsperpage || (($end - $last - 1) < $quiz->questionsperpage)) {
130             array_pop($questions);
131         }
132     }
133     if (is_int($page) && $page >= 1) {
134         $numofpages = quiz_number_of_pages($quiz->questions);
135         if ($numofpages<$page) {
136             //the page specified does not exist in quiz
137             $page = 0;
138         } else {
139             // add ending page break - the following logic requires doing
140             //this at this point
141             $questions[] = 0;
142             $currentpage = 1;
143             $addnow = false;
144             foreach ($questions as $question) {
145                 if ($question == 0) {
146                     $currentpage++;
147                     //The current page is the one after the one we want to add on,
148                     //so we add the question before adding the current page.
149                     if ($currentpage == $page + 1) {
150                         $questions_new[] = $id;
151                     }
152                 }
153                 $questions_new[] = $question;
154             }
155             $questions = $questions_new;
156         }
157     }
158     if ($page == 0) {
159         // add question
160         $questions[] = $id;
161         // add ending page break
162         $questions[] = 0;
163     }
165     // Save new questionslist in database
166     $quiz->questions = implode(',', $questions);
167     $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
169     // Add the new question instance.
170     $instance = new stdClass();
171     $instance->quiz = $quiz->id;
172     $instance->question = $id;
173     $instance->grade = $DB->get_field('question', 'defaultmark', array('id' => $id));
174     $DB->insert_record('quiz_question_instances', $instance);
177 function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
178         $includesubcategories) {
179     global $DB;
181     $category = $DB->get_record('question_categories', array('id' => $categoryid));
182     if (!$category) {
183         print_error('invalidcategoryid', 'error');
184     }
186     $catcontext = get_context_instance_by_id($category->contextid);
187     require_capability('moodle/question:useall', $catcontext);
189     // Find existing random questions in this category that are
190     // not used by any quiz.
191     if ($existingquestions = $DB->get_records_sql(
192             "SELECT q.id, q.qtype FROM {question} q
193             WHERE qtype = 'random'
194                 AND category = ?
195                 AND " . $DB->sql_compare_text('questiontext') . " = ?
196                 AND NOT EXISTS (
197                         SELECT *
198                           FROM {quiz_question_instances}
199                          WHERE question = q.id)
200             ORDER BY id", array($category->id, $includesubcategories))) {
201         // Take as many of these as needed.
202         while (($existingquestion = array_shift($existingquestions)) && $number > 0) {
203             quiz_add_quiz_question($existingquestion->id, $quiz, $addonpage);
204             $number -= 1;
205         }
206     }
208     if ($number <= 0) {
209         return;
210     }
212     // More random questions are needed, create them.
213     for ($i = 0; $i < $number; $i += 1) {
214         $form = new stdClass();
215         $form->questiontext = array('text' => $includesubcategories, 'format' => 0);
216         $form->category = $category->id . ',' . $category->contextid;
217         $form->defaultmark = 1;
218         $form->hidden = 1;
219         $form->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
220         $question = new stdClass();
221         $question->qtype = 'random';
222         $question = question_bank::get_qtype('random')->save_question($question, $form);
223         if (!isset($question->id)) {
224             print_error('cannotinsertrandomquestion', 'quiz');
225         }
226         quiz_add_quiz_question($question->id, $quiz, $addonpage);
227     }
230 /**
231  * Add a page break after at particular position$.
232  * @param string $layout the existinng layout, $quiz->questions.
233  * @param int $index the position into $layout where the empty page should be removed.
234  * @return the updated layout
235  */
236 function quiz_add_page_break_at($layout, $index) {
237     $questionids = explode(',', $layout);
238     if ($index < 0 || $index >= count($questionids)) {
239         return $layout;
240     }
242     array_splice($questionids, $index, 0, '0');
244     return implode(',', $questionids);
247 /**
248  * Add a page break after a particular question.
249  * @param string $layout the existinng layout, $quiz->questions.
250  * @param int $qustionid the question to add the page break after.
251  * @return the updated layout
252  */
253 function quiz_add_page_break_after($layout, $questionid) {
254     $questionids = explode(',', $layout);
255     $key = array_search($questionid, $questionids);
256     if ($key === false || !$questionid) {
257         return $layout;
258     }
260     array_splice($questionids, $key + 1, 0, '0');
262     return implode(',', $questionids);
265 /**
266  * Update the database after $quiz->questions has been changed. For example,
267  * this deletes preview attempts and updates $quiz->sumgrades.
268  * @param $quiz the quiz object.
269  */
270 function quiz_save_new_layout($quiz) {
271     global $DB;
272     $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
273     quiz_delete_previews($quiz);
274     quiz_update_sumgrades($quiz);
277 /**
278  * Save changes to question instance
279  *
280  * Saves changes to the question grades in the quiz_question_instances table.
281  * It does not update 'sumgrades' in the quiz table.
282  *
283  * @param int grade    The maximal grade for the question
284  * @param int $questionid  The id of the question
285  * @param int $quizid  The id of the quiz to update / add the instances for.
286  */
287 function quiz_update_question_instance($grade, $questionid, $quiz) {
288     global $DB;
289     $instance = $DB->get_record('quiz_question_instances', array('quiz' => $quiz->id,
290             'question' => $questionid));
291     $slot = quiz_get_slot_for_question($quiz, $questionid);
293     if (!$instance || !$slot) {
294         throw new coding_exception('Attempt to change the grade of a quesion not in the quiz.');
295     }
297     if (abs($grade - $instance->grade) < 1e-7) {
298         // Grade has not changed. Nothing to do.
299         return;
300     }
302     $instance->grade = $grade;
303     $DB->update_record('quiz_question_instances', $instance);
304     question_engine::set_max_mark_in_attempts(new qubaids_for_quiz($quiz->id),
305             $slot, $grade);
308 // Private function used by the following two.
309 function _quiz_move_question($layout, $questionid, $shift) {
310     if (!$questionid || !($shift == 1 || $shift == -1)) {
311         return $layout;
312     }
314     $questionids = explode(',', $layout);
315     $key = array_search($questionid, $questionids);
316     if ($key === false) {
317         return $layout;
318     }
320     $otherkey = $key + $shift;
321     if ($otherkey < 0 || $otherkey >= count($questionids) - 1) {
322         return $layout;
323     }
325     $temp = $questionids[$otherkey];
326     $questionids[$otherkey] = $questionids[$key];
327     $questionids[$key] = $temp;
329     return implode(',', $questionids);
332 /**
333  * Move a particular question one space earlier in the $quiz->questions list.
334  * If that is not possible, do nothing.
335  * @param string $layout the existinng layout, $quiz->questions.
336  * @param int $questionid the id of a question.
337  * @return the updated layout
338  */
339 function quiz_move_question_up($layout, $questionid) {
340     return _quiz_move_question($layout, $questionid, -1);
343 /**
344  * Move a particular question one space later in the $quiz->questions list.
345  * If that is not possible, do nothing.
346  * @param string $layout the existinng layout, $quiz->questions.
347  * @param int $questionid the id of a question.
348  * @return the updated layout
349  */
350 function quiz_move_question_down($layout, $questionid) {
351     return _quiz_move_question($layout, $questionid, +1);
354 /**
355  * Prints a list of quiz questions for the edit.php main view for edit
356  * ($reordertool = false) and order and paging ($reordertool = true) tabs
357  *
358  * @param object $quiz This is not the standard quiz object used elsewhere but
359  *     it contains the quiz layout in $quiz->questions and the grades in
360  *     $quiz->grades
361  * @param moodle_url $pageurl The url of the current page with the parameters required
362  *     for links returning to the current page, as a moodle_url object
363  * @param bool $allowdelete Indicates whether the delete icons should be displayed
364  * @param bool $reordertool  Indicates whether the reorder tool should be displayed
365  * @param bool $quiz_qbanktool  Indicates whether the question bank should be displayed
366  * @param bool $hasattempts  Indicates whether the quiz has attempts
367  * @param object $defaultcategoryobj
368  * @param bool $canaddquestion is the user able to add and use questions anywere?
369  * @param bool $canaddrandom is the user able to add random questions anywere?
370  */
371 function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
372         $quiz_qbanktool, $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom) {
373     global $CFG, $DB, $OUTPUT;
374     $strorder = get_string('order');
375     $strquestionname = get_string('questionname', 'quiz');
376     $strmaxmark = get_string('markedoutof', 'question');
377     $strremove = get_string('remove', 'quiz');
378     $stredit = get_string('edit');
379     $strview = get_string('view');
380     $straction = get_string('action');
381     $strmove = get_string('move');
382     $strmoveup = get_string('moveup');
383     $strmovedown = get_string('movedown');
384     $strsave = get_string('save', 'quiz');
385     $strreorderquestions = get_string('reorderquestions', 'quiz');
387     $strselectall = get_string('selectall', 'quiz');
388     $strselectnone = get_string('selectnone', 'quiz');
389     $strtype = get_string('type', 'quiz');
390     $strpreview = get_string('preview', 'quiz');
392     if ($quiz->questions) {
393         list($usql, $params) = $DB->get_in_or_equal(explode(',', $quiz->questions));
394         $params[] = $quiz->id;
395         $questions = $DB->get_records_sql("SELECT q.*, qc.contextid, qqi.grade as maxmark
396                               FROM {question} q
397                               JOIN {question_categories} qc ON qc.id = q.category
398                               JOIN {quiz_question_instances} qqi ON qqi.question = q.id
399                              WHERE q.id $usql AND qqi.quiz = ?", $params);
400     } else {
401         $questions = array();
402     }
404     $layout = quiz_clean_layout($quiz->questions);
405     $order = explode(',', $layout);
406     $lastindex = count($order) - 1;
408     $disabled = '';
409     $pagingdisabled = '';
410     if ($hasattempts) {
411         $disabled = 'disabled="disabled"';
412     }
413     if ($hasattempts || $quiz->shufflequestions) {
414         $pagingdisabled = 'disabled="disabled"';
415     }
417     $reordercontrolssetdefaultsubmit = '<div style="display:none;">' .
418         '<input type="submit" name="savechanges" value="' .
419         $strreorderquestions . '" ' . $pagingdisabled . ' /></div>';
420     $reordercontrols1 = '<div class="addnewpagesafterselected">' .
421         '<input type="submit" name="addnewpagesafterselected" value="' .
422         get_string('addnewpagesafterselected', 'quiz') . '"  ' .
423         $pagingdisabled . ' /></div>';
424     $reordercontrols1 .= '<div class="quizdeleteselected">' .
425         '<input type="submit" name="quizdeleteselected" ' .
426         'onclick="return confirm(\'' .
427         get_string('areyousureremoveselected', 'quiz') . '\');" value="' .
428         get_string('removeselected', 'quiz') . '"  ' . $disabled . ' /></div>';
430     $a = '<label class="accesshide" for="moveselectedonpagetop">' . get_string('moveup') . '</label>';
431     $a .= '<input name="moveselectedonpagetop" id="moveselectedonpagetop" type="text" size="2" ' .
432         $pagingdisabled . ' />';
433     $b = '<label class="accesshide" for="moveselectedonpagebottom">' . get_string('movedown') . '</label>';
434     $b .='<input name="moveselectedonpagebottom" id="moveselectedonpagebottom" type="text" size="2" ' .
435         $pagingdisabled . ' />';
437     $reordercontrols2top = '<div class="moveselectedonpage">' .
438         get_string('moveselectedonpage', 'quiz', $a) .
439         '<input type="submit" name="savechanges" value="' .
440         $strmove . '"  ' . $pagingdisabled . ' />' . '
441         <br /><input type="submit" name="savechanges" value="' .
442         $strreorderquestions . '" /></div>';
443     $reordercontrols2bottom = '<div class="moveselectedonpage">' .
444         '<input type="submit" name="savechanges" value="' .
445         $strreorderquestions . '" /><br />' .
446         get_string('moveselectedonpage', 'quiz', $b) .
447         '<input type="submit" name="savechanges" value="' .
448         $strmove . '"  ' . $pagingdisabled . ' /> ' . '</div>';
450     $reordercontrols3 = '<a href="javascript:select_all_in(\'FORM\', null, ' .
451             '\'quizquestions\');">' .
452             $strselectall . '</a> /';
453     $reordercontrols3.=    ' <a href="javascript:deselect_all_in(\'FORM\', ' .
454             'null, \'quizquestions\');">' .
455             $strselectnone . '</a>';
457     $reordercontrolstop = '<div class="reordercontrols">' .
458             $reordercontrolssetdefaultsubmit .
459             $reordercontrols1 . $reordercontrols2top . $reordercontrols3 . "</div>";
460     $reordercontrolsbottom = '<div class="reordercontrols">' .
461             $reordercontrolssetdefaultsubmit .
462             $reordercontrols2bottom . $reordercontrols1 . $reordercontrols3 . "</div>";
464     if ($reordertool) {
465         echo '<form method="post" action="edit.php" id="quizquestions"><div>';
467         echo html_writer::input_hidden_params($pageurl);
468         echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
470         echo $reordercontrolstop;
471     }
473     //the current question ordinal (no descriptions)
474     $qno = 1;
475     //the current question (includes questions and descriptions)
476     $questioncount = 0;
477     //the current page number in iteration
478     $pagecount = 0;
480     $pageopen = false;
482     $returnurl = $pageurl->out_as_local_url(false);
483     $questiontotalcount = count($order);
485     foreach ($order as $count => $qnum) {
487         $reordercheckbox = '';
488         $reordercheckboxlabel = '';
489         $reordercheckboxlabelclose = '';
491         // If the questiontype is missing change the question type
492         if ($qnum && !array_key_exists($qnum, $questions)) {
493             $fakequestion = new stdClass();
494             $fakequestion->id = $qnum;
495             $fakequestion->category = 0;
496             $fakequestion->qtype = 'missingtype';
497             $fakequestion->name = get_string('missingquestion', 'quiz');
498             $fakequestion->questiontext = ' ';
499             $fakequestion->questiontextformat = FORMAT_HTML;
500             $fakequestion->length = 1;
501             $questions[$qnum] = $fakequestion;
502             $quiz->grades[$qnum] = 0;
504         } else if ($qnum && !question_bank::qtype_exists($questions[$qnum]->qtype)) {
505             $questions[$qnum]->qtype = 'missingtype';
506         }
508         if ($qnum != 0 || ($qnum == 0 && !$pageopen)) {
509             //this is either a question or a page break after another
510             //        (no page is currently open)
511             if (!$pageopen) {
512                 //if no page is open, start display of a page
513                 $pagecount++;
514                 echo  '<div class="quizpage"><span class="pagetitle">' .
515                         get_string('page') . '&nbsp;' . $pagecount .
516                         '</span><div class="pagecontent">';
517                 $pageopen = true;
518             }
519             if ($qnum == 0  && $count < $questiontotalcount) {
520                 // This is the second successive page break. Tell the user the page is empty.
521                 echo '<div class="pagestatus">';
522                 print_string('noquestionsonpage', 'quiz');
523                 echo '</div>';
524                 if ($allowdelete) {
525                     echo '<div class="quizpagedelete">';
526                     echo $OUTPUT->action_icon($pageurl->out(true,
527                             array('deleteemptypage' => $count - 1, 'sesskey'=>sesskey())),
528                             new pix_icon('t/delete', $strremove),
529                             new component_action('click',
530                                     'M.core_scroll_manager.save_scroll_action'),
531                             array('title' => $strremove));
532                     echo '</div>';
533                 }
534             }
536             if ($qnum != 0) {
537                 $question = $questions[$qnum];
538                 $questionparams = array(
539                         'returnurl' => $returnurl,
540                         'cmid' => $quiz->cmid,
541                         'id' => $question->id);
542                 $questionurl = new moodle_url('/question/question.php',
543                         $questionparams);
544                 $questioncount++;
545                 //this is an actual question
547                 /* Display question start */
548                 ?>
549 <div class="question">
550     <div class="questioncontainer <?php echo $question->qtype; ?>">
551         <div class="qnum">
552                 <?php
553                 $reordercheckbox = '';
554                 $reordercheckboxlabel = '';
555                 $reordercheckboxlabelclose = '';
556                 if ($reordertool) {
557                     $reordercheckbox = '<input type="checkbox" name="s' . $question->id .
558                         '" id="s' . $question->id . '" />';
559                     $reordercheckboxlabel = '<label for="s' . $question->id . '">';
560                     $reordercheckboxlabelclose = '</label>';
561                 }
562                 if ($question->length == 0) {
563                     $qnodisplay = get_string('infoshort', 'quiz');
564                 } else if ($quiz->shufflequestions) {
565                     $qnodisplay = '?';
566                 } else {
567                     if ($qno > 999 || ($reordertool && $qno > 99)) {
568                         $qnodisplay = html_writer::tag('small', $qno);
569                     } else {
570                         $qnodisplay = $qno;
571                     }
572                     $qno += $question->length;
573                 }
574                 echo $reordercheckboxlabel . $qnodisplay . $reordercheckboxlabelclose .
575                         $reordercheckbox;
577                 ?>
578         </div>
579         <div class="content">
580             <div class="questioncontrols">
581                 <?php
582                 if ($count != 0) {
583                     if (!$hasattempts) {
584                         $upbuttonclass = '';
585                         if ($count >= $lastindex - 1) {
586                             $upbuttonclass = 'upwithoutdown';
587                         }
588                         echo $OUTPUT->action_icon($pageurl->out(true,
589                                 array('up' => $question->id, 'sesskey'=>sesskey())),
590                                 new pix_icon('t/up', $strmoveup),
591                                 new component_action('click',
592                                         'M.core_scroll_manager.save_scroll_action'),
593                                 array('title' => $strmoveup));
594                     }
596                 }
597                 if ($count < $lastindex - 1) {
598                     if (!$hasattempts) {
599                         echo $OUTPUT->action_icon($pageurl->out(true,
600                                 array('down' => $question->id, 'sesskey'=>sesskey())),
601                                 new pix_icon('t/down', $strmovedown),
602                                 new component_action('click',
603                                         'M.core_scroll_manager.save_scroll_action'),
604                                 array('title' => $strmovedown));
605                     }
606                 }
607                 if ($allowdelete && ($question->qtype == 'missingtype' ||
608                         question_has_capability_on($question, 'use', $question->category))) {
609                     // remove from quiz, not question delete.
610                     if (!$hasattempts) {
611                         echo $OUTPUT->action_icon($pageurl->out(true,
612                                 array('remove' => $question->id, 'sesskey'=>sesskey())),
613                                 new pix_icon('t/delete', $strremove),
614                                 new component_action('click',
615                                         'M.core_scroll_manager.save_scroll_action'),
616                                 array('title' => $strremove));
617                     }
618                 }
619                 ?>
620             </div><?php
621                 if (!in_array($question->qtype, array('description', 'missingtype')) && !$reordertool) {
622                     ?>
623 <div class="points">
624 <form method="post" action="edit.php" class="quizsavegradesform"><div>
625     <fieldset class="invisiblefieldset" style="display: block;">
626     <label for="<?php echo "inputq$question->id" ?>"><?php echo $strmaxmark; ?></label>:<br />
627     <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
628     <?php echo html_writer::input_hidden_params($pageurl); ?>
629     <input type="hidden" name="savechanges" value="save" />
630                     <?php
631                     echo '<label class="accesshide" for="inputq' . $question->id .'">' . get_string('question', 'quiz') . '</label>';
632                     echo '<input type="text" name="g' . $question->id .
633                             '" id="inputq' . $question->id .
634                             '" size="' . ($quiz->decimalpoints + 2) .
635                             '" value="' . (0 + $quiz->grades[$qnum]) .
636                             '" tabindex="' . ($lastindex + $qno) . '" />';
637                     ?>
638         <input type="submit" class="pointssubmitbutton" value="<?php echo $strsave; ?>" />
639     </fieldset>
640                     <?php
641                     if ($question->qtype == 'random') {
642                         echo '<a href="' . $questionurl->out() .
643                                 '" class="configurerandomquestion">' .
644                                 get_string("configurerandomquestion", "quiz") . '</a>';
645                     }
647                     ?>
648 </div>
649 </form>
651             </div>
652                     <?php
653                 } else if ($reordertool) {
654                     if ($qnum) {
655                         ?>
656 <div class="qorder">
657                         <?php
658                         echo '<label class="accesshide" for="o' . $question->id . '">' . get_string('reorderquestions', 'quiz') . '</label>';
659                         echo '<input type="text" name="o' . $question->id .
660                                 '" id="o' . $question->id . '"' .
661                                 '" size="2" value="' . (10*$count + 10) .
662                                 '" tabindex="' . ($lastindex + $qno) . '" />';
663                         ?>
664 </div>
665                         <?php
666                     }
667                 }
668                 ?>
669             <div class="questioncontentcontainer">
670                 <?php
671                 if ($question->qtype == 'random') { // it is a random question
672                     if (!$reordertool) {
673                         quiz_print_randomquestion($question, $pageurl, $quiz, $quiz_qbanktool);
674                     } else {
675                         quiz_print_randomquestion_reordertool($question, $pageurl, $quiz);
676                     }
677                 } else { // it is a single question
678                     if (!$reordertool) {
679                         quiz_print_singlequestion($question, $returnurl, $quiz);
680                     } else {
681                         quiz_print_singlequestion_reordertool($question, $returnurl, $quiz);
682                     }
683                 }
684                 ?>
685             </div>
686         </div>
687     </div>
688 </div>
690                 <?php
691             }
692         }
693         //a page break: end the existing page.
694         if ($qnum == 0) {
695             if ($pageopen) {
696                 if (!$reordertool && !($quiz->shufflequestions &&
697                         $count < $questiontotalcount - 1)) {
698                     quiz_print_pagecontrols($quiz, $pageurl, $pagecount,
699                             $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom);
700                 } else if ($count < $questiontotalcount - 1) {
701                     //do not include the last page break for reordering
702                     //to avoid creating a new extra page in the end
703                     echo '<input type="hidden" name="opg' . $pagecount . '" size="2" value="' .
704                             (10*$count + 10) . '" />';
705                 }
706                 echo "</div></div>";
708                 if (!$reordertool && !$quiz->shufflequestions) {
709                     echo $OUTPUT->container_start('addpage');
710                     $url = new moodle_url($pageurl->out_omit_querystring(),
711                             array('cmid' => $quiz->cmid, 'courseid' => $quiz->course,
712                                     'addpage' => $count, 'sesskey' => sesskey()));
713                     echo $OUTPUT->single_button($url, get_string('addpagehere', 'quiz'), 'post',
714                             array('disabled' => $hasattempts,
715                             'actions' => array(new component_action('click',
716                                     'M.core_scroll_manager.save_scroll_action'))));
717                     echo $OUTPUT->container_end();
718                 }
719                 $pageopen = false;
720                 $count++;
721             }
722         }
724     }
725     if ($reordertool) {
726         echo $reordercontrolsbottom;
727         echo '</div></form>';
728     }
731 /**
732  * Print all the controls for adding questions directly into the
733  * specific page in the edit tab of edit.php
734  *
735  * @param object $quiz This is not the standard quiz object used elsewhere but
736  *     it contains the quiz layout in $quiz->questions and the grades in
737  *     $quiz->grades
738  * @param moodle_url $pageurl The url of the current page with the parameters required
739  *     for links returning to the current page, as a moodle_url object
740  * @param int $page the current page number.
741  * @param bool $hasattempts  Indicates whether the quiz has attempts
742  * @param object $defaultcategoryobj
743  * @param bool $canaddquestion is the user able to add and use questions anywere?
744  * @param bool $canaddrandom is the user able to add random questions anywere?
745  */
746 function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts,
747         $defaultcategoryobj, $canaddquestion, $canaddrandom) {
748     global $CFG, $OUTPUT;
749     static $randombuttoncount = 0;
750     $randombuttoncount++;
751     echo '<div class="pagecontrols">';
753     // Get the current context
754     $thiscontext = get_context_instance(CONTEXT_COURSE, $quiz->course);
755     $contexts = new question_edit_contexts($thiscontext);
757     // Get the default category.
758     list($defaultcategoryid) = explode(',', $pageurl->param('cat'));
759     if (empty($defaultcategoryid)) {
760         $defaultcategoryid = $defaultcategoryobj->id;
761     }
763     if ($canaddquestion) {
764         // Create the url the question page will return to
765         $returnurladdtoquiz = new moodle_url($pageurl, array('addonpage' => $page));
767         // Print a button linking to the choose question type page.
768         $returnurladdtoquiz = $returnurladdtoquiz->out_as_local_url(false);
769         $newquestionparams = array('returnurl' => $returnurladdtoquiz,
770                 'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion');
771         create_new_question_button($defaultcategoryid, $newquestionparams,
772                 get_string('addaquestion', 'quiz'),
773                 get_string('createquestionandadd', 'quiz'), $hasattempts);
774     }
776     if ($hasattempts) {
777         $disabled = 'disabled="disabled"';
778     } else {
779         $disabled = '';
780     }
781     if ($canaddrandom) {
782     ?>
783     <div class="singlebutton">
784         <form class="randomquestionform" action="<?php echo $CFG->wwwroot;
785                 ?>/mod/quiz/addrandom.php" method="get">
786             <div>
787                 <input type="hidden" class="addonpage_formelement" name="addonpage" value="<?php
788                         echo $page; ?>" />
789                 <input type="hidden" name="cmid" value="<?php echo $quiz->cmid; ?>" />
790                 <input type="hidden" name="courseid" value="<?php echo $quiz->course; ?>" />
791                 <input type="hidden" name="category" value="<?php
792                         echo $pageurl->param('cat'); ?>" />
793                 <input type="hidden" name="returnurl" value="<?php
794                         echo s(str_replace($CFG->wwwroot, '', $pageurl->out(false))); ?>" />
795                 <input type="submit" id="addrandomdialoglaunch_<?php
796                         echo $randombuttoncount; ?>" value="<?php
797                         echo get_string('addarandomquestion', 'quiz'); ?>" <?php
798                         echo " $disabled"; ?> />
799             </div>
800         </form>
801     </div>
802     <?php echo $OUTPUT->help_icon('addarandomquestion', 'quiz');
803     }
804     echo "\n</div>";
807 /**
808  * Print a given single question in quiz for the edit tab of edit.php.
809  * Meant to be used from quiz_print_question_list()
810  *
811  * @param object $question A question object from the database questions table
812  * @param object $returnurl The url to get back to this page, for example after editing.
813  * @param object $quiz The quiz in the context of which the question is being displayed
814  */
815 function quiz_print_singlequestion($question, $returnurl, $quiz) {
816     echo '<div class="singlequestion ' . $question->qtype . '">';
817     echo quiz_question_edit_button($quiz->cmid, $question, $returnurl,
818             quiz_question_tostring($question) . ' ');
819     echo '<span class="questiontype">';
820     echo print_question_icon($question);
821     echo ' ' . question_bank::get_qtype_name($question->qtype) . '</span>';
822     echo '<span class="questionpreview">' .
823             quiz_question_preview_button($quiz, $question, true) . '</span>';
824     echo "</div>\n";
826 /**
827  * Print a given random question in quiz for the edit tab of edit.php.
828  * Meant to be used from quiz_print_question_list()
829  *
830  * @param object $question A question object from the database questions table
831  * @param object $questionurl The url of the question editing page as a moodle_url object
832  * @param object $quiz The quiz in the context of which the question is being displayed
833  * @param bool $quiz_qbanktool Indicate to this function if the question bank window open
834  */
835 function quiz_print_randomquestion(&$question, &$pageurl, &$quiz, $quiz_qbanktool) {
836     global $DB, $OUTPUT;
837     echo '<div class="quiz_randomquestion">';
839     if (!$category = $DB->get_record('question_categories',
840             array('id' => $question->category))) {
841         echo $OUTPUT->notification('Random question category not found!');
842         return;
843     }
845     echo '<div class="randomquestionfromcategory">';
846     echo print_question_icon($question);
847     print_random_option_icon($question);
848     echo ' ' . get_string('randomfromcategory', 'quiz') . '</div>';
850     $a = new stdClass();
851     $a->arrow = $OUTPUT->rarrow();
852     $strshowcategorycontents = get_string('showcategorycontents', 'quiz', $a);
854     $openqbankurl = $pageurl->out(true, array('qbanktool' => 1,
855             'cat' => $category->id . ',' . $category->contextid));
856     $linkcategorycontents = ' <a href="' . $openqbankurl . '">' . $strshowcategorycontents . '</a>';
858     echo '<div class="randomquestioncategory">';
859     echo '<a href="' . $openqbankurl . '" title="' . $strshowcategorycontents . '">' .
860             $category->name . '</a>';
861     echo '<span class="questionpreview">' .
862             quiz_question_preview_button($quiz, $question, true) . '</span>';
863     echo '</div>';
865     $questionids = question_bank::get_qtype('random')->get_available_questions_from_category(
866             $category->id, $question->questiontext == '1', '0');
867     $questioncount = count($questionids);
869     echo '<div class="randomquestionqlist">';
870     if ($questioncount == 0) {
871         // No questions in category, give an error plus instructions
872         echo '<span class="error">';
873         print_string('noquestionsnotinuse', 'quiz');
874         echo '</span>';
875         echo '<br />';
877         // Embed the link into the string with instructions
878         $a = new stdClass();
879         $a->catname = '<strong>' . $category->name . '</strong>';
880         $a->link = $linkcategorycontents;
881         echo get_string('addnewquestionsqbank', 'quiz', $a);
883     } else {
884         // Category has questions
886         // Get a sample from the database,
887         $questionidstoshow = array_slice($questionids, 0, NUM_QS_TO_SHOW_IN_RANDOM);
888         $questionstoshow = $DB->get_records_list('question', 'id', $questionidstoshow,
889                 '', 'id, qtype, name, questiontext, questiontextformat');
891         // list them,
892         echo '<ul>';
893         foreach ($questionstoshow as $question) {
894             echo '<li>' . quiz_question_tostring($question, true) . '</li>';
895         }
897         // and then display the total number.
898         echo '<li class="totalquestionsinrandomqcategory">';
899         if ($questioncount > NUM_QS_TO_SHOW_IN_RANDOM) {
900             echo '... ';
901         }
902         print_string('totalquestionsinrandomqcategory', 'quiz', $questioncount);
903         echo ' ' . $linkcategorycontents;
904         echo '</li>';
905         echo '</ul>';
906     }
908     echo '</div>';
909     echo '<div class="randomquestioncategorycount">';
910     echo '</div>';
911     echo '</div>';
914 /**
915  * Print a given single question in quiz for the reordertool tab of edit.php.
916  * Meant to be used from quiz_print_question_list()
917  *
918  * @param object $question A question object from the database questions table
919  * @param object $questionurl The url of the question editing page as a moodle_url object
920  * @param object $quiz The quiz in the context of which the question is being displayed
921  */
922 function quiz_print_singlequestion_reordertool($question, $returnurl, $quiz) {
923     echo '<div class="singlequestion ' . $question->qtype . '">';
924     echo '<label for="s' . $question->id . '">';
925     echo print_question_icon($question);
926     echo ' ' . quiz_question_tostring($question);
927     echo '</label>';
928     echo '<span class="questionpreview">' .
929             quiz_question_action_icons($quiz, $quiz->cmid, $question, $returnurl) . '</span>';
930     echo "</div>\n";
933 /**
934  * Print a given random question in quiz for the reordertool tab of edit.php.
935  * Meant to be used from quiz_print_question_list()
936  *
937  * @param object $question A question object from the database questions table
938  * @param object $questionurl The url of the question editing page as a moodle_url object
939  * @param object $quiz The quiz in the context of which the question is being displayed
940  */
941 function quiz_print_randomquestion_reordertool(&$question, &$pageurl, &$quiz) {
942     global $DB, $OUTPUT;
944     // Load the category, and the number of available questions in it.
945     if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
946         echo $OUTPUT->notification('Random question category not found!');
947         return;
948     }
949     $questioncount = count(question_bank::get_qtype(
950             'random')->get_available_questions_from_category(
951             $category->id, $question->questiontext == '1', '0'));
953     $reordercheckboxlabel = '<label for="s' . $question->id . '">';
954     $reordercheckboxlabelclose = '</label>';
956     echo '<div class="quiz_randomquestion">';
957     echo '<div class="randomquestionfromcategory">';
958     echo $reordercheckboxlabel;
959     echo print_question_icon($question);
960     print_random_option_icon($question);
962     if ($questioncount == 0) {
963         echo '<span class="error">';
964         print_string('empty', 'quiz');
965         echo '</span> ';
966     }
968     print_string('random', 'quiz');
969     echo ": $reordercheckboxlabelclose</div>";
971     echo '<div class="randomquestioncategory">';
972     echo $reordercheckboxlabel . $category->name . $reordercheckboxlabelclose;
973     echo '<span class="questionpreview">';
974     echo quiz_question_preview_button($quiz, $question, false);
975     echo '</span>';
976     echo "</div>";
978     echo '<div class="randomquestioncategorycount">';
979     echo '</div>';
980     echo '</div>';
983 /**
984  * Print an icon to indicate the 'include subcategories' state of a random question.
985  * @param $question the random question.
986  */
987 function print_random_option_icon($question) {
988     global $OUTPUT;
989     if (!empty($question->questiontext)) {
990         $icon = 'withsubcat';
991         $tooltip = get_string('randomwithsubcat', 'quiz');
992     } else {
993         $icon = 'nosubcat';
994         $tooltip = get_string('randomnosubcat', 'quiz');
995     }
996     echo '<img src="' . $OUTPUT->pix_url('i/' . $icon) . '" alt="' .
997             $tooltip . '" title="' . $tooltip . '" class="uihint" />';
1000 /**
1001  * Creates a textual representation of a question for display.
1002  *
1003  * @param object $question A question object from the database questions table
1004  * @param bool $showicon If true, show the question's icon with the question. False by default.
1005  * @param bool $showquestiontext If true (default), show question text after question name.
1006  *       If false, show only question name.
1007  * @param bool $return If true (default), return the output. If false, print it.
1008  */
1009 function quiz_question_tostring($question, $showicon = false,
1010         $showquestiontext = true, $return = true) {
1011     global $COURSE;
1012     $result = '';
1013     $result .= '<span class="questionname">';
1014     if ($showicon) {
1015         $result .= print_question_icon($question, true);
1016         echo ' ';
1017     }
1018     $result .= shorten_text(format_string($question->name), 200) . '</span>';
1019     if ($showquestiontext) {
1020         $formatoptions = new stdClass();
1021         $formatoptions->noclean = true;
1022         $formatoptions->para = false;
1023         $questiontext = strip_tags(format_text($question->questiontext,
1024                 $question->questiontextformat,
1025                 $formatoptions, $COURSE->id));
1026         $questiontext = shorten_text($questiontext, 200);
1027         $result .= '<span class="questiontext">';
1028         if (!empty($questiontext)) {
1029             $result .= $questiontext;
1030         } else {
1031             $result .= '<span class="error">';
1032             $result .= get_string('questiontextisempty', 'quiz');
1033             $result .= '</span>';
1034         }
1035         $result .= '</span>';
1036     }
1037     if ($return) {
1038         return $result;
1039     } else {
1040         echo $result;
1041     }
1044 /**
1045  * A column type for the add this question to the quiz.
1046  *
1047  * @copyright  2009 Tim Hunt
1048  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1049  */
1050 class question_bank_add_to_quiz_action_column extends question_bank_action_column_base {
1051     protected $stradd;
1053     public function init() {
1054         parent::init();
1055         $this->stradd = get_string('addtoquiz', 'quiz');
1056     }
1058     public function get_name() {
1059         return 'addtoquizaction';
1060     }
1062     protected function display_content($question, $rowclasses) {
1063         if (!question_has_capability_on($question, 'use')) {
1064             return;
1065         }
1066         // for RTL languages: switch right and left arrows
1067         if (right_to_left()) {
1068             $movearrow = 't/removeright';
1069         } else {
1070             $movearrow = 't/moveleft';
1071         }
1072         $this->print_icon($movearrow, $this->stradd, $this->qbank->add_to_quiz_url($question->id));
1073     }
1075     public function get_required_fields() {
1076         return array('q.id');
1077     }
1080 /**
1081  * A column type for the name followed by the start of the question text.
1082  *
1083  * @copyright  2009 Tim Hunt
1084  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1085  */
1086 class question_bank_question_name_text_column extends question_bank_question_name_column {
1087     public function get_name() {
1088         return 'questionnametext';
1089     }
1091     protected function display_content($question, $rowclasses) {
1092         echo '<div>';
1093         $labelfor = $this->label_for($question);
1094         if ($labelfor) {
1095             echo '<label for="' . $labelfor . '">';
1096         }
1097         echo quiz_question_tostring($question, false, true, true);
1098         if ($labelfor) {
1099             echo '</label>';
1100         }
1101         echo '</div>';
1102     }
1104     public function get_required_fields() {
1105         $fields = parent::get_required_fields();
1106         $fields[] = 'q.questiontext';
1107         $fields[] = 'q.questiontextformat';
1108         return $fields;
1109     }
1112 /**
1113  * Subclass to customise the view of the question bank for the quiz editing screen.
1114  *
1115  * @copyright  2009 Tim Hunt
1116  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1117  */
1118 class quiz_question_bank_view extends question_bank_view {
1119     protected $quizhasattempts = false;
1120     /** @var object the quiz settings. */
1121     protected $quiz = false;
1123     /**
1124      * Constructor
1125      * @param question_edit_contexts $contexts
1126      * @param moodle_url $pageurl
1127      * @param object $course course settings
1128      * @param object $cm activity settings.
1129      * @param object $quiz quiz settings.
1130      */
1131     public function __construct($contexts, $pageurl, $course, $cm, $quiz) {
1132         parent::__construct($contexts, $pageurl, $course, $cm);
1133         $this->quiz = $quiz;
1134     }
1136     protected function known_field_types() {
1137         $types = parent::known_field_types();
1138         $types[] = new question_bank_add_to_quiz_action_column($this);
1139         $types[] = new question_bank_question_name_text_column($this);
1140         return $types;
1141     }
1143     protected function wanted_columns() {
1144         return array('addtoquizaction', 'checkbox', 'qtype', 'questionnametext',
1145                 'editaction', 'previewaction');
1146     }
1148     protected function default_sort() {
1149         return array('qtype' => 1, 'questionnametext' => 1);
1150     }
1152     /**
1153      * Let the question bank display know whether the quiz has been attempted,
1154      * hence whether some bits of UI, like the add this question to the quiz icon,
1155      * should be displayed.
1156      * @param bool $quizhasattempts whether the quiz has attempts.
1157      */
1158     public function set_quiz_has_attempts($quizhasattempts) {
1159         $this->quizhasattempts = $quizhasattempts;
1160         if ($quizhasattempts && isset($this->visiblecolumns['addtoquizaction'])) {
1161             unset($this->visiblecolumns['addtoquizaction']);
1162         }
1163     }
1165     public function preview_question_url($question) {
1166         return quiz_question_preview_url($this->quiz, $question);
1167     }
1169     public function add_to_quiz_url($questionid) {
1170         global $CFG;
1171         $params = $this->baseurl->params();
1172         $params['addquestion'] = $questionid;
1173         $params['sesskey'] = sesskey();
1174         return new moodle_url('/mod/quiz/edit.php', $params);
1175     }
1177     public function display($tabname, $page, $perpage, $cat,
1178             $recurse, $showhidden, $showquestiontext) {
1179         global $OUTPUT;
1180         if ($this->process_actions_needing_ui()) {
1181             return;
1182         }
1184         // Display the current category.
1185         if (!$category = $this->get_current_category($cat)) {
1186             return;
1187         }
1188         $this->print_category_info($category);
1190         echo $OUTPUT->box_start('generalbox questionbank');
1192         $this->display_category_form($this->contexts->having_one_edit_tab_cap($tabname),
1193                 $this->baseurl, $cat);
1195         // continues with list of questions
1196         $this->display_question_list($this->contexts->having_one_edit_tab_cap($tabname),
1197                 $this->baseurl, $cat, $this->cm, $recurse, $page,
1198                 $perpage, $showhidden, $showquestiontext,
1199                 $this->contexts->having_cap('moodle/question:add'));
1201         $this->display_options($recurse, $showhidden, $showquestiontext);
1202         echo $OUTPUT->box_end();
1203     }
1205     protected function print_choose_category_message($categoryandcontext) {
1206         global $OUTPUT;
1207         echo $OUTPUT->box_start('generalbox questionbank');
1208         $this->display_category_form($this->contexts->having_one_edit_tab_cap('edit'),
1209                 $this->baseurl, $categoryandcontext);
1210         echo "<p style=\"text-align:center;\"><b>";
1211         print_string('selectcategoryabove', 'question');
1212         echo "</b></p>";
1213         echo $OUTPUT->box_end();
1214     }
1216     protected function print_category_info($category) {
1217         $formatoptions = new stdClass();
1218         $formatoptions->noclean = true;
1219         $strcategory = get_string('category', 'quiz');
1220         echo '<div class="categoryinfo"><div class="categorynamefieldcontainer">' .
1221                 $strcategory;
1222         echo ': <span class="categorynamefield">';
1223         echo shorten_text(strip_tags(format_string($category->name)), 60);
1224         echo '</span></div><div class="categoryinfofieldcontainer">' .
1225                 '<span class="categoryinfofield">';
1226         echo shorten_text(strip_tags(format_text($category->info, $category->infoformat,
1227                 $formatoptions, $this->course->id)), 200);
1228         echo '</span></div></div>';
1229     }
1231     protected function display_options($recurse, $showhidden, $showquestiontext) {
1232         echo '<form method="get" action="edit.php" id="displayoptions">';
1233         echo "<fieldset class='invisiblefieldset'>";
1234         echo html_writer::input_hidden_params($this->baseurl,
1235                 array('recurse', 'showhidden', 'qbshowtext'));
1236         $this->display_category_form_checkbox('recurse', $recurse,
1237                 get_string('includesubcategories', 'question'));
1238         $this->display_category_form_checkbox('showhidden', $showhidden,
1239                 get_string('showhidden', 'question'));
1240         echo '<noscript><div class="centerpara"><input type="submit" value="' .
1241                 get_string('go') . '" />';
1242         echo '</div></noscript></fieldset></form>';
1243     }
1246 /**
1247  * Prints the form for setting a quiz' overall grade
1248  *
1249  * @param object $quiz The quiz object of the quiz in question
1250  * @param object $pageurl The url of the current page with the parameters required
1251  *     for links returning to the current page, as a moodle_url object
1252  * @param int $tabindex The tabindex to start from for the form elements created
1253  * @return int The tabindex from which the calling page can continue, that is,
1254  *      the last value used +1.
1255  */
1256 function quiz_print_grading_form($quiz, $pageurl, $tabindex) {
1257     global $OUTPUT;
1258     $strsave = get_string('save', 'quiz');
1259     echo '<form method="post" action="edit.php" class="quizsavegradesform"><div>';
1260     echo '<fieldset class="invisiblefieldset" style="display: block;">';
1261     echo "<input type=\"hidden\" name=\"sesskey\" value=\"" . sesskey() . "\" />";
1262     echo html_writer::input_hidden_params($pageurl);
1263     $a = '<input type="text" id="inputmaxgrade" name="maxgrade" size="' .
1264             ($quiz->decimalpoints + 2) . '" tabindex="' . $tabindex
1265          . '" value="' . quiz_format_grade($quiz, $quiz->grade) . '" />';
1266     echo '<label for="inputmaxgrade">' . get_string('maximumgradex', '', $a) . "</label>";
1267     echo '<input type="hidden" name="savechanges" value="save" />';
1268     echo '<input type="submit" value="' . $strsave . '" />';
1269     echo '</fieldset>';
1270     echo "</div></form>\n";
1271     return $tabindex + 1;
1274 /**
1275  * Print the status bar
1276  *
1277  * @param object $quiz The quiz object of the quiz in question
1278  */
1279 function quiz_print_status_bar($quiz) {
1280     global $CFG;
1282     $bits = array();
1284     $bits[] = html_writer::tag('span',
1285             get_string('totalmarksx', 'quiz', quiz_format_grade($quiz, $quiz->sumgrades)),
1286             array('class' => 'totalpoints'));
1288     $bits[] = html_writer::tag('span',
1289             get_string('numquestionsx', 'quiz', quiz_number_of_questions_in_quiz($quiz->questions)),
1290             array('class' => 'numberofquestions'));
1292     $timenow = time();
1294     // Exact open and close dates for the tool-tip.
1295     $dates = array();
1296     if ($quiz->timeopen > 0) {
1297         if ($timenow > $quiz->timeopen) {
1298             $dates[] = get_string('quizopenedon', 'quiz', userdate($quiz->timeopen));
1299         } else {
1300             $dates[] = get_string('quizwillopen', 'quiz', userdate($quiz->timeopen));
1301         }
1302     }
1303     if ($quiz->timeclose > 0) {
1304         if ($timenow > $quiz->timeclose) {
1305             $dates[] = get_string('quizclosed', 'quiz', userdate($quiz->timeclose));
1306         } else {
1307             $dates[] = get_string('quizcloseson', 'quiz', userdate($quiz->timeclose));
1308         }
1309     }
1310     if (empty($dates)) {
1311         $dates[] = get_string('alwaysavailable', 'quiz');
1312     }
1313     $tooltip = implode(', ', $dates);;
1315     // Brief summary on the page.
1316     if ($timenow < $quiz->timeopen) {
1317         $currentstatus = get_string('quizisclosedwillopen', 'quiz',
1318                 userdate($quiz->timeopen, get_string('strftimedatetimeshort', 'langconfig')));
1319     } else if ($quiz->timeclose && $timenow <= $quiz->timeclose) {
1320         $currentstatus = get_string('quizisopenwillclose', 'quiz',
1321                 userdate($quiz->timeclose, get_string('strftimedatetimeshort', 'langconfig')));
1322     } else if ($quiz->timeclose && $timenow > $quiz->timeclose) {
1323         $currentstatus = get_string('quizisclosed', 'quiz');
1324     } else {
1325         $currentstatus = get_string('quizisopen', 'quiz');
1326     }
1328     $bits[] = html_writer::tag('span', $currentstatus,
1329             array('class' => 'quizopeningstatus', 'title' => implode(', ', $dates)));
1331     echo html_writer::tag('div', implode(' | ', $bits), array('class' => 'statusbar'));