cvsimport fixups
[moodle.git] / question / editlib.php
CommitLineData
516cf3eb 1<?php // $Id$
f4b879dd 2
3///////////////////////////////////////////////////////////////////////////
4// //
5// NOTICE OF COPYRIGHT //
6// //
7// Moodle - Modular Object-Oriented Dynamic Learning Environment //
8// http://moodle.org //
9// //
10// Copyright (C) 1999 onwards Martin Dougiamas and others //
11// //
12// This program is free software; you can redistribute it and/or modify //
13// it under the terms of the GNU General Public License as published by //
14// the Free Software Foundation; either version 2 of the License, or //
15// (at your option) any later version. //
16// //
17// This program is distributed in the hope that it will be useful, //
18// but WITHOUT ANY WARRANTY; without even the implied warranty of //
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
20// GNU General Public License for more details: //
21// //
22// http://www.gnu.org/copyleft/gpl.html //
23// //
24///////////////////////////////////////////////////////////////////////////
25
516cf3eb 26/**
b72ff476 27 * Functions used to show question editing interface
4323d029 28 *
4323d029 29 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
30 * @package questionbank
f4b879dd 31 *//** */
516cf3eb 32
4fbfd971 33require_once($CFG->libdir.'/questionlib.php');
516cf3eb 34
f92cf442 35define('DEFAULT_QUESTIONS_PER_PAGE', 20);
36
f4b879dd 37function get_module_from_cmid($cmid) {
f34488b2 38 global $CFG, $DB;
39 if (!$cmrec = $DB->get_record_sql("SELECT cm.*, md.name as modname
40 FROM {course_modules} cm,
41 {modules} md
42 WHERE cm.id = ? AND
43 md.id = cm.module", array($cmid))){
1eda5201 44 print_error('invalidcoursemodule');
f34488b2 45 } elseif (!$modrec =$DB->get_record($cmrec->modname, array('id' => $cmrec->instance))) {
1eda5201 46 print_error('invalidcoursemodule');
86909ce0 47 }
48 $modrec->instance = $modrec->id;
d0e88092 49 $modrec->cmid = $cmrec->id;
ea16c547 50 $cmrec->name = $modrec->name;
271e6dec 51
86909ce0 52 return array($modrec, $cmrec);
53}
4fbfd971 54/**
55* Function to read all questions for category into big array
56*
57* @param int $category category number
36e2232e 58* @param bool $noparent if true only questions with NO parent will be selected
59* @param bool $recurse include subdirectories
5fd8f999 60* @param bool $export set true if this is called by questionbank export
4fbfd971 61* @author added by Howard Miller June 2004
62*/
5fd8f999 63function get_questions_category( $category, $noparent=false, $recurse=true, $export=true ) {
4fbfd971 64
f34488b2 65 global $QTYPES, $DB;
4fbfd971 66
67 // questions will be added to an array
68 $qresults = array();
69
70 // build sql bit for $noparent
71 $npsql = '';
72 if ($noparent) {
73 $npsql = " and parent='0' ";
74 }
75
36e2232e 76 // get (list) of categories
77 if ($recurse) {
78 $categorylist = question_categorylist( $category->id );
79 }
80 else {
81 $categorylist = $category->id;
82 }
83
4fbfd971 84 // get the list of questions for the category
f34488b2 85 list ($usql, $params) = $DB->get_in_or_equal(explode(',', $categorylist));
86 if ($questions = $DB->get_records_select("question","category $usql $npsql", $params, "qtype, name ASC")) {
4fbfd971 87
88 // iterate through questions, getting stuff we need
89 foreach($questions as $question) {
90 $questiontype = $QTYPES[$question->qtype];
5fd8f999 91 $question->export_process = $export;
4fbfd971 92 $questiontype->get_question_options( $question );
93 $qresults[] = $question;
94 }
95 }
96
97 return $qresults;
98}
99
df4e2244 100/**
101 * @param integer $categoryid a category id.
102 * @return boolean whether this is the only top-level category in a context.
103 */
104function question_is_only_toplevel_category_in_context($categoryid) {
105 global $DB;
106 return 1 == $DB->count_records_sql("
107 SELECT count(*)
108 FROM {question_categories} c1,
109 {question_categories} c2
110 WHERE c2.id = ?
111 AND c1.contextid = c2.contextid
112 AND c1.parent = 0 AND c2.parent = 0", array($categoryid));
113}
4fbfd971 114
df4e2244 115/**
116 * Check whether this user is allowed to delete this category.
117 *
118 * @param integer $todelete a category id.
119 */
120function question_can_delete_cat($todelete) {
121 global $DB;
122 if (question_is_only_toplevel_category_in_context($todelete)) {
ac93d63d 123 print_error('cannotdeletecate', 'question');
271e6dec 124 } else {
df4e2244 125 $contextid = $DB->get_field('question_categories', 'contextid', array('id' => $todelete));
271e6dec 126 require_capability('moodle/question:managecategory', get_context_instance_by_id($contextid));
127 }
128}
f4b879dd 129
46795afc 130abstract class question_bank_column_base {
131
132 /**
133 * Output the column header cell.
134 * @param integer $currentsort 0 for none. 1 for normal sort, -1 for reverse sort.
135 */
136 public function display_header($currentsort) {
137 // TODO.
138 }
139
140 /**
141 * Output this column.
142 * @param object $question the row from the $question table, augmented with extra information.
143 * @param string $rowclasses CSS class names that should be applied to this row of output.
144 */
145 public function display($question, $rowclasses) {
146 $this->display_start($question, $rowclasses);
147 $this->display_content($question, $rowclasses);
148 $this->display_end($question, $rowclasses);
149 }
150
151 protected function display_start($question, $rowclasses) {
152 echo '<td class="' . $this->get_css_classes($question, $rowclasses) . '">';
153 }
154
155 /**
156 * @param object $question the row from the $question table, augmented with extra information.
157 * @param string $rowclasses CSS class names that should be applied to this row of output.
158 * @return string CSS class names that should be applied to this column. Should normally be
159 * a simple work relating the the column type like 'questionname'.
160 */
161 abstract protected function get_css_classes($question, $rowclasses);
162
163 /**
164 * Output the contents of this column.
165 * @param object $question the row from the $question table, augmented with extra information.
166 * @param string $rowclasses CSS class names that should be applied to this row of output.
167 */
168 abstract protected function display_content($question, $rowclasses);
169
170 protected function display_end($question, $rowclasses) {
171 echo '</td>';
172 }
173
174 /**
175 * Return an array 'table_alias' => 'JOIN clause' to bring in any data that
176 * this column required.
177 *
178 * The return values for all the columns will be checked. It is OK if two
179 * columns join in the same table with the same alias and identical JOIN clauses.
180 * If to columns try to use the same alias with different joins, you get an error.
181 * The only table included by default is the question table, which is aliased to 'q'.
182 *
183 * @return array 'table_alias' => 'JOIN clause'
184 */
185 public function get_extra_joins() {
186 return array();
187 }
188
189 /**
190 * @return array fields required. use table alias 'q' for the question table, or one of the
191 * ones from get_extra_joins. Every field requested must specify a table prefix.
192 */
193 public function get_required_fields() {
194 return array();
195 }
196
197 /**
198 * Can this column be sorted on? You can return either:
199 * + false for no (the default),
200 * + true for yes, or
201 * + an array of subnames to sort on. E.g. 'firstname', 'lastname'.
202 * If this method returns true, sort_expression must be implemented.
203 * @return mixed
204 */
205 public function is_sortable() {
206 return false;
207 }
208
209 /**
210 * Helper method for building sort clauses.
211 * @param boolean $reverse whether the normal direction should be reversed.
212 * @param string $normaldir 'ASC' or 'DESC'
213 * @return string 'ASC' or 'DESC'
214 */
215 protected function sortorder($reverse, $normaldir = 'ASC') {
216 switch ($normaldir) {
217 case 'ASC':
218 if ($reverse) {
219 return ' DESC';
220 } else {
221 return ' ASC';
222 }
223 case 'DESC':
224 if ($reverse) {
225 return ' ASC';
226 } else {
227 return ' DESC';
228 }
229 default:
230 throw new coding_exception('$normaldir should be ASC or DESC.');
231 }
232 }
233
234 /**
235 * @param $reverse Whether to sort in the reverse of the default sort order.
236 * @param $subsort if is_sortable returns an array of subnames, then this will be
237 * one of those. Otherwise will be empty.
238 * @return string some SQL to go in the order by clause.
239 */
240 public function sort_expression($reverse, $subsort) {
241 throw new coding_exception('A subclass of question_bank_column_base must implement sort_expression if is_sortable returns true.');
242 }
243}
244
245/**
246 * A column with a checkbox for each question with name q{questionid}.
247 */
248class question_bank_checkbox_column extends question_bank_column_base {
249 protected $strselect;
250
251 public function __construct() {
252 parent::__construct();
253 $this->strselect = get_string('select', 'quiz');
254 }
255
256 protected function get_css_classes($question, $rowclasses) {
257 return 'checkbox';
258 }
259
260 protected function display_content($question, $rowclasses) {
261 echo '<input title="' . $this->strselect . '" type="checkbox" name="q' .
262 $question->id . '" id="checkq' . $question->id . '" value="1" />';
263 }
264
265 public function get_required_fields() {
266 return array('q.id');
267 }
268}
269
270/**
271 * A column type for the name of the question type.
272 */
273class question_bank_question_type_column extends question_bank_column_base {
274 protected function get_css_classes($question, $rowclasses) {
275 return 'qtype';
276 }
277
278 protected function display_content($question, $rowclasses) {
279 echo print_question_icon($question);
280 }
281
282 public function get_required_fields() {
283 return array('q.qtype');
284 }
285
286 public function is_sortable() {
287 return true;
288 }
289
290 public function sort_expression($reverse, $subsort) {
291 return 'q.qtype' . $this->sortorder($reverse);
292 }
293}
294
295/**
296 * A column type for the name of the question name.
297 */
298class question_bank_question_name_column extends question_bank_column_base {
299 protected function get_css_classes($question, $rowclasses) {
300 return 'questionname';
301 }
302
303 protected function display_content($question, $rowclasses) {
304 echo format_string($question->name);
305 }
306
307 public function get_required_fields() {
308 return array('q.name');
309 }
310
311 public function is_sortable() {
312 return true;
313 }
314
315 public function sort_expression($reverse, $subsort) {
316 return 'q.name' . $this->sortorder($reverse);
317 }
318}
319
320/**
321 * A column type for the name of the question creator.
322 */
323class question_bank_creator_name_column extends question_bank_column_base {
324 protected function get_css_classes($question, $rowclasses) {
325 return 'creatorname';
326 }
327
328 protected function display_content($question, $rowclasses) {
329 if (!empty($question->creatorfirstname) && !empty($question->creatorlastname)) {
330 $u = new stdClass;
331 $u->firstname = $question->creatorfirstname;
332 $u->lastname = $question->creatorlastname;
333 echo fullname($u);
334 }
335 }
336
337 public function get_extra_joins() {
338 return array('uc' => 'LEFT JOIN {user} uc ON uc.id = q.createdby');
339 }
340
341 public function get_required_fields() {
342 return array('uc.firstname AS creatorfirstname', 'uc.lastname AS creatorlastname');
343 }
344
345 public function is_sortable() {
346 return array('firstname', 'lastname');
347 }
348
349 public function sort_expression($reverse, $subsort) {
350 if (in_array($subsort, $this->is_sortable())) {
351 return 'uc.' . $subsort . $this->sortorder($reverse);
352 } else {
353 throw new coding_exception('Unexpected $subsort type.');
354 }
355 }
356}
357
358/**
359 * A column type for the name of the question last modifier.
360 */
361class question_bank_modifier_name_column extends question_bank_column_base {
362 protected function get_css_classes($question, $rowclasses) {
363 return 'modifiername';
364 }
365
366 protected function display_content($question, $rowclasses) {
367 if (!empty($question->modifierfirstname) && !empty($question->modifierlastname)) {
368 $u = new stdClass;
369 $u->firstname = $question->modifierfirstname;
370 $u->lastname = $question->modifierlastname;
371 echo fullname($u);
372 }
373 }
374
375 public function get_extra_joins() {
376 return array('um' => 'LEFT JOIN {user} um ON um.id = q.modifiedby');
377 }
378
379 public function get_required_fields() {
380 return array('um.firstname AS modifierfirstname', 'um.lastname AS modifierlastname');
381 }
382
383 public function is_sortable() {
384 return array('firstname', 'lastname');
385 }
386
387 public function sort_expression($reverse, $subsort) {
388 if (in_array($subsort, $this->is_sortable())) {
389 return 'um.' . $subsort . $this->sortorder($reverse);
390 } else {
391 throw new coding_exception('Unexpected $subsort type.');
392 }
393 }
394}
395
396/**
397 * A base class for actions that are an icon that lets you manipulate the question in some way.
398 */
399abstract class question_bank_action_column_base extends question_bank_column_base {
400 protected function print_icon($icon, $title, $url) {
401 global $CFG;
402 echo '<a title="' . $title . '" href="' . $url . '">
403 <img src="' . $CFG->pixpath . '/t/' . $icon . '" class="iconsmall" alt="' . $title . '" /></a>';
404 }
405
406 public function get_required_fields() {
407 return array('q.id');
408 }
409}
410
411class question_bank_edit_action_column extends question_bank_action_column_base {
412 protected $stredit;
413 protected $strview;
414 protected $questionurl;
415
416 public function __construct($questionurl) {
417 parent::__construct();
418 $this->questionurl = $questionurl;
419 $this->stredit = get_string('edit');
420 $this->strview = get_string('view');
421 }
422
423 protected function get_css_classes($question, $rowclasses) {
424 return 'editaction';
425 }
426
427 protected function display_content($question, $rowclasses) {
428 if (question_has_capability_on($question, 'edit') ||
429 question_has_capability_on($question, 'move')) {
430 $this->print_icon('edit', $this->stredit, $this->questionurl->out(false, array('id' => $question->id)));
431 } else {
432 $this->print_icon('info', $this->strview, $this->questionurl->out(false, array('id' => $question->id)));
433 }
434 }
435}
436
437class question_bank_preview_action_column extends question_bank_action_column_base {
438 protected $strpreview;
439 protected $quizorcourseid;
440
441 public function __construct($quizorcourseid) {
442 parent::__construct();
443 $this->quizorcourseid = $quizorcourseid;
444 $this->stredit = get_string('preview');
445 }
446
447 protected function get_css_classes($question, $rowclasses) {
448 return 'previewaction';
449 }
450
451 protected function display_content($question, $rowclasses) {
452 if (question_has_capability_on($question, 'use')) {
453 link_to_popup_window('/question/preview.php?id=' . $question->id .
454 $this->quizorcourseid, 'questionpreview',
455 ' <img src="' . $CFG->pixpath . '/t/preview.gif" class="iconsmall" alt="' . $this->strpreview . '" />',
456 0, 0, $this->strpreview, QUESTION_PREVIEW_POPUP_OPTIONS);
457 }
458 }
459
460 public function get_required_fields() {
461 return array('q.id');
462 }
463}
464
465class question_bank_move_action_column extends question_bank_action_column_base {
466 protected $strmove;
467 protected $questionurl;
468
469 public function __construct($questionurl) {
470 parent::__construct();
471 $this->questionurl = $questionurl;
472 $this->strmove = get_string('move');
473 }
474
475 protected function get_css_classes($question, $rowclasses) {
476 return 'editaction';
477 }
478
479 protected function display_content($question, $rowclasses) {
480 if (question_has_capability_on($question, 'move')) {
481 $this->print_icon('move', $this->strmove, $this->questionurl->out(false, array('id' => $question->id)));
482 }
483 }
484}
485
486/**
487 * action to delete (or hide) a question, or restore a previously hidden question.
488 */
489class question_bank_delete_action_column extends question_bank_action_column_base {
490 protected $strdelete;
491 protected $strrestore;
492 protected $questionurl;
493
494 public function __construct($questionurl) {
495 parent::__construct();
496 $this->questionurl = $questionurl;
497 $this->strdelete = get_string('delete');
498 $this->strrestore = get_string('restore');
499 }
500
501 protected function get_css_classes($question, $rowclasses) {
502 return 'deleteaction';
503 }
504
505 protected function display_content($question, $rowclasses) {
506 if (question_has_capability_on($question, 'edit')) {
507 if ($question->hidden) {
508 $this->print_icon('restore', $this->strrestore, $pageurl->out(false, array('unhide' => $question->id)));
509 } else {
510 $this->print_icon('restore', $this->strrestore,
511 $pageurl->out(false, array('deleteselected' => $question->id, 'q' . $question->id => 1)));
512 }
513 }
514 }
515
516 public function get_required_fields() {
517 return array('q.id', 'q.hidden');
518 }
519}
520
4fbfd971 521/**
f4b879dd 522 * This class prints a view of the question bank, including
523 * + Some controls to allow users to to select what is displayed.
524 * + A list of questions as a table.
525 * + Further controls to do things with the questions.
526 *
527 * This class gives a basic view, and provides plenty of hooks where subclasses
528 * can override parts of the display.
529 *
530 * The list of questions presented as a table is generated by creating a list of
531 * question_bank_column objects, one for each 'column' to be displayed. These
532 * manage
533 * + outputting the contents of that column, given a $question object, but also
534 * + generating the right fragments of SQL to ensure the necessary data is present,
535 * and sorted in the right order.
536 * + outputting table headers.
4fbfd971 537 */
f4b879dd 538class question_bank_view {
539 public function __construct() {
516cf3eb 540
f4b879dd 541 }
516cf3eb 542
f4b879dd 543 /**
544 * Shows the question bank editing interface.
545 *
546 * The function also processes a number of actions:
547 *
548 * Actions affecting the question pool:
549 * move Moves a question to a different category
550 * deleteselected Deletes the selected questions from the category
551 * Other actions:
552 * category Chooses the category
553 * displayoptions Sets display options
f4b879dd 554 */
555 function display($tabname, $contexts, $pageurl, $cm, $page, $perpage, $sortorder,
556 $sortorderdecoded, $cat, $recurse, $showhidden, $showquestiontext){
557 global $COURSE, $DB;
558
559 if (optional_param('deleteselected', false, PARAM_BOOL)){ // teacher still has to confirm
560 // make a list of all the questions that are selected
561 $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted.
562 $questionlist = ''; // comma separated list of ids of questions to be deleted
563 $questionnames = ''; // string with names of questions separated by <br /> with
564 // an asterix in front of those that are in use
565 $inuse = false; // set to true if at least one of the questions is in use
566 foreach ($rawquestions as $key => $value) { // Parse input for question ids
567 if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
568 $key = $matches[1];
569 $questionlist .= $key.',';
570 question_require_capability_on($key, 'edit');
571 if ($DB->record_exists('quiz_question_instances', array('question' => $key))) {
572 $questionnames .= '* ';
573 $inuse = true;
574 }
5b5e5ab2 575 $questionnames .= $DB->get_field('question', 'name', array('id' => $key)) . '<br />';
f4b879dd 576 }
577 }
578 if (!$questionlist) { // no questions were selected
579 redirect($pageurl->out());
580 }
581 $questionlist = rtrim($questionlist, ',');
dcd51df2 582
f4b879dd 583 // Add an explanation about questions in use
584 if ($inuse) {
585 $questionnames .= '<br />'.get_string('questionsinuse', 'quiz');
586 }
587 notice_yesno(get_string("deletequestionscheck", "quiz", $questionnames),
588 $pageurl->out_action(array('deleteselected'=>$questionlist, 'confirm'=>md5($questionlist))),
589 $pageurl->out_action());
590
5b5e5ab2 591 return;
f4b879dd 592 }
516cf3eb 593
5b5e5ab2 594 // Category selection form
f4b879dd 595 print_box_start('generalbox questionbank');
596 print_heading(get_string('questionbank', 'question'), '', 2);
5b5e5ab2 597
598 $this->display_category_form($contexts->having_one_edit_tab_cap($tabname),
599 $pageurl, $cat, $recurse, $showhidden, $showquestiontext);
624cbc9c 600
f4b879dd 601 // continues with list of questions
602 $this->display_question_list($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, isset($cm) ? $cm : null,
603 $recurse, $page, $perpage, $showhidden, $sortorder, $sortorderdecoded, $showquestiontext,
604 $contexts->having_cap('moodle/question:add'));
516cf3eb 605
f4b879dd 606 print_box_end();
516cf3eb 607 }
608
f4b879dd 609 /**
610 * prints a form to choose categories
611 */
5b5e5ab2 612 function display_category_form($contexts, $pageurl, $current, $recurse=1,
613 $showhidden=false, $showquestiontext=false) {
f4b879dd 614 global $CFG;
516cf3eb 615
f4b879dd 616 /// Get all the existing categories now
617 $catmenu = question_category_options($contexts, false, 0, true);
516cf3eb 618
f4b879dd 619 $strcategory = get_string('category', 'quiz');
620 $strshow = get_string('show', 'quiz');
621 $streditcats = get_string('editcategories', 'quiz');
622
5b5e5ab2 623 popup_form('edit.php?'.$pageurl->get_query_string().'&amp;category=',
624 $catmenu, 'catmenu', $current, '', '', '', false, 'self',
625 "<strong>$strcategory</strong>");
f4b879dd 626
627 echo '<form method="get" action="edit.php" id="displayoptions">';
628 echo "<fieldset class='invisiblefieldset'>";
629 echo $pageurl->hidden_params_out(array('recurse', 'showhidden', 'showquestiontext'));
630 $this->display_category_form_checkbox('recurse', $recurse);
631 $this->display_category_form_checkbox('showhidden', $showhidden);
632 $this->display_category_form_checkbox('showquestiontext', $showquestiontext);
633 echo '<noscript><div class="centerpara"><input type="submit" value="'. get_string('go') .'" />';
634 echo '</div></noscript></fieldset></form>';
516cf3eb 635 }
636
f4b879dd 637 /**
638 * Private funciton to help the preceeding function.
639 */
640 protected function display_category_form_checkbox($name, $checked) {
641 echo '<div><input type="hidden" id="' . $name . '_off" name="' . $name . '" value="0" />';
642 echo '<input type="checkbox" id="' . $name . '_on" name="' . $name . '" value="1"';
643 if ($checked) {
644 echo ' checked="checked"';
516cf3eb 645 }
f4b879dd 646 echo ' onchange="getElementById(\'displayoptions\').submit(); return true;" />';
647 echo '<label for="' . $name . '_on">';
648 print_string($name, 'quiz');
649 echo "</label></div>\n";
516cf3eb 650 }
651
f4b879dd 652 /**
653 * Prints the table of questions in a category with interactions
654 *
655 * @param object $course The course object
656 * @param int $categoryid The id of the question category to be displayed
657 * @param int $cm The course module record if we are in the context of a particular module, 0 otherwise
658 * @param int $recurse This is 1 if subcategories should be included, 0 otherwise
659 * @param int $page The number of the page to be displayed
660 * @param int $perpage Number of questions to show per page
661 * @param boolean $showhidden True if also hidden questions should be displayed
662 * @param boolean $showquestiontext whether the text of each question should be shown in the list
663 */
5b5e5ab2 664 function display_question_list($contexts, $pageurl, $categoryandcontext,
665 $cm = null, $recurse=1, $page=0, $perpage=100, $showhidden=false,
666 $sortorder='typename', $sortorderdecoded='qtype, name ASC',
f4b879dd 667 $showquestiontext = false, $addcontexts = array()) {
668 global $USER, $CFG, $THEME, $COURSE, $DB;
669
670 list($categoryid, $contextid)= explode(',', $categoryandcontext);
671
672 $qtypemenu = question_type_menu();
673
674 $strcategory = get_string("category", "quiz");
675 $strquestion = get_string("question", "quiz");
676 $straddquestions = get_string("addquestions", "quiz");
677 $strimportquestions = get_string("importquestions", "quiz");
678 $strexportquestions = get_string("exportquestions", "quiz");
5b5e5ab2 679 $strnoquestions = get_string("noquestions", "quiz");
f4b879dd 680 $strselect = get_string("select", "quiz");
681 $strselectall = get_string("selectall", "quiz");
682 $strselectnone = get_string("selectnone", "quiz");
683 $strcreatenewquestion = get_string("createnewquestion", "quiz");
5b5e5ab2 684 $strquestion = get_string("question", "quiz");
f4b879dd 685 $strdelete = get_string("delete");
686 $stredit = get_string("edit");
687 $strmove = get_string('moveqtoanothercontext', 'question');
688 $strview = get_string("view");
689 $straction = get_string("action");
690 $strrestore = get_string('restore');
691
692 $strtype = get_string("type", "quiz");
693 $strcreatemultiple = get_string("createmultiple", "quiz");
694 $strpreview = get_string("preview","quiz");
695
696 if (!$categoryid) {
697 echo "<p style=\"text-align:center;\"><b>";
698 print_string("selectcategoryabove", "quiz");
699 echo "</b></p>";
700 return;
624cbc9c 701 }
f4b879dd 702
5b5e5ab2 703 if (!$category = $DB->get_record('question_categories',
704 array('id' => $categoryid, 'contextid' => $contextid))) {
f4b879dd 705 notify('Category not found!');
706 return;
624cbc9c 707 }
5b5e5ab2 708
f4b879dd 709 $catcontext = get_context_instance_by_id($contextid);
710 $canadd = has_capability('moodle/question:add', $catcontext);
711 //check for capabilities on all questions in category, will also apply to sub cats.
712 $caneditall =has_capability('moodle/question:editall', $catcontext);
713 $canuseall =has_capability('moodle/question:useall', $catcontext);
714 $canmoveall =has_capability('moodle/question:moveall', $catcontext);
715
716 if ($cm AND $cm->modname == 'quiz') {
717 $quizid = $cm->instance;
718 } else {
719 $quizid = 0;
624cbc9c 720 }
5b5e5ab2 721
722 // Create the url of the new question page to forward to.
f4b879dd 723 $returnurl = $pageurl->out();
724 $questionurl = new moodle_url("$CFG->wwwroot/question/question.php",
725 array('returnurl' => $returnurl));
726 if ($cm!==null){
727 $questionurl->param('cmid', $cm->id);
728 } else {
729 $questionurl->param('courseid', $COURSE->id);
624cbc9c 730 }
f4b879dd 731 $questionmoveurl = new moodle_url("$CFG->wwwroot/question/contextmoveq.php",
732 array('returnurl' => $returnurl));
733 if ($cm!==null){
734 $questionmoveurl->param('cmid', $cm->id);
735 } else {
736 $questionmoveurl->param('courseid', $COURSE->id);
516cf3eb 737 }
5b5e5ab2 738
f4b879dd 739 echo '<div class="boxaligncenter">';
740 $formatoptions = new stdClass;
741 $formatoptions->noclean = true;
742 echo format_text($category->info, FORMAT_MOODLE, $formatoptions, $COURSE->id);
5b5e5ab2 743 echo "</div>\n";
f4b879dd 744
5b5e5ab2 745 echo '<div class="createnewquestion">';
f4b879dd 746 if ($canadd) {
5b5e5ab2 747 popup_form($questionurl->out(false, array('category' => $category->id)).
748 '&amp;qtype=', $qtypemenu, "addquestion", "", "choose", "",
749 "", false, "self", "<strong>$strcreatenewquestion</strong>");
f4b879dd 750 helpbutton("questiontypes", $strcreatenewquestion, "quiz");
5b5e5ab2 751 } else {
f4b879dd 752 print_string('nopermissionadd', 'question');
271e6dec 753 }
f4b879dd 754 echo '</div>';
755
756 $categorylist = ($recurse) ? question_categorylist($category->id) : $category->id;
5b5e5ab2 757 $categorylist_array = explode(',', $categorylist);
f4b879dd 758
f4b879dd 759 $showhidden = $showhidden ? '' : " AND hidden = '0'";
5b5e5ab2 760
f4b879dd 761 list($usql, $params) = $DB->get_in_or_equal($categorylist_array);
5b5e5ab2 762 if (!$totalnumber = $DB->count_records_select('question',
763 "category $usql AND parent = '0' $showhidden", $params)) {
764 echo '<div class="categoryquestionscontainer noquestionsincategory">';
765 print_string('noquestions', 'quiz');
766 echo '</div>';
f4b879dd 767 return;
271e6dec 768 }
769
5b5e5ab2 770 if (!$questions = $DB->get_records_select('question',
771 "category $usql AND parent = '0' $showhidden", $params, $sortorderdecoded,
772 '*', $page*$perpage, $perpage)) {
773
f4b879dd 774 // There are no questions on the requested page.
775 $page = 0;
5b5e5ab2 776 if (!$questions = $DB->get_records_select('question',
777 "category $usql AND parent = '0' $showhidden", $params, $sortorderdecoded,
778 '*', 0, $perpage)) {
f4b879dd 779 // There are no questions at all
5b5e5ab2 780 echo '<div class="categoryquestionscontainer noquestionsincategory">';
781 print_string('noquestions', 'quiz');
782 echo '</div>';
f4b879dd 783 return;
516cf3eb 784 }
785 }
516cf3eb 786
5b5e5ab2 787 echo '<div class="categorysortopotionscontainer">';
f4b879dd 788 $this->display_question_sort_options($pageurl, $sortorder);
5b5e5ab2 789 echo '</div>';
790
791 echo '<div class="categorypagingbarcontainer">';
792 print_paging_bar($totalnumber, $page, $perpage, $pageurl, 'qpage');
793 echo '</div>';
f4b879dd 794
795 echo '<form method="post" action="edit.php">';
796 echo '<fieldset class="invisiblefieldset" style="display: block;">';
797 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
798 echo $pageurl->hidden_params_out();
5b5e5ab2 799 echo '<div class="categoryquestionscontainer">';
800 echo '<table id="categoryquestions" style="width: 100%"><colgroup><col id="qaction"></col><col id="qname"></col><col id="qextraactions"></col></colgroup><tr>';
f4b879dd 801 echo "<th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">$straction</th>";
802
5b5e5ab2 803 echo "<th style=\"white-space:nowrap; text-align: left;\" class=\"header\" scope=\"col\">$strquestion</th>";
804 echo "<th style=\"white-space:nowrap; text-align: left;\" class=\"header\" scope=\"col\"></th>";
516cf3eb 805 echo "</tr>\n";
f4b879dd 806 foreach ($questions as $question) {
807 $nameclass = '';
808 $textclass = '';
809 if ($question->hidden) {
810 $nameclass = 'dimmed_text';
811 $textclass = 'dimmed_text';
812 }
813 if ($showquestiontext) {
814 $nameclass .= ' header';
815 }
816 if ($nameclass) {
817 $nameclass = 'class="' . $nameclass . '"';
818 }
819 if ($textclass) {
820 $textclass = 'class="' . $textclass . '"';
821 }
624cbc9c 822
f4b879dd 823 echo "<tr>\n<td style=\"white-space:nowrap;\" $nameclass>\n";
516cf3eb 824
f4b879dd 825 $canuseq = question_has_capability_on($question, 'use', $question->category);
826 if (function_exists('module_specific_actions')) {
827 echo module_specific_actions($pageurl, $question->id, $cm->id, $canuseq);
828 }
95947ac9 829
5b5e5ab2 830 if ($caneditall || $canmoveall || $canuseall){
831 echo "<input title=\"$strselect\" type=\"checkbox\" name=\"q$question->id\" id=\"checkq$question->id\" value=\"1\" />";
832 }
833 echo "</td>\n";
834
835 echo "<td $nameclass><div>";
836 print_question_icon($question);
837 echo format_string($question->name);
838 echo "</div></td>\n";
839
840 echo "<td>\n";
841
842 // edit, hide, delete question, using question capabilities, not quiz capabilities
843 if (question_has_capability_on($question, 'edit', $question->category) ||
844 question_has_capability_on($question, 'move', $question->category)) {
845 echo "<a title=\"$stredit\" href=\"".$questionurl->out(false, array('id'=>$question->id))."\">
846 <img src=\"$CFG->pixpath/t/edit.gif\" alt=\"$stredit\" /></a>";
847 } elseif (question_has_capability_on($question, 'view', $question->category)) {
848 echo "<a title=\"$strview\" href=\"".$questionurl->out(false, array('id'=>$question->id))."\">
849 <img src=\"$CFG->pixpath/i/info.gif\" alt=\"$strview\" /></a>";
850 }
851
f4b879dd 852 // preview
853 if ($canuseq) {
854 $quizorcourseid = $quizid?('&amp;quizid=' . $quizid):('&amp;courseid=' .$COURSE->id);
5b5e5ab2 855 link_to_popup_window('/question/preview.php?id=' . $question->id .
856 $quizorcourseid, 'questionpreview',
857 " <img src=\"$CFG->pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" />",
f4b879dd 858 0, 0, $strpreview, QUESTION_PREVIEW_POPUP_OPTIONS);
859 }
f4b879dd 860
861 if (question_has_capability_on($question, 'move', $question->category) && question_has_capability_on($question, 'view', $question->category)) {
5b5e5ab2 862 echo "<a title=\"$strmove\" href=\"".$questionurl->out(false, array('id'=>$question->id, 'movecontext'=>1))."\">
863 <img src=\"$CFG->pixpath/t/move.gif\" alt=\"$strmove\" /></a>";
f4b879dd 864 }
516cf3eb 865
f4b879dd 866 if (question_has_capability_on($question, 'edit', $question->category)) {
867 // hide-feature
868 if($question->hidden) {
5b5e5ab2 869 echo "<a title=\"$strrestore\" href=\"edit.php?".$pageurl->get_query_string()."&amp;unhide=$question->id&amp;sesskey=".sesskey()."\">
870 <img src=\"$CFG->pixpath/t/restore.gif\" alt=\"$strrestore\" /></a>";
f4b879dd 871 } else {
5b5e5ab2 872 echo "<a title=\"$strdelete\" href=\"edit.php?".$pageurl->get_query_string()."&amp;deleteselected=$question->id&amp;q$question->id=1\">
873 <img src=\"$CFG->pixpath/t/delete.gif\" alt=\"$strdelete\" /></a>";
f4b879dd 874 }
875 }
f4b879dd 876 echo "</td>\n";
877
f4b879dd 878 echo "</tr>\n";
879 if($showquestiontext){
880 echo '<tr><td colspan="3" ' . $textclass . '>';
881 $formatoptions = new stdClass;
882 $formatoptions->noclean = true;
883 $formatoptions->para = false;
884 echo format_text($question->questiontext, $question->questiontextformat,
885 $formatoptions, $COURSE->id);
886 echo "</td></tr>\n";
887 }
271e6dec 888 }
5b5e5ab2 889 echo "</table></div>\n";
86909ce0 890
5b5e5ab2 891 echo '<div class="categorypagingbarcontainer pagingbottom">';
f4b879dd 892 $paging = print_paging_bar($totalnumber, $page, $perpage, $pageurl, 'qpage', false, true);
893 if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) {
894 if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) {
895 $showall = '<a href="edit.php?'.$pageurl->get_query_string(array('qperpage'=>1000)).'">'.get_string('showall', 'moodle', $totalnumber).'</a>';
896 } else {
897 $showall = '<a href="edit.php?'.$pageurl->get_query_string(array('qperpage'=>DEFAULT_QUESTIONS_PER_PAGE)).'">'.get_string('showperpage', 'moodle', DEFAULT_QUESTIONS_PER_PAGE).'</a>';
898 }
899 if ($paging) {
900 $paging = substr($paging, 0, strrpos($paging, '</div>'));
901 $paging .= "<br />$showall</div>";
902 } else {
903 $paging = "<div class='paging'>$showall</div>";
904 }
86909ce0 905 }
f4b879dd 906 echo $paging;
5b5e5ab2 907 echo '</div>';
f4b879dd 908
5b5e5ab2 909 echo '<div class="categoryselectallcontainer">';
f4b879dd 910 if ($caneditall || $canmoveall || $canuseall){
911 echo '<a href="javascript:select_all_in(\'TABLE\',null,\'categoryquestions\');">'.$strselectall.'</a> /'.
912 ' <a href="javascript:deselect_all_in(\'TABLE\',null,\'categoryquestions\');">'.$strselectnone.'</a>';
913 echo '<br />';
5b5e5ab2 914 }
915 echo "</div>\n";
916
917 echo '<div class="modulespecificbuttonscontainer">';
918 if ($caneditall || $canmoveall || $canuseall){
f4b879dd 919 echo '<strong>&nbsp;'.get_string('withselected', 'quiz').':</strong><br />';
920
921 if (function_exists('module_specific_buttons')) {
922 echo module_specific_buttons($cm->id);
923 }
5b5e5ab2 924
f4b879dd 925 // print delete and move selected question
926 if ($caneditall) {
5b5e5ab2 927 echo '<input type="submit" name="deleteselected" value="' . $strdelete . "\" />\n";
f4b879dd 928 }
5b5e5ab2 929
f4b879dd 930 if ($canmoveall && count($addcontexts)) {
931 echo '<input type="submit" name="move" value="'.get_string('moveto', 'quiz')."\" />\n";
932 question_category_select_menu($addcontexts, false, 0, "$category->id,$category->contextid");
933 }
934
935 if (function_exists('module_specific_controls') && $canuseall) {
5b5e5ab2 936 $modulespecific = module_specific_controls($totalnumber, $recurse, $category, $cm->id);
937 if(!empty($modulespecific)){
938 echo "<hr />$modulespecific";
939 }
271e6dec 940 }
941 }
5b5e5ab2 942 echo "</div>\n";
943
f4b879dd 944 echo '</fieldset>';
945 echo "</form>\n";
946 }
947
948 function display_question_sort_options($pageurl, $sortorder){
949 global $USER;
950 //sort options
951 $html = "<div class=\"mdl-align questionsortoptions\">";
952 // POST method should only be used for parameters that change data
953 // or if POST method has to be used, the user must be redirected immediately to
954 // non-POSTed page to not break the back button
955 $html .= '<form method="get" action="edit.php">';
956 $html .= '<fieldset class="invisiblefieldset" style="display: block;">';
957 $html .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
958 $html .= $pageurl->hidden_params_out(array('qsortorder'));
959 //choose_from_menu concatenates the form name with
960 //"menu" so the label is for menuqsortorder
961 $sortoptions = array('alpha' => get_string("qname", "quiz"),
962 'typealpha' => get_string("qtypename", "quiz"),
963 'age' => get_string("age", "quiz"));
964 $a = choose_from_menu($sortoptions, 'qsortorder', $sortorder, false, 'this.form.submit();', '0', true);
965 $html .= '<label for="menuqsortorder">'.get_string('sortquestionsbyx', 'quiz', $a).'</label>';
966 $html .= '<noscript><div><input type="submit" value="'.get_string("sortsubmit", "quiz").'" /></div></noscript>';
967 $html .= '</fieldset>';
968 $html .= "</form>\n";
969 $html .= "</div>\n";
970 echo $html;
971 }
972
973 function process_actions($pageurl, $cm){
974 global $CFG, $COURSE, $DB;
975 /// Now, check for commands on this page and modify variables as necessary
976 if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) { /// Move selected questions to new category
977 $category = required_param('category', PARAM_SEQUENCE);
978 list($tocategoryid, $contextid) = explode(',', $category);
979 if (! $tocategory = $DB->get_record('question_categories', array('id' => $tocategoryid, 'contextid' => $contextid))) {
980 print_error('cannotfindcate', 'question');
271e6dec 981 }
f4b879dd 982 $tocontext = get_context_instance_by_id($contextid);
983 require_capability('moodle/question:add', $tocontext);
984 $rawdata = (array) data_submitted();
985 $questionids = array();
986 foreach ($rawdata as $key => $value) { // Parse input for question ids
987 if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
988 $key = $matches[1];
989 $questionids[] = $key;
271e6dec 990 }
991 }
f4b879dd 992 if ($questionids){
993 list($usql, $params) = $DB->get_in_or_equal($questionids);
994 $sql = "SELECT q.*, c.contextid FROM {question} q, {question_categories} c WHERE q.id $usql AND c.id = q.category";
995 if (!$questions = $DB->get_records_sql($sql, $params)){
996 print_error('questiondoesnotexist', 'question', $pageurl->out());
271e6dec 997 }
f4b879dd 998 $checkforfiles = false;
999 foreach ($questions as $question){
1000 //check capabilities
1001 question_require_capability_on($question, 'move');
1002 $fromcontext = get_context_instance_by_id($question->contextid);
1003 if (get_filesdir_from_context($fromcontext) != get_filesdir_from_context($tocontext)){
1004 $checkforfiles = true;
1005 }
1006 }
1007 $returnurl = $pageurl->out(false, array('category'=>"$tocategoryid,$contextid"));
1008 if (!$checkforfiles){
1009 if (!question_move_questions_to_category(implode(',', $questionids), $tocategory->id)) {
1010 print_error('errormovingquestions', 'question', $returnurl, $questionids);
1011 }
1012 redirect($returnurl);
271e6dec 1013 } else {
f4b879dd 1014 $movecontexturl = new moodle_url($CFG->wwwroot.'/question/contextmoveq.php',
1015 array('returnurl' => $returnurl,
1016 'ids'=>$questionidlist,
1017 'tocatid'=> $tocategoryid));
1018 if ($cm){
1019 $movecontexturl->param('cmid', $cm->id);
1020 } else {
1021 $movecontexturl->param('courseid', $COURSE->id);
1022 }
1023 redirect($movecontexturl->out());
86909ce0 1024 }
1025 }
1026 }
86909ce0 1027
f4b879dd 1028 if (optional_param('deleteselected', false, PARAM_BOOL)) { // delete selected questions from the category
1029 if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) { // teacher has already confirmed the action
1030 $deleteselected = required_param('deleteselected');
1031 if ($confirm == md5($deleteselected)) {
1032 if ($questionlist = explode(',', $deleteselected)) {
1033 // for each question either hide it if it is in use or delete it
1034 foreach ($questionlist as $questionid) {
1035 question_require_capability_on($questionid, 'edit');
1036 if ($DB->record_exists('quiz_question_instances', array('question' => $questionid))) {
1037 if (!$DB->set_field('question', 'hidden', 1, array('id' => $questionid))) {
1038 question_require_capability_on($questionid, 'edit');
1039 print_error('cannothidequestion', 'question');
1040 }
1041 } else {
1042 delete_question($questionid);
86909ce0 1043 }
86909ce0 1044 }
1045 }
f4b879dd 1046 redirect($pageurl->out());
1047 } else {
1048 print_error('invalidconfirm', 'question');
86909ce0 1049 }
86909ce0 1050 }
86909ce0 1051 }
8e77884c 1052
f4b879dd 1053 // Unhide a question
1054 if(($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) {
1055 question_require_capability_on($unhide, 'edit');
1056 if(!$DB->set_field('question', 'hidden', 0, array('id', $unhide))) {
1057 print_error('cannotunhidequestion', 'question');
271e6dec 1058 }
271e6dec 1059 redirect($pageurl->out());
1060 }
271e6dec 1061 }
86909ce0 1062}
f4b879dd 1063
b72ff476 1064/**
1065 * Common setup for all pages for editing questions.
271e6dec 1066 * @param string $edittab code for this edit tab
b72ff476 1067 * @param boolean $requirecmid require cmid? default false
1068 * @param boolean $requirecourseid require courseid, if cmid is not given? default true
271e6dec 1069 * @return array $thispageurl, $contexts, $cmid, $cm, $module, $pagevars
b72ff476 1070 */
271e6dec 1071function question_edit_setup($edittab, $requirecmid = false, $requirecourseid = true){
f34488b2 1072 global $COURSE, $QUESTION_EDITTABCAPS, $DB;
271e6dec 1073
1074 //$thispageurl is used to construct urls for all question edit pages we link to from this page. It contains an array
36e298bc 1075 //of parameters that are passed from page to page.
b72ff476 1076 $thispageurl = new moodle_url();
b4a2e413 1077 $thispageurl->remove_params(); // We are going to explicity add back everything important - this avoids unwanted params from being retained.
1078
b72ff476 1079 if ($requirecmid){
1080 $cmid =required_param('cmid', PARAM_INT);
1081 } else {
1082 $cmid = optional_param('cmid', 0, PARAM_INT);
1083 }
1084 if ($cmid){
271e6dec 1085 list($module, $cm) = get_module_from_cmid($cmid);
b72ff476 1086 $courseid = $cm->course;
1087 $thispageurl->params(compact('cmid'));
271e6dec 1088 require_login($courseid, false, $cm);
1089 $thiscontext = get_context_instance(CONTEXT_MODULE, $cmid);
b72ff476 1090 } else {
1091 $module = null;
1092 $cm = null;
1093 if ($requirecourseid){
1094 $courseid = required_param('courseid', PARAM_INT);
1095 } else {
1096 $courseid = optional_param('courseid', 0, PARAM_INT);
1097 }
1098 if ($courseid){
1099 $thispageurl->params(compact('courseid'));
271e6dec 1100 require_login($courseid, false);
1101 $thiscontext = get_context_instance(CONTEXT_COURSE, $courseid);
1102 } else {
1103 $thiscontext = null;
b72ff476 1104 }
1105 }
271e6dec 1106
1107 if ($thiscontext){
1108 $contexts = new question_edit_contexts($thiscontext);
1109 $contexts->require_one_edit_tab_cap($edittab);
1110
1111 } else {
1112 $contexts = null;
1113 }
1114
1115
1116
36e298bc 1117 $pagevars['qpage'] = optional_param('qpage', -1, PARAM_INT);
271e6dec 1118
7bc26c8f 1119 //pass 'cat' from page to page and when 'category' comes from a drop down menu
271e6dec 1120 //then we also reset the qpage so we go to page 1 of
7bc26c8f 1121 //a new cat.
271e6dec 1122 $pagevars['cat'] = optional_param('cat', 0, PARAM_SEQUENCE);// if empty will be set up later
1123 if ($category = optional_param('category', 0, PARAM_SEQUENCE)){
a18fbcfb 1124 if ($pagevars['cat'] != $category){ // is this a move to a new category?
1125 $pagevars['cat'] = $category;
1126 $pagevars['qpage'] = 0;
1127 }
7bc26c8f 1128 }
1129 if ($pagevars['cat']){
1130 $thispageurl->param('cat', $pagevars['cat']);
1131 }
b72ff476 1132 if ($pagevars['qpage'] > -1) {
1133 $thispageurl->param('qpage', $pagevars['qpage']);
1134 } else {
1135 $pagevars['qpage'] = 0;
1136 }
1137
36e298bc 1138 $pagevars['qperpage'] = optional_param('qperpage', -1, PARAM_INT);
b72ff476 1139 if ($pagevars['qperpage'] > -1) {
1140 $thispageurl->param('qperpage', $pagevars['qperpage']);
1141 } else {
1142 $pagevars['qperpage'] = DEFAULT_QUESTIONS_PER_PAGE;
1143 }
271e6dec 1144
36e298bc 1145 $sortoptions = array('alpha' => 'name, qtype ASC',
1146 'typealpha' => 'qtype, name ASC',
1147 'age' => 'id ASC');
1148
1149 if ($sortorder = optional_param('qsortorder', '', PARAM_ALPHA)) {
1150 $pagevars['qsortorderdecoded'] = $sortoptions[$sortorder];
1151 $pagevars['qsortorder'] = $sortorder;
1152 $thispageurl->param('qsortorder', $sortorder);
b72ff476 1153 } else {
7bc26c8f 1154 $pagevars['qsortorderdecoded'] = $sortoptions['typealpha'];
1155 $pagevars['qsortorder'] = 'typealpha';
271e6dec 1156 }
36e298bc 1157
271e6dec 1158 $defaultcategory = question_make_default_categories($contexts->all());
1159
1160 $contextlistarr = array();
1161 foreach ($contexts->having_one_edit_tab_cap($edittab) as $context){
1162 $contextlistarr[] = "'$context->id'";
1163 }
1164 $contextlist = join($contextlistarr, ' ,');
1165 if (!empty($pagevars['cat'])){
1166 $catparts = explode(',', $pagevars['cat']);
f34488b2 1167 if (!$catparts[0] || (FALSE !== array_search($catparts[1], $contextlistarr)) ||
1168 !$DB->count_records_select("question_categories", "id = ? AND contextid = ?", array($catparts[0], $catparts[1]))) {
5a2a5331 1169 print_error('invalidcategory', 'quiz');
271e6dec 1170 }
1171 } else {
1172 $category = $defaultcategory;
1173 $pagevars['cat'] = "$category->id,$category->contextid";
36e298bc 1174 }
1175
1176 if(($recurse = optional_param('recurse', -1, PARAM_BOOL)) != -1) {
1177 $pagevars['recurse'] = $recurse;
1178 $thispageurl->param('recurse', $recurse);
1179 } else {
1180 $pagevars['recurse'] = 1;
1181 }
271e6dec 1182
36e298bc 1183 if(($showhidden = optional_param('showhidden', -1, PARAM_BOOL)) != -1) {
1184 $pagevars['showhidden'] = $showhidden;
1185 $thispageurl->param('showhidden', $showhidden);
1186 } else {
1187 $pagevars['showhidden'] = 0;
1188 }
271e6dec 1189
36e298bc 1190 if(($showquestiontext = optional_param('showquestiontext', -1, PARAM_BOOL)) != -1) {
1191 $pagevars['showquestiontext'] = $showquestiontext;
1192 $thispageurl->param('showquestiontext', $showquestiontext);
1193 } else {
1194 $pagevars['showquestiontext'] = 0;
1195 }
271e6dec 1196
986effb6 1197 //category list page
1198 $pagevars['cpage'] = optional_param('cpage', 1, PARAM_INT);
1199 if ($pagevars['cpage'] != 1){
1200 $thispageurl->param('cpage', $pagevars['cpage']);
1201 }
36e298bc 1202
271e6dec 1203
1204 return array($thispageurl, $contexts, $cmid, $cm, $module, $pagevars);
1205}
1206class question_edit_contexts{
1207 var $allcontexts;
1208 /**
1209 * @param current context
1210 */
1211 function question_edit_contexts($thiscontext){
1212 $pcontextids = get_parent_contexts($thiscontext);
1213 $contexts = array($thiscontext);
1214 foreach ($pcontextids as $pcontextid){
1215 $contexts[] = get_context_instance_by_id($pcontextid);
1216 }
1217 $this->allcontexts = $contexts;
1218 }
1219 /**
1220 * @return array all parent contexts
1221 */
1222 function all(){
1223 return $this->allcontexts;
1224 }
1225 /**
1226 * @return object lowest context which must be either the module or course context
1227 */
1228 function lowest(){
1229 return $this->allcontexts[0];
1230 }
1231 /**
1232 * @param string $cap capability
1233 * @return array parent contexts having capability, zero based index
1234 */
1235 function having_cap($cap){
1236 $contextswithcap = array();
1237 foreach ($this->allcontexts as $context){
1238 if (has_capability($cap, $context)){
1239 $contextswithcap[] = $context;
1240 }
1241 }
1242 return $contextswithcap;
1243 }
1244 /**
1245 * @param array $caps capabilities
1246 * @return array parent contexts having at least one of $caps, zero based index
1247 */
1248 function having_one_cap($caps){
1249 $contextswithacap = array();
1250 foreach ($this->allcontexts as $context){
1251 foreach ($caps as $cap){
1252 if (has_capability($cap, $context)){
1253 $contextswithacap[] = $context;
1254 break; //done with caps loop
1255 }
1256 }
1257 }
1258 return $contextswithacap;
1259 }
1260 /**
1261 * @param string $tabname edit tab name
1262 * @return array parent contexts having at least one of $caps, zero based index
1263 */
1264 function having_one_edit_tab_cap($tabname){
1265 global $QUESTION_EDITTABCAPS;
1266 return $this->having_one_cap($QUESTION_EDITTABCAPS[$tabname]);
1267 }
1268 /**
1269 * Has at least one parent context got the cap $cap?
1270 *
1271 * @param string $cap capability
1272 * @return boolean
1273 */
1274 function have_cap($cap){
1275 return (count($this->having_cap($cap)));
1276 }
1277
1278 /**
1279 * Has at least one parent context got one of the caps $caps?
1280 *
1281 * @param string $cap capability
1282 * @return boolean
1283 */
1284 function have_one_cap($caps){
1285 foreach ($caps as $cap){
1286 if ($this->have_cap($cap)){
1287 return true;
1288 }
1289 }
1290 return false;
1291 }
1292 /**
1293 * Has at least one parent context got one of the caps for actions on $tabname
1294 *
1295 * @param string $tabname edit tab name
1296 * @return boolean
1297 */
1298 function have_one_edit_tab_cap($tabname){
1299 global $QUESTION_EDITTABCAPS;
1300 return $this->have_one_cap($QUESTION_EDITTABCAPS[$tabname]);
1301 }
1302 /**
1303 * Throw error if at least one parent context hasn't got the cap $cap
1304 *
1305 * @param string $cap capability
1306 */
1307 function require_cap($cap){
1308 if (!$this->have_cap($cap)){
1309 print_error('nopermissions', '', '', $cap);
1310 }
1311 }
1312 /**
1313 * Throw error if at least one parent context hasn't got one of the caps $caps
1314 *
1315 * @param array $cap capabilities
1316 */
1317 function require_one_cap($caps){
1318 if (!$this->have_one_cap($caps)){
1319 $capsstring = join($caps, ', ');
1320 print_error('nopermissions', '', '', $capsstring);
1321 }
1322 }
1323 /**
1324 * Throw error if at least one parent context hasn't got one of the caps $caps
1325 *
1326 * @param string $tabname edit tab name
1327 */
1328 function require_one_edit_tab_cap($tabname){
1329 if (!$this->have_one_edit_tab_cap($tabname)){
1330 print_error('nopermissions', '', '', 'access question edit tab '.$tabname);
1331 }
1332 }
1333}
1334
1335//capabilities for each page of edit tab.
1336//this determines which contexts' categories are available. At least one
1337//page is displayed if user has one of the capability on at least one context
1338$QUESTION_EDITTABCAPS = array(
a856c14e 1339 'editq' => array('moodle/question:add',
1340 'moodle/question:editmine',
1341 'moodle/question:editall',
1342 'moodle/question:viewmine',
1343 'moodle/question:viewall',
1344 'moodle/question:usemine',
1345 'moodle/question:useall',
1346 'moodle/question:movemine',
1347 'moodle/question:moveall'),
1348 'questions'=>array('moodle/question:add',
1349 'moodle/question:editmine',
1350 'moodle/question:editall',
1351 'moodle/question:viewmine',
1352 'moodle/question:viewall',
1353 'moodle/question:movemine',
1354 'moodle/question:moveall'),
1355 'categories'=>array('moodle/question:managecategory'),
1356 'import'=>array('moodle/question:add'),
1357 'export'=>array('moodle/question:viewall', 'moodle/question:viewmine'));
271e6dec 1358
1359/**
1360 * Make sure user is logged in as required in this context.
1361 */
1362function require_login_in_context($contextorid = null){
f34488b2 1363 global $DB;
271e6dec 1364 if (!is_object($contextorid)){
1365 $context = get_context_instance_by_id($contextorid);
1366 } else {
1367 $context = $contextorid;
1368 }
1369 if ($context && ($context->contextlevel == CONTEXT_COURSE)) {
1370 require_login($context->instanceid);
1371 } else if ($context && ($context->contextlevel == CONTEXT_MODULE)) {
f34488b2 1372 if ($cm = $DB->get_record('course_modules',array('id' =>$context->instanceid))) {
1373 if (!$course = $DB->get_record('course', array('id' => $cm->course))) {
0be2c858 1374 print_error('invalidcourseid');
271e6dec 1375 }
1376 require_course_login($course, true, $cm);
1377
1378 } else {
0be2c858 1379 print_error('invalidcoursemodule');
271e6dec 1380 }
1381 } else if ($context && ($context->contextlevel == CONTEXT_SYSTEM)) {
1382 if (!empty($CFG->forcelogin)) {
1383 require_login();
1384 }
1385
1386 } else {
1387 require_login();
1388 }
b72ff476 1389}
871ab9a3 1390?>