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