Merge branch 'MDL-21625_b' of git://github.com/rwijaya/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 = 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 = '<input name="moveselectedonpagetop" type="text" size="2" ' .
431         $pagingdisabled . ' />';
432     $b = '<input name="moveselectedonpagebottom" type="text" size="2" ' .
433         $pagingdisabled . ' />';
435     $reordercontrols2top = '<div class="moveselectedonpage">' .
436         '<label>' . get_string('moveselectedonpage', 'quiz', $a) . '</label>' .
437         '<input type="submit" name="savechanges" value="' .
438         $strmove . '"  ' . $pagingdisabled . ' />' . '
439         <br /><input type="submit" name="savechanges" value="' .
440         $strreorderquestions . '" /></div>';
441     $reordercontrols2bottom = '<div class="moveselectedonpage">' .
442         '<input type="submit" name="savechanges" value="' .
443         $strreorderquestions . '" /><br />' .
444         '<label>' . get_string('moveselectedonpage', 'quiz', $b) . '</label>' .
445         '<input type="submit" name="savechanges" value="' .
446         $strmove . '"  ' . $pagingdisabled . ' /> ' . '</div>';
448     $reordercontrols3 = '<a href="javascript:select_all_in(\'FORM\', null, ' .
449             '\'quizquestions\');">' .
450             $strselectall . '</a> /';
451     $reordercontrols3.=    ' <a href="javascript:deselect_all_in(\'FORM\', ' .
452             'null, \'quizquestions\');">' .
453             $strselectnone . '</a>';
455     $reordercontrolstop = '<div class="reordercontrols">' .
456             $reordercontrolssetdefaultsubmit .
457             $reordercontrols1 . $reordercontrols2top . $reordercontrols3 . "</div>";
458     $reordercontrolsbottom = '<div class="reordercontrols">' .
459             $reordercontrolssetdefaultsubmit .
460             $reordercontrols2bottom . $reordercontrols1 . $reordercontrols3 . "</div>";
462     if ($reordertool) {
463         echo '<form method="post" action="edit.php" id="quizquestions"><div>';
465         echo html_writer::input_hidden_params($pageurl);
466         echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
468         echo $reordercontrolstop;
469     }
471     // The current question ordinal (no descriptions).
472     $qno = 1;
473     // The current question (includes questions and descriptions).
474     $questioncount = 0;
475     // The current page number in iteration.
476     $pagecount = 0;
478     $pageopen = false;
480     $returnurl = $pageurl->out_as_local_url(false);
481     $questiontotalcount = count($order);
483     foreach ($order as $count => $qnum) {
485         $reordercheckbox = '';
486         $reordercheckboxlabel = '';
487         $reordercheckboxlabelclose = '';
489         // If the questiontype is missing change the question type.
490         if ($qnum && !array_key_exists($qnum, $questions)) {
491             $fakequestion = new stdClass();
492             $fakequestion->id = $qnum;
493             $fakequestion->category = 0;
494             $fakequestion->qtype = 'missingtype';
495             $fakequestion->name = get_string('missingquestion', 'quiz');
496             $fakequestion->questiontext = ' ';
497             $fakequestion->questiontextformat = FORMAT_HTML;
498             $fakequestion->length = 1;
499             $questions[$qnum] = $fakequestion;
500             $quiz->grades[$qnum] = 0;
502         } else if ($qnum && !question_bank::qtype_exists($questions[$qnum]->qtype)) {
503             $questions[$qnum]->qtype = 'missingtype';
504         }
506         if ($qnum != 0 || ($qnum == 0 && !$pageopen)) {
507             // This is either a question or a page break after another (no page is currently open).
508             if (!$pageopen) {
509                 // If no page is open, start display of a page.
510                 $pagecount++;
511                 echo  '<div class="quizpage"><span class="pagetitle">' .
512                         get_string('page') . '&nbsp;' . $pagecount .
513                         '</span><div class="pagecontent">';
514                 $pageopen = true;
515             }
516             if ($qnum == 0  && $count < $questiontotalcount) {
517                 // This is the second successive page break. Tell the user the page is empty.
518                 echo '<div class="pagestatus">';
519                 print_string('noquestionsonpage', 'quiz');
520                 echo '</div>';
521                 if ($allowdelete) {
522                     echo '<div class="quizpagedelete">';
523                     echo $OUTPUT->action_icon($pageurl->out(true,
524                             array('deleteemptypage' => $count - 1, 'sesskey'=>sesskey())),
525                             new pix_icon('t/delete', $strremove),
526                             new component_action('click',
527                                     'M.core_scroll_manager.save_scroll_action'),
528                             array('title' => $strremove));
529                     echo '</div>';
530                 }
531             }
533             if ($qnum != 0) {
534                 $question = $questions[$qnum];
535                 $questionparams = array(
536                         'returnurl' => $returnurl,
537                         'cmid' => $quiz->cmid,
538                         'id' => $question->id);
539                 $questionurl = new moodle_url('/question/question.php',
540                         $questionparams);
541                 $questioncount++;
543                 // This is an actual question.
544                 ?>
545 <div class="question">
546     <div class="questioncontainer <?php echo $question->qtype; ?>">
547         <div class="qnum">
548                 <?php
549                 $reordercheckbox = '';
550                 $reordercheckboxlabel = '';
551                 $reordercheckboxlabelclose = '';
552                 if ($reordertool) {
553                     $reordercheckbox = '<input type="checkbox" name="s' . $question->id .
554                         '" id="s' . $question->id . '" />';
555                     $reordercheckboxlabel = '<label for="s' . $question->id . '">';
556                     $reordercheckboxlabelclose = '</label>';
557                 }
558                 if ($question->length == 0) {
559                     $qnodisplay = get_string('infoshort', 'quiz');
560                 } else if ($quiz->shufflequestions) {
561                     $qnodisplay = '?';
562                 } else {
563                     if ($qno > 999 || ($reordertool && $qno > 99)) {
564                         $qnodisplay = html_writer::tag('small', $qno);
565                     } else {
566                         $qnodisplay = $qno;
567                     }
568                     $qno += $question->length;
569                 }
570                 echo $reordercheckboxlabel . $qnodisplay . $reordercheckboxlabelclose .
571                         $reordercheckbox;
573                 ?>
574         </div>
575         <div class="content">
576             <div class="questioncontrols">
577                 <?php
578                 if ($count != 0) {
579                     if (!$hasattempts) {
580                         $upbuttonclass = '';
581                         if ($count >= $lastindex - 1) {
582                             $upbuttonclass = 'upwithoutdown';
583                         }
584                         echo $OUTPUT->action_icon($pageurl->out(true,
585                                 array('up' => $question->id, 'sesskey'=>sesskey())),
586                                 new pix_icon('t/up', $strmoveup),
587                                 new component_action('click',
588                                         'M.core_scroll_manager.save_scroll_action'),
589                                 array('title' => $strmoveup));
590                     }
592                 }
593                 if ($count < $lastindex - 1) {
594                     if (!$hasattempts) {
595                         echo $OUTPUT->action_icon($pageurl->out(true,
596                                 array('down' => $question->id, 'sesskey'=>sesskey())),
597                                 new pix_icon('t/down', $strmovedown),
598                                 new component_action('click',
599                                         'M.core_scroll_manager.save_scroll_action'),
600                                 array('title' => $strmovedown));
601                     }
602                 }
603                 if ($allowdelete && ($question->qtype == 'missingtype' ||
604                         question_has_capability_on($question, 'use', $question->category))) {
605                     // Remove from quiz, not question delete.
606                     if (!$hasattempts) {
607                         echo $OUTPUT->action_icon($pageurl->out(true,
608                                 array('remove' => $question->id, 'sesskey'=>sesskey())),
609                                 new pix_icon('t/delete', $strremove),
610                                 new component_action('click',
611                                         'M.core_scroll_manager.save_scroll_action'),
612                                 array('title' => $strremove));
613                     }
614                 }
615                 ?>
616             </div><?php
617                 if (!in_array($question->qtype, array('description', 'missingtype')) && !$reordertool) {
618                     ?>
619 <div class="points">
620 <form method="post" action="edit.php" class="quizsavegradesform"><div>
621     <fieldset class="invisiblefieldset" style="display: block;">
622     <label for="<?php echo "inputq$question->id" ?>"><?php echo $strmaxmark; ?></label>:<br />
623     <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
624     <?php echo html_writer::input_hidden_params($pageurl); ?>
625     <input type="hidden" name="savechanges" value="save" />
626                     <?php
627                     echo '<input type="text" name="g' . $question->id .
628                             '" id="inputq' . $question->id .
629                             '" size="' . ($quiz->decimalpoints + 2) .
630                             '" value="' . (0 + $quiz->grades[$qnum]) .
631                             '" tabindex="' . ($lastindex + $qno) . '" />';
632                     ?>
633         <input type="submit" class="pointssubmitbutton" value="<?php echo $strsave; ?>" />
634     </fieldset>
635                     <?php
636                     if ($question->qtype == 'random') {
637                         echo '<a href="' . $questionurl->out() .
638                                 '" class="configurerandomquestion">' .
639                                 get_string("configurerandomquestion", "quiz") . '</a>';
640                     }
642                     ?>
643 </div>
644 </form>
646             </div>
647                     <?php
648                 } else if ($reordertool) {
649                     if ($qnum) {
650                         ?>
651 <div class="qorder">
652                         <?php
653                         echo '<label class="accesshide" for="o' . $question->id . '">' .
654                                 get_string('questionposition', 'quiz', $qnodisplay) . '</label>';
655                         echo '<input type="text" name="o' . $question->id .
656                                 '" id="o' . $question->id . '"' .
657                                 '" size="2" value="' . (10*$count + 10) .
658                                 '" tabindex="' . ($lastindex + $qno) . '" />';
659                         ?>
660 </div>
661                         <?php
662                     }
663                 }
664                 ?>
665             <div class="questioncontentcontainer">
666                 <?php
667                 if ($question->qtype == 'random') { // It is a random question.
668                     if (!$reordertool) {
669                         quiz_print_randomquestion($question, $pageurl, $quiz, $quiz_qbanktool);
670                     } else {
671                         quiz_print_randomquestion_reordertool($question, $pageurl, $quiz);
672                     }
673                 } else { // It is a single question.
674                     if (!$reordertool) {
675                         quiz_print_singlequestion($question, $returnurl, $quiz);
676                     } else {
677                         quiz_print_singlequestion_reordertool($question, $returnurl, $quiz);
678                     }
679                 }
680                 ?>
681             </div>
682         </div>
683     </div>
684 </div>
686                 <?php
687             }
688         }
689         // A page break: end the existing page.
690         if ($qnum == 0) {
691             if ($pageopen) {
692                 if (!$reordertool && !($quiz->shufflequestions &&
693                         $count < $questiontotalcount - 1)) {
694                     quiz_print_pagecontrols($quiz, $pageurl, $pagecount,
695                             $hasattempts, $defaultcategoryobj, $canaddquestion, $canaddrandom);
696                 } else if ($count < $questiontotalcount - 1) {
697                     // Do not include the last page break for reordering
698                     // to avoid creating a new extra page in the end.
699                     echo '<input type="hidden" name="opg' . $pagecount . '" size="2" value="' .
700                             (10*$count + 10) . '" />';
701                 }
702                 echo "</div></div>";
704                 if (!$reordertool && !$quiz->shufflequestions) {
705                     echo $OUTPUT->container_start('addpage');
706                     $url = new moodle_url($pageurl->out_omit_querystring(),
707                             array('cmid' => $quiz->cmid, 'courseid' => $quiz->course,
708                                     'addpage' => $count, 'sesskey' => sesskey()));
709                     echo $OUTPUT->single_button($url, get_string('addpagehere', 'quiz'), 'post',
710                             array('disabled' => $hasattempts,
711                             'actions' => array(new component_action('click',
712                                     'M.core_scroll_manager.save_scroll_action'))));
713                     echo $OUTPUT->container_end();
714                 }
715                 $pageopen = false;
716                 $count++;
717             }
718         }
720     }
721     if ($reordertool) {
722         echo $reordercontrolsbottom;
723         echo '</div></form>';
724     }
727 /**
728  * Print all the controls for adding questions directly into the
729  * specific page in the edit tab of edit.php
730  *
731  * @param object $quiz This is not the standard quiz object used elsewhere but
732  *     it contains the quiz layout in $quiz->questions and the grades in
733  *     $quiz->grades
734  * @param moodle_url $pageurl The url of the current page with the parameters required
735  *     for links returning to the current page, as a moodle_url object
736  * @param int $page the current page number.
737  * @param bool $hasattempts  Indicates whether the quiz has attempts
738  * @param object $defaultcategoryobj
739  * @param bool $canaddquestion is the user able to add and use questions anywere?
740  * @param bool $canaddrandom is the user able to add random questions anywere?
741  */
742 function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts,
743         $defaultcategoryobj, $canaddquestion, $canaddrandom) {
744     global $CFG, $OUTPUT;
745     static $randombuttoncount = 0;
746     $randombuttoncount++;
747     echo '<div class="pagecontrols">';
749     // Get the current context.
750     $thiscontext = context_course::instance($quiz->course);
751     $contexts = new question_edit_contexts($thiscontext);
753     // Get the default category.
754     list($defaultcategoryid) = explode(',', $pageurl->param('cat'));
755     if (empty($defaultcategoryid)) {
756         $defaultcategoryid = $defaultcategoryobj->id;
757     }
759     if ($canaddquestion) {
760         // Create the url the question page will return to.
761         $returnurladdtoquiz = new moodle_url($pageurl, array('addonpage' => $page));
763         // Print a button linking to the choose question type page.
764         $returnurladdtoquiz = $returnurladdtoquiz->out_as_local_url(false);
765         $newquestionparams = array('returnurl' => $returnurladdtoquiz,
766                 'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion');
767         create_new_question_button($defaultcategoryid, $newquestionparams,
768                 get_string('addaquestion', 'quiz'),
769                 get_string('createquestionandadd', 'quiz'), $hasattempts);
770     }
772     if ($hasattempts) {
773         $disabled = 'disabled="disabled"';
774     } else {
775         $disabled = '';
776     }
777     if ($canaddrandom) {
778     ?>
779     <div class="singlebutton">
780         <form class="randomquestionform" action="<?php echo $CFG->wwwroot;
781                 ?>/mod/quiz/addrandom.php" method="get">
782             <div>
783                 <input type="hidden" class="addonpage_formelement" name="addonpage" value="<?php
784                         echo $page; ?>" />
785                 <input type="hidden" name="cmid" value="<?php echo $quiz->cmid; ?>" />
786                 <input type="hidden" name="courseid" value="<?php echo $quiz->course; ?>" />
787                 <input type="hidden" name="category" value="<?php
788                         echo $pageurl->param('cat'); ?>" />
789                 <input type="hidden" name="returnurl" value="<?php
790                         echo s(str_replace($CFG->wwwroot, '', $pageurl->out(false))); ?>" />
791                 <input type="submit" id="addrandomdialoglaunch_<?php
792                         echo $randombuttoncount; ?>" value="<?php
793                         echo get_string('addarandomquestion', 'quiz'); ?>" <?php
794                         echo " $disabled"; ?> />
795             </div>
796         </form>
797     </div>
798     <?php echo $OUTPUT->help_icon('addarandomquestion', 'quiz');
799     }
800     echo "\n</div>";
803 /**
804  * Print a given single question in quiz for the edit tab of edit.php.
805  * Meant to be used from quiz_print_question_list()
806  *
807  * @param object $question A question object from the database questions table
808  * @param object $returnurl The url to get back to this page, for example after editing.
809  * @param object $quiz The quiz in the context of which the question is being displayed
810  */
811 function quiz_print_singlequestion($question, $returnurl, $quiz) {
812     echo '<div class="singlequestion ' . $question->qtype . '">';
813     echo quiz_question_edit_button($quiz->cmid, $question, $returnurl,
814             quiz_question_tostring($question) . ' ');
815     echo '<span class="questiontype">';
816     echo print_question_icon($question);
817     echo ' ' . question_bank::get_qtype_name($question->qtype) . '</span>';
818     echo '<span class="questionpreview">' .
819             quiz_question_preview_button($quiz, $question, true) . '</span>';
820     echo "</div>\n";
822 /**
823  * Print a given random question in quiz for the edit tab of edit.php.
824  * Meant to be used from quiz_print_question_list()
825  *
826  * @param object $question A question object from the database questions table
827  * @param object $questionurl The url of the question editing page as a moodle_url object
828  * @param object $quiz The quiz in the context of which the question is being displayed
829  * @param bool $quiz_qbanktool Indicate to this function if the question bank window open
830  */
831 function quiz_print_randomquestion(&$question, &$pageurl, &$quiz, $quiz_qbanktool) {
832     global $DB, $OUTPUT;
833     echo '<div class="quiz_randomquestion">';
835     if (!$category = $DB->get_record('question_categories',
836             array('id' => $question->category))) {
837         echo $OUTPUT->notification('Random question category not found!');
838         return;
839     }
841     echo '<div class="randomquestionfromcategory">';
842     echo print_question_icon($question);
843     print_random_option_icon($question);
844     echo ' ' . get_string('randomfromcategory', 'quiz') . '</div>';
846     $a = new stdClass();
847     $a->arrow = $OUTPUT->rarrow();
848     $strshowcategorycontents = get_string('showcategorycontents', 'quiz', $a);
850     $openqbankurl = $pageurl->out(true, array('qbanktool' => 1,
851             'cat' => $category->id . ',' . $category->contextid));
852     $linkcategorycontents = ' <a href="' . $openqbankurl . '">' . $strshowcategorycontents . '</a>';
854     echo '<div class="randomquestioncategory">';
855     echo '<a href="' . $openqbankurl . '" title="' . $strshowcategorycontents . '">' .
856             $category->name . '</a>';
857     echo '<span class="questionpreview">' .
858             quiz_question_preview_button($quiz, $question, true) . '</span>';
859     echo '</div>';
861     $questionids = question_bank::get_qtype('random')->get_available_questions_from_category(
862             $category->id, $question->questiontext == '1', '0');
863     $questioncount = count($questionids);
865     echo '<div class="randomquestionqlist">';
866     if ($questioncount == 0) {
867         // No questions in category, give an error plus instructions.
868         echo '<span class="error">';
869         print_string('noquestionsnotinuse', 'quiz');
870         echo '</span>';
871         echo '<br />';
873         // Embed the link into the string with instructions.
874         $a = new stdClass();
875         $a->catname = '<strong>' . $category->name . '</strong>';
876         $a->link = $linkcategorycontents;
877         echo get_string('addnewquestionsqbank', 'quiz', $a);
879     } else {
880         // Category has questions.
882         // Get a sample from the database.
883         $questionidstoshow = array_slice($questionids, 0, NUM_QS_TO_SHOW_IN_RANDOM);
884         $questionstoshow = $DB->get_records_list('question', 'id', $questionidstoshow,
885                 '', 'id, qtype, name, questiontext, questiontextformat');
887         // Then list them.
888         echo '<ul>';
889         foreach ($questionstoshow as $question) {
890             echo '<li>' . quiz_question_tostring($question, true) . '</li>';
891         }
893         // Finally display the total number.
894         echo '<li class="totalquestionsinrandomqcategory">';
895         if ($questioncount > NUM_QS_TO_SHOW_IN_RANDOM) {
896             echo '... ';
897         }
898         print_string('totalquestionsinrandomqcategory', 'quiz', $questioncount);
899         echo ' ' . $linkcategorycontents;
900         echo '</li>';
901         echo '</ul>';
902     }
904     echo '</div>';
905     echo '<div class="randomquestioncategorycount">';
906     echo '</div>';
907     echo '</div>';
910 /**
911  * Print a given single question in quiz for the reordertool tab of edit.php.
912  * Meant to be used from quiz_print_question_list()
913  *
914  * @param object $question A question object from the database questions table
915  * @param object $questionurl The url of the question editing page as a moodle_url object
916  * @param object $quiz The quiz in the context of which the question is being displayed
917  */
918 function quiz_print_singlequestion_reordertool($question, $returnurl, $quiz) {
919     echo '<div class="singlequestion ' . $question->qtype . '">';
920     echo '<label for="s' . $question->id . '">';
921     echo print_question_icon($question);
922     echo ' ' . quiz_question_tostring($question);
923     echo '</label>';
924     echo '<span class="questionpreview">' .
925             quiz_question_action_icons($quiz, $quiz->cmid, $question, $returnurl) . '</span>';
926     echo "</div>\n";
929 /**
930  * Print a given random question in quiz for the reordertool tab of edit.php.
931  * Meant to be used from quiz_print_question_list()
932  *
933  * @param object $question A question object from the database questions table
934  * @param object $questionurl The url of the question editing page as a moodle_url object
935  * @param object $quiz The quiz in the context of which the question is being displayed
936  */
937 function quiz_print_randomquestion_reordertool(&$question, &$pageurl, &$quiz) {
938     global $DB, $OUTPUT;
940     // Load the category, and the number of available questions in it.
941     if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
942         echo $OUTPUT->notification('Random question category not found!');
943         return;
944     }
945     $questioncount = count(question_bank::get_qtype(
946             'random')->get_available_questions_from_category(
947             $category->id, $question->questiontext == '1', '0'));
949     $reordercheckboxlabel = '<label for="s' . $question->id . '">';
950     $reordercheckboxlabelclose = '</label>';
952     echo '<div class="quiz_randomquestion">';
953     echo '<div class="randomquestionfromcategory">';
954     echo $reordercheckboxlabel;
955     echo print_question_icon($question);
956     print_random_option_icon($question);
958     if ($questioncount == 0) {
959         echo '<span class="error">';
960         print_string('empty', 'quiz');
961         echo '</span> ';
962     }
964     print_string('random', 'quiz');
965     echo ": $reordercheckboxlabelclose</div>";
967     echo '<div class="randomquestioncategory">';
968     echo $reordercheckboxlabel . $category->name . $reordercheckboxlabelclose;
969     echo '<span class="questionpreview">';
970     echo quiz_question_preview_button($quiz, $question, false);
971     echo '</span>';
972     echo "</div>";
974     echo '<div class="randomquestioncategorycount">';
975     echo '</div>';
976     echo '</div>';
979 /**
980  * Print an icon to indicate the 'include subcategories' state of a random question.
981  * @param $question the random question.
982  */
983 function print_random_option_icon($question) {
984     global $OUTPUT;
985     if (!empty($question->questiontext)) {
986         $icon = 'withsubcat';
987         $tooltip = get_string('randomwithsubcat', 'quiz');
988     } else {
989         $icon = 'nosubcat';
990         $tooltip = get_string('randomnosubcat', 'quiz');
991     }
992     echo '<img src="' . $OUTPUT->pix_url('i/' . $icon) . '" alt="' .
993             $tooltip . '" title="' . $tooltip . '" class="uihint" />';
996 /**
997  * Creates a textual representation of a question for display.
998  *
999  * @param object $question A question object from the database questions table
1000  * @param bool $showicon If true, show the question's icon with the question. False by default.
1001  * @param bool $showquestiontext If true (default), show question text after question name.
1002  *       If false, show only question name.
1003  * @param bool $return If true (default), return the output. If false, print it.
1004  */
1005 function quiz_question_tostring($question, $showicon = false,
1006         $showquestiontext = true, $return = true) {
1007     global $COURSE;
1008     $result = '';
1009     $result .= '<span class="questionname">';
1010     if ($showicon) {
1011         $result .= print_question_icon($question, true);
1012         echo ' ';
1013     }
1014     $result .= shorten_text(format_string($question->name), 200) . '</span>';
1015     if ($showquestiontext) {
1016         $formatoptions = new stdClass();
1017         $formatoptions->noclean = true;
1018         $formatoptions->para = false;
1019         $questiontext = strip_tags(format_text($question->questiontext,
1020                 $question->questiontextformat,
1021                 $formatoptions, $COURSE->id));
1022         $questiontext = shorten_text($questiontext, 200);
1023         $result .= '<span class="questiontext">';
1024         if (!empty($questiontext)) {
1025             $result .= $questiontext;
1026         } else {
1027             $result .= '<span class="error">';
1028             $result .= get_string('questiontextisempty', 'quiz');
1029             $result .= '</span>';
1030         }
1031         $result .= '</span>';
1032     }
1033     if ($return) {
1034         return $result;
1035     } else {
1036         echo $result;
1037     }
1040 /**
1041  * A column type for the add this question to the quiz.
1042  *
1043  * @copyright  2009 Tim Hunt
1044  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1045  */
1046 class question_bank_add_to_quiz_action_column extends question_bank_action_column_base {
1047     protected $stradd;
1049     public function init() {
1050         parent::init();
1051         $this->stradd = get_string('addtoquiz', 'quiz');
1052     }
1054     public function get_name() {
1055         return 'addtoquizaction';
1056     }
1058     protected function display_content($question, $rowclasses) {
1059         if (!question_has_capability_on($question, 'use')) {
1060             return;
1061         }
1062         // For RTL languages: switch right and left arrows.
1063         if (right_to_left()) {
1064             $movearrow = 't/removeright';
1065         } else {
1066             $movearrow = 't/moveleft';
1067         }
1068         $this->print_icon($movearrow, $this->stradd, $this->qbank->add_to_quiz_url($question->id));
1069     }
1071     public function get_required_fields() {
1072         return array('q.id');
1073     }
1076 /**
1077  * A column type for the name followed by the start of the question text.
1078  *
1079  * @copyright  2009 Tim Hunt
1080  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1081  */
1082 class question_bank_question_name_text_column extends question_bank_question_name_column {
1083     public function get_name() {
1084         return 'questionnametext';
1085     }
1087     protected function display_content($question, $rowclasses) {
1088         echo '<div>';
1089         $labelfor = $this->label_for($question);
1090         if ($labelfor) {
1091             echo '<label for="' . $labelfor . '">';
1092         }
1093         echo quiz_question_tostring($question, false, true, true);
1094         if ($labelfor) {
1095             echo '</label>';
1096         }
1097         echo '</div>';
1098     }
1100     public function get_required_fields() {
1101         $fields = parent::get_required_fields();
1102         $fields[] = 'q.questiontext';
1103         $fields[] = 'q.questiontextformat';
1104         return $fields;
1105     }
1108 /**
1109  * Subclass to customise the view of the question bank for the quiz editing screen.
1110  *
1111  * @copyright  2009 Tim Hunt
1112  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1113  */
1114 class quiz_question_bank_view extends question_bank_view {
1115     protected $quizhasattempts = false;
1116     /** @var object the quiz settings. */
1117     protected $quiz = false;
1119     /**
1120      * Constructor
1121      * @param question_edit_contexts $contexts
1122      * @param moodle_url $pageurl
1123      * @param object $course course settings
1124      * @param object $cm activity settings.
1125      * @param object $quiz quiz settings.
1126      */
1127     public function __construct($contexts, $pageurl, $course, $cm, $quiz) {
1128         parent::__construct($contexts, $pageurl, $course, $cm);
1129         $this->quiz = $quiz;
1130     }
1132     protected function known_field_types() {
1133         $types = parent::known_field_types();
1134         $types[] = new question_bank_add_to_quiz_action_column($this);
1135         $types[] = new question_bank_question_name_text_column($this);
1136         return $types;
1137     }
1139     protected function wanted_columns() {
1140         return array('addtoquizaction', 'checkbox', 'qtype', 'questionnametext',
1141                 'editaction', 'previewaction');
1142     }
1144     /**
1145      * Specify the column heading
1146      *
1147      * @return string Column name for the heading
1148      */
1149     protected function heading_column() {
1150         return 'questionnametext';
1151     }
1153     protected function default_sort() {
1154         $this->requiredcolumns['qtype'] = $this->knowncolumntypes['qtype'];
1155         $this->requiredcolumns['questionnametext'] = $this->knowncolumntypes['questionnametext'];
1156         return array('qtype' => 1, 'questionnametext' => 1);
1157     }
1159     /**
1160      * Let the question bank display know whether the quiz has been attempted,
1161      * hence whether some bits of UI, like the add this question to the quiz icon,
1162      * should be displayed.
1163      * @param bool $quizhasattempts whether the quiz has attempts.
1164      */
1165     public function set_quiz_has_attempts($quizhasattempts) {
1166         $this->quizhasattempts = $quizhasattempts;
1167         if ($quizhasattempts && isset($this->visiblecolumns['addtoquizaction'])) {
1168             unset($this->visiblecolumns['addtoquizaction']);
1169         }
1170     }
1172     public function preview_question_url($question) {
1173         return quiz_question_preview_url($this->quiz, $question);
1174     }
1176     public function add_to_quiz_url($questionid) {
1177         global $CFG;
1178         $params = $this->baseurl->params();
1179         $params['addquestion'] = $questionid;
1180         $params['sesskey'] = sesskey();
1181         return new moodle_url('/mod/quiz/edit.php', $params);
1182     }
1184     public function display($tabname, $page, $perpage, $cat,
1185             $recurse, $showhidden, $showquestiontext) {
1186         global $OUTPUT;
1187         if ($this->process_actions_needing_ui()) {
1188             return;
1189         }
1191         // Display the current category.
1192         if (!$category = $this->get_current_category($cat)) {
1193             return;
1194         }
1195         $this->print_category_info($category);
1197         echo $OUTPUT->box_start('generalbox questionbank');
1199         $this->display_category_form($this->contexts->having_one_edit_tab_cap($tabname),
1200                 $this->baseurl, $cat);
1202         // Continues with list of questions.
1203         $this->display_question_list($this->contexts->having_one_edit_tab_cap($tabname),
1204                 $this->baseurl, $cat, $this->cm, $recurse, $page,
1205                 $perpage, $showhidden, $showquestiontext,
1206                 $this->contexts->having_cap('moodle/question:add'));
1208         $this->display_options($recurse, $showhidden, $showquestiontext);
1209         echo $OUTPUT->box_end();
1210     }
1212     protected function print_choose_category_message($categoryandcontext) {
1213         global $OUTPUT;
1214         echo $OUTPUT->box_start('generalbox questionbank');
1215         $this->display_category_form($this->contexts->having_one_edit_tab_cap('edit'),
1216                 $this->baseurl, $categoryandcontext);
1217         echo "<p style=\"text-align:center;\"><b>";
1218         print_string('selectcategoryabove', 'question');
1219         echo "</b></p>";
1220         echo $OUTPUT->box_end();
1221     }
1223     protected function print_category_info($category) {
1224         $formatoptions = new stdClass();
1225         $formatoptions->noclean = true;
1226         $strcategory = get_string('category', 'quiz');
1227         echo '<div class="categoryinfo"><div class="categorynamefieldcontainer">' .
1228                 $strcategory;
1229         echo ': <span class="categorynamefield">';
1230         echo shorten_text(strip_tags(format_string($category->name)), 60);
1231         echo '</span></div><div class="categoryinfofieldcontainer">' .
1232                 '<span class="categoryinfofield">';
1233         echo shorten_text(strip_tags(format_text($category->info, $category->infoformat,
1234                 $formatoptions, $this->course->id)), 200);
1235         echo '</span></div></div>';
1236     }
1238     protected function display_options($recurse, $showhidden, $showquestiontext) {
1239         echo '<form method="get" action="edit.php" id="displayoptions">';
1240         echo "<fieldset class='invisiblefieldset'>";
1241         echo html_writer::input_hidden_params($this->baseurl,
1242                 array('recurse', 'showhidden', 'qbshowtext'));
1243         $this->display_category_form_checkbox('recurse', $recurse,
1244                 get_string('includesubcategories', 'question'));
1245         $this->display_category_form_checkbox('showhidden', $showhidden,
1246                 get_string('showhidden', 'question'));
1247         echo '<noscript><div class="centerpara"><input type="submit" value="' .
1248                 get_string('go') . '" />';
1249         echo '</div></noscript></fieldset></form>';
1250     }
1253 /**
1254  * Prints the form for setting a quiz' overall grade
1255  *
1256  * @param object $quiz The quiz object of the quiz in question
1257  * @param object $pageurl The url of the current page with the parameters required
1258  *     for links returning to the current page, as a moodle_url object
1259  * @param int $tabindex The tabindex to start from for the form elements created
1260  * @return int The tabindex from which the calling page can continue, that is,
1261  *      the last value used +1.
1262  */
1263 function quiz_print_grading_form($quiz, $pageurl, $tabindex) {
1264     global $OUTPUT;
1265     $strsave = get_string('save', 'quiz');
1266     echo '<form method="post" action="edit.php" class="quizsavegradesform"><div>';
1267     echo '<fieldset class="invisiblefieldset" style="display: block;">';
1268     echo "<input type=\"hidden\" name=\"sesskey\" value=\"" . sesskey() . "\" />";
1269     echo html_writer::input_hidden_params($pageurl);
1270     $a = '<input type="text" id="inputmaxgrade" name="maxgrade" size="' .
1271             ($quiz->decimalpoints + 2) . '" tabindex="' . $tabindex
1272          . '" value="' . quiz_format_grade($quiz, $quiz->grade) . '" />';
1273     echo '<label for="inputmaxgrade">' . get_string('maximumgradex', '', $a) . "</label>";
1274     echo '<input type="hidden" name="savechanges" value="save" />';
1275     echo '<input type="submit" value="' . $strsave . '" />';
1276     echo '</fieldset>';
1277     echo "</div></form>\n";
1278     return $tabindex + 1;
1281 /**
1282  * Print the status bar
1283  *
1284  * @param object $quiz The quiz object of the quiz in question
1285  */
1286 function quiz_print_status_bar($quiz) {
1287     global $CFG;
1289     $bits = array();
1291     $bits[] = html_writer::tag('span',
1292             get_string('totalmarksx', 'quiz', quiz_format_grade($quiz, $quiz->sumgrades)),
1293             array('class' => 'totalpoints'));
1295     $bits[] = html_writer::tag('span',
1296             get_string('numquestionsx', 'quiz', quiz_number_of_questions_in_quiz($quiz->questions)),
1297             array('class' => 'numberofquestions'));
1299     $timenow = time();
1301     // Exact open and close dates for the tool-tip.
1302     $dates = array();
1303     if ($quiz->timeopen > 0) {
1304         if ($timenow > $quiz->timeopen) {
1305             $dates[] = get_string('quizopenedon', 'quiz', userdate($quiz->timeopen));
1306         } else {
1307             $dates[] = get_string('quizwillopen', 'quiz', userdate($quiz->timeopen));
1308         }
1309     }
1310     if ($quiz->timeclose > 0) {
1311         if ($timenow > $quiz->timeclose) {
1312             $dates[] = get_string('quizclosed', 'quiz', userdate($quiz->timeclose));
1313         } else {
1314             $dates[] = get_string('quizcloseson', 'quiz', userdate($quiz->timeclose));
1315         }
1316     }
1317     if (empty($dates)) {
1318         $dates[] = get_string('alwaysavailable', 'quiz');
1319     }
1320     $tooltip = implode(', ', $dates);;
1322     // Brief summary on the page.
1323     if ($timenow < $quiz->timeopen) {
1324         $currentstatus = get_string('quizisclosedwillopen', 'quiz',
1325                 userdate($quiz->timeopen, get_string('strftimedatetimeshort', 'langconfig')));
1326     } else if ($quiz->timeclose && $timenow <= $quiz->timeclose) {
1327         $currentstatus = get_string('quizisopenwillclose', 'quiz',
1328                 userdate($quiz->timeclose, get_string('strftimedatetimeshort', 'langconfig')));
1329     } else if ($quiz->timeclose && $timenow > $quiz->timeclose) {
1330         $currentstatus = get_string('quizisclosed', 'quiz');
1331     } else {
1332         $currentstatus = get_string('quizisopen', 'quiz');
1333     }
1335     $bits[] = html_writer::tag('span', $currentstatus,
1336             array('class' => 'quizopeningstatus', 'title' => implode(', ', $dates)));
1338     echo html_writer::tag('div', implode(' | ', $bits), array('class' => 'statusbar'));