MDL-17942 fixed incorrect mysql setting name
[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
4fbfd971 130/**
f4b879dd 131 * This class prints a view of the question bank, including
132 * + Some controls to allow users to to select what is displayed.
133 * + A list of questions as a table.
134 * + Further controls to do things with the questions.
135 *
136 * This class gives a basic view, and provides plenty of hooks where subclasses
137 * can override parts of the display.
138 *
139 * The list of questions presented as a table is generated by creating a list of
140 * question_bank_column objects, one for each 'column' to be displayed. These
141 * manage
142 * + outputting the contents of that column, given a $question object, but also
143 * + generating the right fragments of SQL to ensure the necessary data is present,
144 * and sorted in the right order.
145 * + outputting table headers.
4fbfd971 146 */
f4b879dd 147class question_bank_view {
148 public function __construct() {
516cf3eb 149
f4b879dd 150 }
516cf3eb 151
f4b879dd 152 /**
153 * Shows the question bank editing interface.
154 *
155 * The function also processes a number of actions:
156 *
157 * Actions affecting the question pool:
158 * move Moves a question to a different category
159 * deleteselected Deletes the selected questions from the category
160 * Other actions:
161 * category Chooses the category
162 * displayoptions Sets display options
163 *
164 * @param moodle_url $pageurl object representing this pages url.
165 */
166 function display($tabname, $contexts, $pageurl, $cm, $page, $perpage, $sortorder,
167 $sortorderdecoded, $cat, $recurse, $showhidden, $showquestiontext){
168 global $COURSE, $DB;
169
170 if (optional_param('deleteselected', false, PARAM_BOOL)){ // teacher still has to confirm
171 // make a list of all the questions that are selected
172 $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted.
173 $questionlist = ''; // comma separated list of ids of questions to be deleted
174 $questionnames = ''; // string with names of questions separated by <br /> with
175 // an asterix in front of those that are in use
176 $inuse = false; // set to true if at least one of the questions is in use
177 foreach ($rawquestions as $key => $value) { // Parse input for question ids
178 if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
179 $key = $matches[1];
180 $questionlist .= $key.',';
181 question_require_capability_on($key, 'edit');
182 if ($DB->record_exists('quiz_question_instances', array('question' => $key))) {
183 $questionnames .= '* ';
184 $inuse = true;
185 }
186 $questionnames .= $DB->get_field('question', 'name', array('id' => $key)).'<br />';
187 }
188 }
189 if (!$questionlist) { // no questions were selected
190 redirect($pageurl->out());
191 }
192 $questionlist = rtrim($questionlist, ',');
dcd51df2 193
f4b879dd 194 // Add an explanation about questions in use
195 if ($inuse) {
196 $questionnames .= '<br />'.get_string('questionsinuse', 'quiz');
197 }
198 notice_yesno(get_string("deletequestionscheck", "quiz", $questionnames),
199 $pageurl->out_action(array('deleteselected'=>$questionlist, 'confirm'=>md5($questionlist))),
200 $pageurl->out_action());
201
202 echo '</td></tr>';
203 echo '</table>';
204 print_footer($COURSE);
205 exit;
206 }
516cf3eb 207
0fd5feef 208
f4b879dd 209 // starts with category selection form
210 print_box_start('generalbox questionbank');
211 print_heading(get_string('questionbank', 'question'), '', 2);
212 $this->display_category_form($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, $recurse, $showhidden, $showquestiontext);
624cbc9c 213
f4b879dd 214 // continues with list of questions
215 $this->display_question_list($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, isset($cm) ? $cm : null,
216 $recurse, $page, $perpage, $showhidden, $sortorder, $sortorderdecoded, $showquestiontext,
217 $contexts->having_cap('moodle/question:add'));
516cf3eb 218
f4b879dd 219 print_box_end();
516cf3eb 220 }
221
f4b879dd 222 /**
223 * prints a form to choose categories
224 */
225 function display_category_form($contexts, $pageurl, $current, $recurse=1, $showhidden=false, $showquestiontext=false) {
226 global $CFG;
516cf3eb 227
516cf3eb 228
f4b879dd 229 /// Get all the existing categories now
230 $catmenu = question_category_options($contexts, false, 0, true);
516cf3eb 231
f4b879dd 232 $strcategory = get_string('category', 'quiz');
233 $strshow = get_string('show', 'quiz');
234 $streditcats = get_string('editcategories', 'quiz');
235
236 popup_form('edit.php?'.$pageurl->get_query_string().'&amp;category=', $catmenu, 'catmenu', $current, '', '', '', false, 'self', "<strong>$strcategory</strong>");
237
238 echo '<form method="get" action="edit.php" id="displayoptions">';
239 echo "<fieldset class='invisiblefieldset'>";
240 echo $pageurl->hidden_params_out(array('recurse', 'showhidden', 'showquestiontext'));
241 $this->display_category_form_checkbox('recurse', $recurse);
242 $this->display_category_form_checkbox('showhidden', $showhidden);
243 $this->display_category_form_checkbox('showquestiontext', $showquestiontext);
244 echo '<noscript><div class="centerpara"><input type="submit" value="'. get_string('go') .'" />';
245 echo '</div></noscript></fieldset></form>';
516cf3eb 246 }
247
f4b879dd 248 /**
249 * Private funciton to help the preceeding function.
250 */
251 protected function display_category_form_checkbox($name, $checked) {
252 echo '<div><input type="hidden" id="' . $name . '_off" name="' . $name . '" value="0" />';
253 echo '<input type="checkbox" id="' . $name . '_on" name="' . $name . '" value="1"';
254 if ($checked) {
255 echo ' checked="checked"';
516cf3eb 256 }
f4b879dd 257 echo ' onchange="getElementById(\'displayoptions\').submit(); return true;" />';
258 echo '<label for="' . $name . '_on">';
259 print_string($name, 'quiz');
260 echo "</label></div>\n";
516cf3eb 261 }
262
f4b879dd 263 /**
264 * Prints the table of questions in a category with interactions
265 *
266 * @param object $course The course object
267 * @param int $categoryid The id of the question category to be displayed
268 * @param int $cm The course module record if we are in the context of a particular module, 0 otherwise
269 * @param int $recurse This is 1 if subcategories should be included, 0 otherwise
270 * @param int $page The number of the page to be displayed
271 * @param int $perpage Number of questions to show per page
272 * @param boolean $showhidden True if also hidden questions should be displayed
273 * @param boolean $showquestiontext whether the text of each question should be shown in the list
274 */
275 function display_question_list($contexts, $pageurl, $categoryandcontext, $cm = null,
276 $recurse=1, $page=0, $perpage=100, $showhidden=false, $sortorder='typename', $sortorderdecoded='qtype, name ASC',
277 $showquestiontext = false, $addcontexts = array()) {
278 global $USER, $CFG, $THEME, $COURSE, $DB;
279
280 list($categoryid, $contextid)= explode(',', $categoryandcontext);
281
282 $qtypemenu = question_type_menu();
283
284 $strcategory = get_string("category", "quiz");
285 $strquestion = get_string("question", "quiz");
286 $straddquestions = get_string("addquestions", "quiz");
287 $strimportquestions = get_string("importquestions", "quiz");
288 $strexportquestions = get_string("exportquestions", "quiz");
289 $strnoquestions = get_string("noquestionsincategory", "quiz");
290 $strselect = get_string("select", "quiz");
291 $strselectall = get_string("selectall", "quiz");
292 $strselectnone = get_string("selectnone", "quiz");
293 $strcreatenewquestion = get_string("createnewquestion", "quiz");
294 $strquestionname = get_string("questionname", "quiz");
295 $strdelete = get_string("delete");
296 $stredit = get_string("edit");
297 $strmove = get_string('moveqtoanothercontext', 'question');
298 $strview = get_string("view");
299 $straction = get_string("action");
300 $strrestore = get_string('restore');
301
302 $strtype = get_string("type", "quiz");
303 $strcreatemultiple = get_string("createmultiple", "quiz");
304 $strpreview = get_string("preview","quiz");
305
306 if (!$categoryid) {
307 echo "<p style=\"text-align:center;\"><b>";
308 print_string("selectcategoryabove", "quiz");
309 echo "</b></p>";
310 return;
624cbc9c 311 }
f4b879dd 312
313 if (!$category = $DB->get_record('question_categories', array('id' => $categoryid, 'contextid' => $contextid))) {
314 notify('Category not found!');
315 return;
624cbc9c 316 }
f4b879dd 317 $catcontext = get_context_instance_by_id($contextid);
318 $canadd = has_capability('moodle/question:add', $catcontext);
319 //check for capabilities on all questions in category, will also apply to sub cats.
320 $caneditall =has_capability('moodle/question:editall', $catcontext);
321 $canuseall =has_capability('moodle/question:useall', $catcontext);
322 $canmoveall =has_capability('moodle/question:moveall', $catcontext);
323
324 if ($cm AND $cm->modname == 'quiz') {
325 $quizid = $cm->instance;
326 } else {
327 $quizid = 0;
624cbc9c 328 }
f4b879dd 329 $returnurl = $pageurl->out();
330 $questionurl = new moodle_url("$CFG->wwwroot/question/question.php",
331 array('returnurl' => $returnurl));
332 if ($cm!==null){
333 $questionurl->param('cmid', $cm->id);
334 } else {
335 $questionurl->param('courseid', $COURSE->id);
624cbc9c 336 }
f4b879dd 337 $questionmoveurl = new moodle_url("$CFG->wwwroot/question/contextmoveq.php",
338 array('returnurl' => $returnurl));
339 if ($cm!==null){
340 $questionmoveurl->param('cmid', $cm->id);
341 } else {
342 $questionmoveurl->param('courseid', $COURSE->id);
516cf3eb 343 }
f4b879dd 344 echo '<div class="boxaligncenter">';
345 $formatoptions = new stdClass;
346 $formatoptions->noclean = true;
347 echo format_text($category->info, FORMAT_MOODLE, $formatoptions, $COURSE->id);
348
349 echo '<table><tr>';
350
351 if ($canadd) {
352 echo '<td valign="top" align="right">';
353 popup_form ($questionurl->out(false, array('category' => $category->id)).'&amp;qtype=', $qtypemenu, "addquestion", "", "choose", "", "", false, "self", "<strong>$strcreatenewquestion</strong>");
354 echo '</td><td valign="top" align="right">';
355 helpbutton("questiontypes", $strcreatenewquestion, "quiz");
356 echo '</td>';
271e6dec 357 }
f4b879dd 358 else {
359 echo '<td>';
360 print_string('nopermissionadd', 'question');
361 echo '</td>';
271e6dec 362 }
363
f4b879dd 364 echo '</tr></table>';
365 echo '</div>';
366
367 $categorylist = ($recurse) ? question_categorylist($category->id) : $category->id;
368
369 // hide-feature
370 $showhidden = $showhidden ? '' : " AND hidden = '0'";
371 $categorylist_array = explode(',', $categorylist);
372 list($usql, $params) = $DB->get_in_or_equal($categorylist_array);
373 if (!$totalnumber = $DB->count_records_select('question', "category $usql AND parent = '0' $showhidden", $params)) {
374 echo "<p style=\"text-align:center;\">";
375 echo $strnoquestions;
376 echo "</p>";
377 return;
271e6dec 378 }
379
f4b879dd 380 if (!$questions = $DB->get_records_select('question', "category $usql AND parent = '0' $showhidden", $params, $sortorderdecoded, '*', $page*$perpage, $perpage)) {
381 // There are no questions on the requested page.
382 $page = 0;
383 if (!$questions = $DB->get_records_select('question', "category $usql AND parent = '0' $showhidden", $params, $sortorderdecoded, '*', 0, $perpage)) {
384 // There are no questions at all
385 echo "<p style=\"text-align:center;\">";
386 echo $strnoquestions;
387 echo "</p>";
388 return;
516cf3eb 389 }
390 }
516cf3eb 391
f4b879dd 392 print_paging_bar($totalnumber, $page, $perpage, $pageurl, 'qpage');
393 $this->display_question_sort_options($pageurl, $sortorder);
394
395 echo '<form method="post" action="edit.php">';
396 echo '<fieldset class="invisiblefieldset" style="display: block;">';
397 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
398 echo $pageurl->hidden_params_out();
399 echo '<table id="categoryquestions" style="width: 100%"><tr>';
400 echo "<th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">$straction</th>";
401
402 echo "<th style=\"white-space:nowrap; text-align: left;\" class=\"header\" scope=\"col\">$strquestionname</th>
403 <th style=\"white-space:nowrap; text-align: right;\" class=\"header\" scope=\"col\">$strtype</th>";
516cf3eb 404 echo "</tr>\n";
f4b879dd 405 foreach ($questions as $question) {
406 $nameclass = '';
407 $textclass = '';
408 if ($question->hidden) {
409 $nameclass = 'dimmed_text';
410 $textclass = 'dimmed_text';
411 }
412 if ($showquestiontext) {
413 $nameclass .= ' header';
414 }
415 if ($nameclass) {
416 $nameclass = 'class="' . $nameclass . '"';
417 }
418 if ($textclass) {
419 $textclass = 'class="' . $textclass . '"';
420 }
624cbc9c 421
f4b879dd 422 echo "<tr>\n<td style=\"white-space:nowrap;\" $nameclass>\n";
516cf3eb 423
f4b879dd 424 $canuseq = question_has_capability_on($question, 'use', $question->category);
425 if (function_exists('module_specific_actions')) {
426 echo module_specific_actions($pageurl, $question->id, $cm->id, $canuseq);
427 }
95947ac9 428
f4b879dd 429 // preview
430 if ($canuseq) {
431 $quizorcourseid = $quizid?('&amp;quizid=' . $quizid):('&amp;courseid=' .$COURSE->id);
432 link_to_popup_window('/question/preview.php?id=' . $question->id . $quizorcourseid, 'questionpreview',
433 "<img src=\"$CFG->pixpath/t/preview.gif\" class=\"iconsmall\" alt=\"$strpreview\" />",
434 0, 0, $strpreview, QUESTION_PREVIEW_POPUP_OPTIONS);
435 }
436 // edit, hide, delete question, using question capabilities, not quiz capabilieies
437 if (question_has_capability_on($question, 'edit', $question->category) || question_has_capability_on($question, 'move', $question->category)) {
438 echo "<a title=\"$stredit\" href=\"".$questionurl->out(false, array('id'=>$question->id))."\"><img
439 src=\"$CFG->pixpath/t/edit.gif\" alt=\"$stredit\" /></a>&nbsp;";
440 } elseif (question_has_capability_on($question, 'view', $question->category)){
441 echo "<a title=\"$strview\" href=\"".$questionurl->out(false, array('id'=>$question->id))."\"><img
442 src=\"$CFG->pixpath/i/info.gif\" alt=\"$strview\" /></a>&nbsp;";
443 }
444
445 if (question_has_capability_on($question, 'move', $question->category) && question_has_capability_on($question, 'view', $question->category)) {
446 echo "<a title=\"$strmove\" href=\"".$questionurl->out(false, array('id'=>$question->id, 'movecontext'=>1))."\"><img
447 src=\"$CFG->pixpath/t/move.gif\" alt=\"$strmove\" /></a>&nbsp;";
448 }
516cf3eb 449
f4b879dd 450 if (question_has_capability_on($question, 'edit', $question->category)) {
451 // hide-feature
452 if($question->hidden) {
453 echo "<a title=\"$strrestore\" href=\"edit.php?".$pageurl->get_query_string()."&amp;unhide=$question->id&amp;sesskey=".sesskey()."\"><img
454 src=\"$CFG->pixpath/t/restore.gif\" alt=\"$strrestore\" /></a>";
455 } else {
456 echo "<a title=\"$strdelete\" href=\"edit.php?".$pageurl->get_query_string()."&amp;deleteselected=$question->id&amp;q$question->id=1\"><img
457 src=\"$CFG->pixpath/t/delete.gif\" alt=\"$strdelete\" /></a>";
458 }
459 }
460 if ($caneditall || $canmoveall || $canuseall){
461 echo "&nbsp;<input title=\"$strselect\" type=\"checkbox\" name=\"q$question->id\" value=\"1\" />";
462 }
463 echo "</td>\n";
464
465 echo "<td $nameclass>" . format_string($question->name) . "</td>\n";
466 echo "<td $nameclass style='text-align: right'>\n";
467 print_question_icon($question);
468 echo "</td>\n";
469 echo "</tr>\n";
470 if($showquestiontext){
471 echo '<tr><td colspan="3" ' . $textclass . '>';
472 $formatoptions = new stdClass;
473 $formatoptions->noclean = true;
474 $formatoptions->para = false;
475 echo format_text($question->questiontext, $question->questiontextformat,
476 $formatoptions, $COURSE->id);
477 echo "</td></tr>\n";
478 }
271e6dec 479 }
f4b879dd 480 echo "</table>\n";
86909ce0 481
f4b879dd 482 $paging = print_paging_bar($totalnumber, $page, $perpage, $pageurl, 'qpage', false, true);
483 if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) {
484 if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) {
485 $showall = '<a href="edit.php?'.$pageurl->get_query_string(array('qperpage'=>1000)).'">'.get_string('showall', 'moodle', $totalnumber).'</a>';
486 } else {
487 $showall = '<a href="edit.php?'.$pageurl->get_query_string(array('qperpage'=>DEFAULT_QUESTIONS_PER_PAGE)).'">'.get_string('showperpage', 'moodle', DEFAULT_QUESTIONS_PER_PAGE).'</a>';
488 }
489 if ($paging) {
490 $paging = substr($paging, 0, strrpos($paging, '</div>'));
491 $paging .= "<br />$showall</div>";
492 } else {
493 $paging = "<div class='paging'>$showall</div>";
494 }
86909ce0 495 }
f4b879dd 496 echo $paging;
497
498 if ($caneditall || $canmoveall || $canuseall){
499 echo '<a href="javascript:select_all_in(\'TABLE\',null,\'categoryquestions\');">'.$strselectall.'</a> /'.
500 ' <a href="javascript:deselect_all_in(\'TABLE\',null,\'categoryquestions\');">'.$strselectnone.'</a>';
501 echo '<br />';
502 echo '<strong>&nbsp;'.get_string('withselected', 'quiz').':</strong><br />';
503
504 if (function_exists('module_specific_buttons')) {
505 echo module_specific_buttons($cm->id);
506 }
507 // print delete and move selected question
508 if ($caneditall) {
509 echo '<input type="submit" name="deleteselected" value="'.$strdelete."\" />\n";
510 }
511 if ($canmoveall && count($addcontexts)) {
512 echo '<input type="submit" name="move" value="'.get_string('moveto', 'quiz')."\" />\n";
513 question_category_select_menu($addcontexts, false, 0, "$category->id,$category->contextid");
514 }
515
516 if (function_exists('module_specific_controls') && $canuseall) {
517 echo module_specific_controls($totalnumber, $recurse, $category, $cm->id);
271e6dec 518 }
519 }
f4b879dd 520 echo '</fieldset>';
521 echo "</form>\n";
522 }
523
524 function display_question_sort_options($pageurl, $sortorder){
525 global $USER;
526 //sort options
527 $html = "<div class=\"mdl-align questionsortoptions\">";
528 // POST method should only be used for parameters that change data
529 // or if POST method has to be used, the user must be redirected immediately to
530 // non-POSTed page to not break the back button
531 $html .= '<form method="get" action="edit.php">';
532 $html .= '<fieldset class="invisiblefieldset" style="display: block;">';
533 $html .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
534 $html .= $pageurl->hidden_params_out(array('qsortorder'));
535 //choose_from_menu concatenates the form name with
536 //"menu" so the label is for menuqsortorder
537 $sortoptions = array('alpha' => get_string("qname", "quiz"),
538 'typealpha' => get_string("qtypename", "quiz"),
539 'age' => get_string("age", "quiz"));
540 $a = choose_from_menu($sortoptions, 'qsortorder', $sortorder, false, 'this.form.submit();', '0', true);
541 $html .= '<label for="menuqsortorder">'.get_string('sortquestionsbyx', 'quiz', $a).'</label>';
542 $html .= '<noscript><div><input type="submit" value="'.get_string("sortsubmit", "quiz").'" /></div></noscript>';
543 $html .= '</fieldset>';
544 $html .= "</form>\n";
545 $html .= "</div>\n";
546 echo $html;
547 }
548
549 function process_actions($pageurl, $cm){
550 global $CFG, $COURSE, $DB;
551 /// Now, check for commands on this page and modify variables as necessary
552 if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) { /// Move selected questions to new category
553 $category = required_param('category', PARAM_SEQUENCE);
554 list($tocategoryid, $contextid) = explode(',', $category);
555 if (! $tocategory = $DB->get_record('question_categories', array('id' => $tocategoryid, 'contextid' => $contextid))) {
556 print_error('cannotfindcate', 'question');
271e6dec 557 }
f4b879dd 558 $tocontext = get_context_instance_by_id($contextid);
559 require_capability('moodle/question:add', $tocontext);
560 $rawdata = (array) data_submitted();
561 $questionids = array();
562 foreach ($rawdata as $key => $value) { // Parse input for question ids
563 if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
564 $key = $matches[1];
565 $questionids[] = $key;
271e6dec 566 }
567 }
f4b879dd 568 if ($questionids){
569 list($usql, $params) = $DB->get_in_or_equal($questionids);
570 $sql = "SELECT q.*, c.contextid FROM {question} q, {question_categories} c WHERE q.id $usql AND c.id = q.category";
571 if (!$questions = $DB->get_records_sql($sql, $params)){
572 print_error('questiondoesnotexist', 'question', $pageurl->out());
271e6dec 573 }
f4b879dd 574 $checkforfiles = false;
575 foreach ($questions as $question){
576 //check capabilities
577 question_require_capability_on($question, 'move');
578 $fromcontext = get_context_instance_by_id($question->contextid);
579 if (get_filesdir_from_context($fromcontext) != get_filesdir_from_context($tocontext)){
580 $checkforfiles = true;
581 }
582 }
583 $returnurl = $pageurl->out(false, array('category'=>"$tocategoryid,$contextid"));
584 if (!$checkforfiles){
585 if (!question_move_questions_to_category(implode(',', $questionids), $tocategory->id)) {
586 print_error('errormovingquestions', 'question', $returnurl, $questionids);
587 }
588 redirect($returnurl);
271e6dec 589 } else {
f4b879dd 590 $movecontexturl = new moodle_url($CFG->wwwroot.'/question/contextmoveq.php',
591 array('returnurl' => $returnurl,
592 'ids'=>$questionidlist,
593 'tocatid'=> $tocategoryid));
594 if ($cm){
595 $movecontexturl->param('cmid', $cm->id);
596 } else {
597 $movecontexturl->param('courseid', $COURSE->id);
598 }
599 redirect($movecontexturl->out());
86909ce0 600 }
601 }
602 }
86909ce0 603
f4b879dd 604 if (optional_param('deleteselected', false, PARAM_BOOL)) { // delete selected questions from the category
605 if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) { // teacher has already confirmed the action
606 $deleteselected = required_param('deleteselected');
607 if ($confirm == md5($deleteselected)) {
608 if ($questionlist = explode(',', $deleteselected)) {
609 // for each question either hide it if it is in use or delete it
610 foreach ($questionlist as $questionid) {
611 question_require_capability_on($questionid, 'edit');
612 if ($DB->record_exists('quiz_question_instances', array('question' => $questionid))) {
613 if (!$DB->set_field('question', 'hidden', 1, array('id' => $questionid))) {
614 question_require_capability_on($questionid, 'edit');
615 print_error('cannothidequestion', 'question');
616 }
617 } else {
618 delete_question($questionid);
86909ce0 619 }
86909ce0 620 }
621 }
f4b879dd 622 redirect($pageurl->out());
623 } else {
624 print_error('invalidconfirm', 'question');
86909ce0 625 }
86909ce0 626 }
86909ce0 627 }
8e77884c 628
f4b879dd 629 // Unhide a question
630 if(($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) {
631 question_require_capability_on($unhide, 'edit');
632 if(!$DB->set_field('question', 'hidden', 0, array('id', $unhide))) {
633 print_error('cannotunhidequestion', 'question');
271e6dec 634 }
271e6dec 635 redirect($pageurl->out());
636 }
271e6dec 637 }
86909ce0 638}
f4b879dd 639
b72ff476 640/**
641 * Common setup for all pages for editing questions.
271e6dec 642 * @param string $edittab code for this edit tab
b72ff476 643 * @param boolean $requirecmid require cmid? default false
644 * @param boolean $requirecourseid require courseid, if cmid is not given? default true
271e6dec 645 * @return array $thispageurl, $contexts, $cmid, $cm, $module, $pagevars
b72ff476 646 */
271e6dec 647function question_edit_setup($edittab, $requirecmid = false, $requirecourseid = true){
f34488b2 648 global $COURSE, $QUESTION_EDITTABCAPS, $DB;
271e6dec 649
650 //$thispageurl is used to construct urls for all question edit pages we link to from this page. It contains an array
36e298bc 651 //of parameters that are passed from page to page.
b72ff476 652 $thispageurl = new moodle_url();
b4a2e413 653 $thispageurl->remove_params(); // We are going to explicity add back everything important - this avoids unwanted params from being retained.
654
b72ff476 655 if ($requirecmid){
656 $cmid =required_param('cmid', PARAM_INT);
657 } else {
658 $cmid = optional_param('cmid', 0, PARAM_INT);
659 }
660 if ($cmid){
271e6dec 661 list($module, $cm) = get_module_from_cmid($cmid);
b72ff476 662 $courseid = $cm->course;
663 $thispageurl->params(compact('cmid'));
271e6dec 664 require_login($courseid, false, $cm);
665 $thiscontext = get_context_instance(CONTEXT_MODULE, $cmid);
b72ff476 666 } else {
667 $module = null;
668 $cm = null;
669 if ($requirecourseid){
670 $courseid = required_param('courseid', PARAM_INT);
671 } else {
672 $courseid = optional_param('courseid', 0, PARAM_INT);
673 }
674 if ($courseid){
675 $thispageurl->params(compact('courseid'));
271e6dec 676 require_login($courseid, false);
677 $thiscontext = get_context_instance(CONTEXT_COURSE, $courseid);
678 } else {
679 $thiscontext = null;
b72ff476 680 }
681 }
271e6dec 682
683 if ($thiscontext){
684 $contexts = new question_edit_contexts($thiscontext);
685 $contexts->require_one_edit_tab_cap($edittab);
686
687 } else {
688 $contexts = null;
689 }
690
691
692
36e298bc 693 $pagevars['qpage'] = optional_param('qpage', -1, PARAM_INT);
271e6dec 694
7bc26c8f 695 //pass 'cat' from page to page and when 'category' comes from a drop down menu
271e6dec 696 //then we also reset the qpage so we go to page 1 of
7bc26c8f 697 //a new cat.
271e6dec 698 $pagevars['cat'] = optional_param('cat', 0, PARAM_SEQUENCE);// if empty will be set up later
699 if ($category = optional_param('category', 0, PARAM_SEQUENCE)){
a18fbcfb 700 if ($pagevars['cat'] != $category){ // is this a move to a new category?
701 $pagevars['cat'] = $category;
702 $pagevars['qpage'] = 0;
703 }
7bc26c8f 704 }
705 if ($pagevars['cat']){
706 $thispageurl->param('cat', $pagevars['cat']);
707 }
b72ff476 708 if ($pagevars['qpage'] > -1) {
709 $thispageurl->param('qpage', $pagevars['qpage']);
710 } else {
711 $pagevars['qpage'] = 0;
712 }
713
36e298bc 714 $pagevars['qperpage'] = optional_param('qperpage', -1, PARAM_INT);
b72ff476 715 if ($pagevars['qperpage'] > -1) {
716 $thispageurl->param('qperpage', $pagevars['qperpage']);
717 } else {
718 $pagevars['qperpage'] = DEFAULT_QUESTIONS_PER_PAGE;
719 }
271e6dec 720
36e298bc 721 $sortoptions = array('alpha' => 'name, qtype ASC',
722 'typealpha' => 'qtype, name ASC',
723 'age' => 'id ASC');
724
725 if ($sortorder = optional_param('qsortorder', '', PARAM_ALPHA)) {
726 $pagevars['qsortorderdecoded'] = $sortoptions[$sortorder];
727 $pagevars['qsortorder'] = $sortorder;
728 $thispageurl->param('qsortorder', $sortorder);
b72ff476 729 } else {
7bc26c8f 730 $pagevars['qsortorderdecoded'] = $sortoptions['typealpha'];
731 $pagevars['qsortorder'] = 'typealpha';
271e6dec 732 }
36e298bc 733
271e6dec 734 $defaultcategory = question_make_default_categories($contexts->all());
735
736 $contextlistarr = array();
737 foreach ($contexts->having_one_edit_tab_cap($edittab) as $context){
738 $contextlistarr[] = "'$context->id'";
739 }
740 $contextlist = join($contextlistarr, ' ,');
741 if (!empty($pagevars['cat'])){
742 $catparts = explode(',', $pagevars['cat']);
f34488b2 743 if (!$catparts[0] || (FALSE !== array_search($catparts[1], $contextlistarr)) ||
744 !$DB->count_records_select("question_categories", "id = ? AND contextid = ?", array($catparts[0], $catparts[1]))) {
5a2a5331 745 print_error('invalidcategory', 'quiz');
271e6dec 746 }
747 } else {
748 $category = $defaultcategory;
749 $pagevars['cat'] = "$category->id,$category->contextid";
36e298bc 750 }
751
752 if(($recurse = optional_param('recurse', -1, PARAM_BOOL)) != -1) {
753 $pagevars['recurse'] = $recurse;
754 $thispageurl->param('recurse', $recurse);
755 } else {
756 $pagevars['recurse'] = 1;
757 }
271e6dec 758
36e298bc 759 if(($showhidden = optional_param('showhidden', -1, PARAM_BOOL)) != -1) {
760 $pagevars['showhidden'] = $showhidden;
761 $thispageurl->param('showhidden', $showhidden);
762 } else {
763 $pagevars['showhidden'] = 0;
764 }
271e6dec 765
36e298bc 766 if(($showquestiontext = optional_param('showquestiontext', -1, PARAM_BOOL)) != -1) {
767 $pagevars['showquestiontext'] = $showquestiontext;
768 $thispageurl->param('showquestiontext', $showquestiontext);
769 } else {
770 $pagevars['showquestiontext'] = 0;
771 }
271e6dec 772
986effb6 773 //category list page
774 $pagevars['cpage'] = optional_param('cpage', 1, PARAM_INT);
775 if ($pagevars['cpage'] != 1){
776 $thispageurl->param('cpage', $pagevars['cpage']);
777 }
36e298bc 778
271e6dec 779
780 return array($thispageurl, $contexts, $cmid, $cm, $module, $pagevars);
781}
782class question_edit_contexts{
783 var $allcontexts;
784 /**
785 * @param current context
786 */
787 function question_edit_contexts($thiscontext){
788 $pcontextids = get_parent_contexts($thiscontext);
789 $contexts = array($thiscontext);
790 foreach ($pcontextids as $pcontextid){
791 $contexts[] = get_context_instance_by_id($pcontextid);
792 }
793 $this->allcontexts = $contexts;
794 }
795 /**
796 * @return array all parent contexts
797 */
798 function all(){
799 return $this->allcontexts;
800 }
801 /**
802 * @return object lowest context which must be either the module or course context
803 */
804 function lowest(){
805 return $this->allcontexts[0];
806 }
807 /**
808 * @param string $cap capability
809 * @return array parent contexts having capability, zero based index
810 */
811 function having_cap($cap){
812 $contextswithcap = array();
813 foreach ($this->allcontexts as $context){
814 if (has_capability($cap, $context)){
815 $contextswithcap[] = $context;
816 }
817 }
818 return $contextswithcap;
819 }
820 /**
821 * @param array $caps capabilities
822 * @return array parent contexts having at least one of $caps, zero based index
823 */
824 function having_one_cap($caps){
825 $contextswithacap = array();
826 foreach ($this->allcontexts as $context){
827 foreach ($caps as $cap){
828 if (has_capability($cap, $context)){
829 $contextswithacap[] = $context;
830 break; //done with caps loop
831 }
832 }
833 }
834 return $contextswithacap;
835 }
836 /**
837 * @param string $tabname edit tab name
838 * @return array parent contexts having at least one of $caps, zero based index
839 */
840 function having_one_edit_tab_cap($tabname){
841 global $QUESTION_EDITTABCAPS;
842 return $this->having_one_cap($QUESTION_EDITTABCAPS[$tabname]);
843 }
844 /**
845 * Has at least one parent context got the cap $cap?
846 *
847 * @param string $cap capability
848 * @return boolean
849 */
850 function have_cap($cap){
851 return (count($this->having_cap($cap)));
852 }
853
854 /**
855 * Has at least one parent context got one of the caps $caps?
856 *
857 * @param string $cap capability
858 * @return boolean
859 */
860 function have_one_cap($caps){
861 foreach ($caps as $cap){
862 if ($this->have_cap($cap)){
863 return true;
864 }
865 }
866 return false;
867 }
868 /**
869 * Has at least one parent context got one of the caps for actions on $tabname
870 *
871 * @param string $tabname edit tab name
872 * @return boolean
873 */
874 function have_one_edit_tab_cap($tabname){
875 global $QUESTION_EDITTABCAPS;
876 return $this->have_one_cap($QUESTION_EDITTABCAPS[$tabname]);
877 }
878 /**
879 * Throw error if at least one parent context hasn't got the cap $cap
880 *
881 * @param string $cap capability
882 */
883 function require_cap($cap){
884 if (!$this->have_cap($cap)){
885 print_error('nopermissions', '', '', $cap);
886 }
887 }
888 /**
889 * Throw error if at least one parent context hasn't got one of the caps $caps
890 *
891 * @param array $cap capabilities
892 */
893 function require_one_cap($caps){
894 if (!$this->have_one_cap($caps)){
895 $capsstring = join($caps, ', ');
896 print_error('nopermissions', '', '', $capsstring);
897 }
898 }
899 /**
900 * Throw error if at least one parent context hasn't got one of the caps $caps
901 *
902 * @param string $tabname edit tab name
903 */
904 function require_one_edit_tab_cap($tabname){
905 if (!$this->have_one_edit_tab_cap($tabname)){
906 print_error('nopermissions', '', '', 'access question edit tab '.$tabname);
907 }
908 }
909}
910
911//capabilities for each page of edit tab.
912//this determines which contexts' categories are available. At least one
913//page is displayed if user has one of the capability on at least one context
914$QUESTION_EDITTABCAPS = array(
a856c14e 915 'editq' => array('moodle/question:add',
916 'moodle/question:editmine',
917 'moodle/question:editall',
918 'moodle/question:viewmine',
919 'moodle/question:viewall',
920 'moodle/question:usemine',
921 'moodle/question:useall',
922 'moodle/question:movemine',
923 'moodle/question:moveall'),
924 'questions'=>array('moodle/question:add',
925 'moodle/question:editmine',
926 'moodle/question:editall',
927 'moodle/question:viewmine',
928 'moodle/question:viewall',
929 'moodle/question:movemine',
930 'moodle/question:moveall'),
931 'categories'=>array('moodle/question:managecategory'),
932 'import'=>array('moodle/question:add'),
933 'export'=>array('moodle/question:viewall', 'moodle/question:viewmine'));
271e6dec 934
935/**
936 * Make sure user is logged in as required in this context.
937 */
938function require_login_in_context($contextorid = null){
f34488b2 939 global $DB;
271e6dec 940 if (!is_object($contextorid)){
941 $context = get_context_instance_by_id($contextorid);
942 } else {
943 $context = $contextorid;
944 }
945 if ($context && ($context->contextlevel == CONTEXT_COURSE)) {
946 require_login($context->instanceid);
947 } else if ($context && ($context->contextlevel == CONTEXT_MODULE)) {
f34488b2 948 if ($cm = $DB->get_record('course_modules',array('id' =>$context->instanceid))) {
949 if (!$course = $DB->get_record('course', array('id' => $cm->course))) {
0be2c858 950 print_error('invalidcourseid');
271e6dec 951 }
952 require_course_login($course, true, $cm);
953
954 } else {
0be2c858 955 print_error('invalidcoursemodule');
271e6dec 956 }
957 } else if ($context && ($context->contextlevel == CONTEXT_SYSTEM)) {
958 if (!empty($CFG->forcelogin)) {
959 require_login();
960 }
961
962 } else {
963 require_login();
964 }
b72ff476 965}
871ab9a3 966?>