Commit | Line | Data |
---|---|---|
17f229fa RM |
1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | ||
17f229fa | 18 | /** |
701ae1eb | 19 | * Class to print a view of the question bank. |
17f229fa | 20 | * |
701ae1eb TH |
21 | * @package core_question |
22 | * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
17f229fa RM |
24 | */ |
25 | ||
701ae1eb TH |
26 | namespace core_question\bank; |
27 | defined('MOODLE_INTERNAL') || die(); | |
28 | ||
29 | use core_question\bank\search\condition; | |
30 | ||
17f229fa RM |
31 | |
32 | /** | |
33 | * This class prints a view of the question bank, including | |
34 | * + Some controls to allow users to to select what is displayed. | |
35 | * + A list of questions as a table. | |
36 | * + Further controls to do things with the questions. | |
37 | * | |
38 | * This class gives a basic view, and provides plenty of hooks where subclasses | |
39 | * can override parts of the display. | |
40 | * | |
41 | * The list of questions presented as a table is generated by creating a list of | |
42 | * core_question\bank\column objects, one for each 'column' to be displayed. These | |
43 | * manage | |
44 | * + outputting the contents of that column, given a $question object, but also | |
45 | * + generating the right fragments of SQL to ensure the necessary data is present, | |
46 | * and sorted in the right order. | |
47 | * + outputting table headers. | |
48 | * | |
701ae1eb TH |
49 | * @copyright 2009 Tim Hunt |
50 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
17f229fa RM |
51 | */ |
52 | class view { | |
53 | const MAX_SORTS = 3; | |
54 | ||
d30c6cdc TH |
55 | /** |
56 | * @var \moodle_url base URL for the current page. Used as the | |
57 | * basis for making URLs for actions that reload the page. | |
58 | */ | |
17f229fa | 59 | protected $baseurl; |
d30c6cdc TH |
60 | |
61 | /** | |
62 | * @var \moodle_url used as a basis for URLs that edit a question. | |
63 | */ | |
17f229fa | 64 | protected $editquestionurl; |
d30c6cdc TH |
65 | |
66 | /** | |
67 | * @var \question_edit_contexts | |
68 | */ | |
17f229fa | 69 | protected $contexts; |
d30c6cdc TH |
70 | |
71 | /** | |
72 | * @var object|\cm_info|null if we are in a module context, the cm. | |
73 | */ | |
17f229fa | 74 | protected $cm; |
d30c6cdc TH |
75 | |
76 | /** | |
77 | * @var object the course we are within. | |
78 | */ | |
17f229fa | 79 | protected $course; |
d30c6cdc TH |
80 | |
81 | /** | |
82 | * @var \question_bank_column_base[] these are all the 'columns' that are | |
83 | * part of the display. Array keys are the class name. | |
84 | */ | |
85 | protected $requiredcolumns; | |
86 | ||
87 | /** | |
88 | * @var \question_bank_column_base[] these are the 'columns' that are | |
89 | * actually displayed as a column, in order. Array keys are the class name. | |
90 | */ | |
17f229fa | 91 | protected $visiblecolumns; |
d30c6cdc TH |
92 | |
93 | /** | |
94 | * @var \question_bank_column_base[] these are the 'columns' that are | |
95 | * actually displayed as an additional row (e.g. question text), in order. | |
96 | * Array keys are the class name. | |
97 | */ | |
17f229fa | 98 | protected $extrarows; |
d30c6cdc TH |
99 | |
100 | /** | |
101 | * @var array list of column class names for which columns to sort on. | |
102 | */ | |
17f229fa | 103 | protected $sort; |
d30c6cdc TH |
104 | |
105 | /** | |
106 | * @var int|null id of the a question to highlight in the list (if present). | |
107 | */ | |
17f229fa | 108 | protected $lastchangedid; |
d30c6cdc TH |
109 | |
110 | /** | |
111 | * @var string SQL to count the number of questions matching the current | |
112 | * search conditions. | |
113 | */ | |
17f229fa | 114 | protected $countsql; |
d30c6cdc TH |
115 | |
116 | /** | |
117 | * @var string SQL to actually load the question data to display. | |
118 | */ | |
17f229fa | 119 | protected $loadsql; |
d30c6cdc TH |
120 | |
121 | /** | |
122 | * @var array params used by $countsql and $loadsql (which currently must be the same). | |
123 | */ | |
17f229fa | 124 | protected $sqlparams; |
d30c6cdc TH |
125 | |
126 | /** | |
127 | * @var condition[] search conditions. | |
128 | */ | |
17f229fa RM |
129 | protected $searchconditions = array(); |
130 | ||
131 | /** | |
132 | * Constructor | |
b116f8b9 TH |
133 | * @param \question_edit_contexts $contexts |
134 | * @param \moodle_url $pageurl | |
17f229fa RM |
135 | * @param object $course course settings |
136 | * @param object $cm (optional) activity settings. | |
137 | */ | |
138 | public function __construct($contexts, $pageurl, $course, $cm = null) { | |
17f229fa RM |
139 | $this->contexts = $contexts; |
140 | $this->baseurl = $pageurl; | |
141 | $this->course = $course; | |
142 | $this->cm = $cm; | |
143 | ||
17f229fa RM |
144 | // Create the url of the new question page to forward to. |
145 | $returnurl = $pageurl->out_as_local_url(false); | |
146 | $this->editquestionurl = new \moodle_url('/question/question.php', | |
147 | array('returnurl' => $returnurl)); | |
148 | if ($cm !== null) { | |
149 | $this->editquestionurl->param('cmid', $cm->id); | |
150 | } else { | |
151 | $this->editquestionurl->param('courseid', $this->course->id); | |
152 | } | |
153 | ||
154 | $this->lastchangedid = optional_param('lastchanged', 0, PARAM_INT); | |
155 | ||
156 | $this->init_columns($this->wanted_columns(), $this->heading_column()); | |
157 | $this->init_sort(); | |
d30c6cdc | 158 | $this->init_search_conditions(); |
17f229fa RM |
159 | } |
160 | ||
161 | /** | |
162 | * Initialize search conditions from plugins | |
163 | * local_*_get_question_bank_search_conditions() must return an array of | |
164 | * \core_question\bank\search\condition objects. | |
165 | */ | |
166 | protected function init_search_conditions() { | |
167 | $searchplugins = get_plugin_list_with_function('local', 'get_question_bank_search_conditions'); | |
168 | foreach ($searchplugins as $component => $function) { | |
169 | foreach ($function($this) as $searchobject) { | |
170 | $this->add_searchcondition($searchobject); | |
171 | } | |
172 | } | |
173 | } | |
174 | ||
175 | protected function wanted_columns() { | |
176 | global $CFG; | |
177 | ||
178 | if (empty($CFG->questionbankcolumns)) { | |
179 | $questionbankcolumns = array('checkbox_column', 'question_type_column', | |
c11c8892 | 180 | 'question_name_idnumber_tags_column', 'edit_menu_column', |
701ae1eb TH |
181 | 'edit_action_column', 'copy_action_column', 'tags_action_column', |
182 | 'preview_action_column', 'delete_action_column', 'export_xml_action_column', | |
c11c8892 | 183 | 'creator_name_column', 'modifier_name_column'); |
17f229fa RM |
184 | } else { |
185 | $questionbankcolumns = explode(',', $CFG->questionbankcolumns); | |
186 | } | |
187 | if (question_get_display_preference('qbshowtext', 0, PARAM_BOOL, new \moodle_url(''))) { | |
188 | $questionbankcolumns[] = 'question_text_row'; | |
189 | } | |
190 | ||
191 | foreach ($questionbankcolumns as $fullname) { | |
192 | if (! class_exists($fullname)) { | |
193 | if (class_exists('core_question\\bank\\' . $fullname)) { | |
194 | $fullname = 'core_question\\bank\\' . $fullname; | |
195 | } else { | |
196 | throw new \coding_exception("No such class exists: $fullname"); | |
197 | } | |
198 | } | |
199 | $this->requiredcolumns[$fullname] = new $fullname($this); | |
200 | } | |
201 | return $this->requiredcolumns; | |
202 | } | |
203 | ||
204 | ||
205 | /** | |
206 | * Get a column object from its name. | |
207 | * | |
208 | * @param string $columnname. | |
209 | * @return \core_question\bank\column_base. | |
210 | */ | |
211 | protected function get_column_type($columnname) { | |
212 | if (! class_exists($columnname)) { | |
213 | if (class_exists('core_question\\bank\\' . $columnname)) { | |
214 | $columnname = 'core_question\\bank\\' . $columnname; | |
215 | } else { | |
216 | throw new \coding_exception("No such class exists: $columnname"); | |
217 | } | |
218 | } | |
219 | if (empty($this->requiredcolumns[$columnname])) { | |
220 | $this->requiredcolumns[$columnname] = new $columnname($this); | |
221 | } | |
222 | return $this->requiredcolumns[$columnname]; | |
223 | } | |
224 | ||
225 | /** | |
226 | * Specify the column heading | |
227 | * | |
228 | * @return string Column name for the heading | |
229 | */ | |
230 | protected function heading_column() { | |
231 | return 'question_bank_question_name_column'; | |
232 | } | |
233 | ||
234 | /** | |
235 | * Initializing table columns | |
236 | * | |
237 | * @param array $wanted Collection of column names | |
238 | * @param string $heading The name of column that is set as heading | |
239 | */ | |
240 | protected function init_columns($wanted, $heading = '') { | |
701ae1eb TH |
241 | // If we are using the edit menu column, allow it to absorb all the actions. |
242 | foreach ($wanted as $column) { | |
243 | if ($column instanceof edit_menu_column) { | |
244 | $wanted = $column->claim_menuable_columns($wanted); | |
245 | break; | |
246 | } | |
247 | } | |
248 | ||
249 | // Now split columns into real columns and rows. | |
17f229fa RM |
250 | $this->visiblecolumns = array(); |
251 | $this->extrarows = array(); | |
252 | foreach ($wanted as $column) { | |
253 | if ($column->is_extra_row()) { | |
254 | $this->extrarows[get_class($column)] = $column; | |
255 | } else { | |
256 | $this->visiblecolumns[get_class($column)] = $column; | |
257 | } | |
258 | } | |
259 | if (array_key_exists($heading, $this->requiredcolumns)) { | |
260 | $this->requiredcolumns[$heading]->set_as_heading(); | |
261 | } | |
262 | } | |
263 | ||
264 | /** | |
265 | * @param string $colname a column internal name. | |
266 | * @return bool is this column included in the output? | |
267 | */ | |
268 | public function has_column($colname) { | |
269 | return isset($this->visiblecolumns[$colname]); | |
270 | } | |
271 | ||
272 | /** | |
273 | * @return int The number of columns in the table. | |
274 | */ | |
275 | public function get_column_count() { | |
276 | return count($this->visiblecolumns); | |
277 | } | |
278 | ||
279 | public function get_courseid() { | |
280 | return $this->course->id; | |
281 | } | |
282 | ||
283 | protected function init_sort() { | |
284 | $this->init_sort_from_params(); | |
285 | if (empty($this->sort)) { | |
286 | $this->sort = $this->default_sort(); | |
287 | } | |
288 | } | |
289 | ||
290 | /** | |
291 | * Deal with a sort name of the form columnname, or colname_subsort by | |
d30c6cdc | 292 | * breaking it up, validating the bits that are present, and returning them. |
17f229fa | 293 | * If there is no subsort, then $subsort is returned as ''. |
d30c6cdc TH |
294 | * |
295 | * @param string $sort the sort parameter to process. | |
17f229fa RM |
296 | * @return array array($colname, $subsort). |
297 | */ | |
298 | protected function parse_subsort($sort) { | |
299 | // Do the parsing. | |
300 | if (strpos($sort, '-') !== false) { | |
301 | list($colname, $subsort) = explode('-', $sort, 2); | |
302 | } else { | |
303 | $colname = $sort; | |
304 | $subsort = ''; | |
305 | } | |
306 | // Validate the column name. | |
307 | $column = $this->get_column_type($colname); | |
308 | if (!isset($column) || !$column->is_sortable()) { | |
309 | for ($i = 1; $i <= self::MAX_SORTS; $i++) { | |
310 | $this->baseurl->remove_params('qbs' . $i); | |
311 | } | |
312 | throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $colname); | |
313 | } | |
314 | // Validate the subsort, if present. | |
315 | if ($subsort) { | |
316 | $subsorts = $column->is_sortable(); | |
317 | if (!is_array($subsorts) || !isset($subsorts[$subsort])) { | |
318 | throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $sort); | |
319 | } | |
320 | } | |
321 | return array($colname, $subsort); | |
322 | } | |
323 | ||
324 | protected function init_sort_from_params() { | |
325 | $this->sort = array(); | |
326 | for ($i = 1; $i <= self::MAX_SORTS; $i++) { | |
327 | if (!$sort = optional_param('qbs' . $i, '', PARAM_TEXT)) { | |
328 | break; | |
329 | } | |
330 | // Work out the appropriate order. | |
331 | $order = 1; | |
332 | if ($sort[0] == '-') { | |
333 | $order = -1; | |
334 | $sort = substr($sort, 1); | |
335 | if (!$sort) { | |
336 | break; | |
337 | } | |
338 | } | |
339 | // Deal with subsorts. | |
d30c6cdc | 340 | list($colname) = $this->parse_subsort($sort); |
17f229fa RM |
341 | $this->requiredcolumns[$colname] = $this->get_column_type($colname); |
342 | $this->sort[$sort] = $order; | |
343 | } | |
344 | } | |
345 | ||
346 | protected function sort_to_params($sorts) { | |
347 | $params = array(); | |
348 | $i = 0; | |
349 | foreach ($sorts as $sort => $order) { | |
350 | $i += 1; | |
351 | if ($order < 0) { | |
352 | $sort = '-' . $sort; | |
353 | } | |
354 | $params['qbs' . $i] = $sort; | |
355 | } | |
356 | return $params; | |
357 | } | |
358 | ||
359 | protected function default_sort() { | |
0aa17f26 JP |
360 | return array( |
361 | 'core_question\bank\question_type_column' => 1, | |
362 | 'core_question\bank\question_name_idnumber_tags_column-name' => 1 | |
363 | ); | |
17f229fa RM |
364 | } |
365 | ||
366 | /** | |
d30c6cdc | 367 | * @param string $sort a column or column_subsort name. |
17f229fa RM |
368 | * @return int the current sort order for this column -1, 0, 1 |
369 | */ | |
370 | public function get_primary_sort_order($sort) { | |
371 | $order = reset($this->sort); | |
372 | $primarysort = key($this->sort); | |
373 | if ($sort == $primarysort) { | |
374 | return $order; | |
375 | } else { | |
376 | return 0; | |
377 | } | |
378 | } | |
379 | ||
380 | /** | |
381 | * Get a URL to redisplay the page with a new sort for the question bank. | |
d30c6cdc | 382 | * |
17f229fa RM |
383 | * @param string $sort the column, or column_subsort to sort on. |
384 | * @param bool $newsortreverse whether to sort in reverse order. | |
385 | * @return string The new URL. | |
386 | */ | |
387 | public function new_sort_url($sort, $newsortreverse) { | |
388 | if ($newsortreverse) { | |
389 | $order = -1; | |
390 | } else { | |
391 | $order = 1; | |
392 | } | |
393 | // Tricky code to add the new sort at the start, removing it from where it was before, if it was present. | |
394 | $newsort = array_reverse($this->sort); | |
395 | if (isset($newsort[$sort])) { | |
396 | unset($newsort[$sort]); | |
397 | } | |
398 | $newsort[$sort] = $order; | |
399 | $newsort = array_reverse($newsort); | |
400 | if (count($newsort) > self::MAX_SORTS) { | |
401 | $newsort = array_slice($newsort, 0, self::MAX_SORTS, true); | |
402 | } | |
403 | return $this->baseurl->out(true, $this->sort_to_params($newsort)); | |
404 | } | |
405 | ||
406 | /** | |
407 | * Create the SQL query to retrieve the indicated questions | |
d30c6cdc TH |
408 | * |
409 | * @param \stdClass $category no longer used. | |
17f229fa RM |
410 | * @param bool $recurse no longer used. |
411 | * @param bool $showhidden no longer used. | |
412 | * @deprecated since Moodle 2.7 MDL-40313. | |
413 | * @see build_query() | |
414 | * @see \core_question\bank\search\condition | |
415 | * @todo MDL-41978 This will be deleted in Moodle 2.8 | |
416 | */ | |
417 | protected function build_query_sql($category, $recurse, $showhidden) { | |
418 | debugging('build_query_sql() is deprecated, please use \core_question\bank\view::build_query() and ' . | |
419 | '\core_question\bank\search\condition classes instead.', DEBUG_DEVELOPER); | |
420 | self::build_query(); | |
421 | } | |
422 | ||
423 | /** | |
424 | * Create the SQL query to retrieve the indicated questions, based on | |
425 | * \core_question\bank\search\condition filters. | |
426 | */ | |
427 | protected function build_query() { | |
17f229fa RM |
428 | // Get the required tables and fields. |
429 | $joins = array(); | |
430 | $fields = array('q.hidden', 'q.category'); | |
431 | foreach ($this->requiredcolumns as $column) { | |
432 | $extrajoins = $column->get_extra_joins(); | |
433 | foreach ($extrajoins as $prefix => $join) { | |
434 | if (isset($joins[$prefix]) && $joins[$prefix] != $join) { | |
435 | throw new \coding_exception('Join ' . $join . ' conflicts with previous join ' . $joins[$prefix]); | |
436 | } | |
437 | $joins[$prefix] = $join; | |
438 | } | |
439 | $fields = array_merge($fields, $column->get_required_fields()); | |
440 | } | |
441 | $fields = array_unique($fields); | |
442 | ||
443 | // Build the order by clause. | |
444 | $sorts = array(); | |
445 | foreach ($this->sort as $sort => $order) { | |
446 | list($colname, $subsort) = $this->parse_subsort($sort); | |
447 | $sorts[] = $this->requiredcolumns[$colname]->sort_expression($order < 0, $subsort); | |
448 | } | |
449 | ||
450 | // Build the where clause. | |
451 | $tests = array('q.parent = 0'); | |
452 | $this->sqlparams = array(); | |
453 | foreach ($this->searchconditions as $searchcondition) { | |
454 | if ($searchcondition->where()) { | |
455 | $tests[] = '((' . $searchcondition->where() .'))'; | |
456 | } | |
457 | if ($searchcondition->params()) { | |
458 | $this->sqlparams = array_merge($this->sqlparams, $searchcondition->params()); | |
459 | } | |
460 | } | |
461 | // Build the SQL. | |
462 | $sql = ' FROM {question} q ' . implode(' ', $joins); | |
463 | $sql .= ' WHERE ' . implode(' AND ', $tests); | |
464 | $this->countsql = 'SELECT count(1)' . $sql; | |
465 | $this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts); | |
466 | } | |
467 | ||
468 | protected function get_question_count() { | |
469 | global $DB; | |
470 | return $DB->count_records_sql($this->countsql, $this->sqlparams); | |
471 | } | |
472 | ||
d30c6cdc TH |
473 | /** |
474 | * Load the questions we need to display. | |
475 | * | |
476 | * @param int $page page to display. | |
477 | * @param int $perpage number of questions per page. | |
478 | * @return \moodle_recordset questionid => data about each question. | |
479 | */ | |
17f229fa RM |
480 | protected function load_page_questions($page, $perpage) { |
481 | global $DB; | |
482 | $questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, $page * $perpage, $perpage); | |
61cc1e64 | 483 | if (empty($questions)) { |
a938e409 | 484 | $questions->close(); |
61cc1e64 | 485 | // No questions on this page. Reset to page 0. |
17f229fa RM |
486 | $questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, 0, $perpage); |
487 | } | |
488 | return $questions; | |
489 | } | |
490 | ||
491 | public function base_url() { | |
492 | return $this->baseurl; | |
493 | } | |
494 | ||
701ae1eb TH |
495 | /** |
496 | * Get the URL for editing a question as a {@link \moodle_url}. | |
497 | * | |
498 | * @param int $questionid the question id. | |
499 | * @return \moodle_url the URL, HTML-escaped. | |
500 | */ | |
501 | public function edit_question_moodle_url($questionid) { | |
502 | return new \moodle_url($this->editquestionurl, ['id' => $questionid]); | |
503 | } | |
504 | ||
505 | /** | |
506 | * Get the URL for editing a question as a HTML-escaped string. | |
507 | * | |
508 | * @param int $questionid the question id. | |
509 | * @return string the URL, HTML-escaped. | |
510 | */ | |
17f229fa | 511 | public function edit_question_url($questionid) { |
701ae1eb TH |
512 | return $this->edit_question_moodle_url($questionid)->out(); |
513 | } | |
514 | ||
515 | /** | |
516 | * Get the URL for duplicating a question as a {@link \moodle_url}. | |
517 | * | |
518 | * @param int $questionid the question id. | |
519 | * @return \moodle_url the URL. | |
520 | */ | |
521 | public function copy_question_moodle_url($questionid) { | |
522 | return new \moodle_url($this->editquestionurl, ['id' => $questionid, 'makecopy' => 1]); | |
17f229fa RM |
523 | } |
524 | ||
525 | /** | |
526 | * Get the URL for duplicating a given question. | |
527 | * @param int $questionid the question id. | |
d30c6cdc | 528 | * @return string the URL, HTML-escaped. |
17f229fa RM |
529 | */ |
530 | public function copy_question_url($questionid) { | |
701ae1eb | 531 | return $this->copy_question_moodle_url($questionid)->out(); |
17f229fa RM |
532 | } |
533 | ||
534 | /** | |
535 | * Get the context we are displaying the question bank for. | |
d30c6cdc | 536 | * @return \context context object. |
17f229fa RM |
537 | */ |
538 | public function get_most_specific_context() { | |
539 | return $this->contexts->lowest(); | |
540 | } | |
541 | ||
542 | /** | |
543 | * Get the URL to preview a question. | |
d30c6cdc TH |
544 | * @param \stdClass $questiondata the data defining the question. |
545 | * @return \moodle_url the URL. | |
17f229fa RM |
546 | */ |
547 | public function preview_question_url($questiondata) { | |
548 | return question_preview_url($questiondata->id, null, null, null, null, | |
549 | $this->get_most_specific_context()); | |
550 | } | |
551 | ||
552 | /** | |
553 | * Shows the question bank editing interface. | |
554 | * | |
555 | * The function also processes a number of actions: | |
556 | * | |
557 | * Actions affecting the question pool: | |
558 | * move Moves a question to a different category | |
559 | * deleteselected Deletes the selected questions from the category | |
560 | * Other actions: | |
561 | * category Chooses the category | |
d30c6cdc TH |
562 | * |
563 | * @param string $tabname question bank edit tab name, for permission checking. | |
564 | * @param int $page the page number to show. | |
565 | * @param int $perpage the number of questions per page to show. | |
566 | * @param string $cat 'categoryid,contextid'. | |
567 | * @param int $recurse Whether to include subcategories. | |
568 | * @param bool $showhidden whether deleted questions should be displayed. | |
569 | * @param bool $showquestiontext whether the text of each question should be shown in the list. Deprecated. | |
570 | * @param array $tagids current list of selected tags. | |
17f229fa RM |
571 | */ |
572 | public function display($tabname, $page, $perpage, $cat, | |
fff03332 | 573 | $recurse, $showhidden, $showquestiontext, $tagids = []) { |
2cfd8d16 | 574 | global $PAGE, $CFG; |
17f229fa RM |
575 | |
576 | if ($this->process_actions_needing_ui()) { | |
577 | return; | |
578 | } | |
579 | $editcontexts = $this->contexts->having_one_edit_tab_cap($tabname); | |
d30c6cdc | 580 | list(, $contextid) = explode(',', $cat); |
fff03332 | 581 | $catcontext = \context::instance_by_id($contextid); |
ad54e635 | 582 | $thiscontext = $this->get_most_specific_context(); |
17f229fa | 583 | // Category selection form. |
f33dc783 | 584 | $this->display_question_bank_header(); |
2cfd8d16 SL |
585 | |
586 | // Display tag filter if usetags setting is enabled. | |
587 | if ($CFG->usetags) { | |
588 | array_unshift($this->searchconditions, | |
589 | new \core_question\bank\search\tag_condition([$catcontext, $thiscontext], $tagids)); | |
590 | $PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']); | |
591 | } | |
592 | ||
17f229fa RM |
593 | array_unshift($this->searchconditions, new \core_question\bank\search\hidden_condition(!$showhidden)); |
594 | array_unshift($this->searchconditions, new \core_question\bank\search\category_condition( | |
595 | $cat, $recurse, $editcontexts, $this->baseurl, $this->course)); | |
596 | $this->display_options_form($showquestiontext); | |
597 | ||
598 | // Continues with list of questions. | |
f33dc783 | 599 | $this->display_question_list($editcontexts, |
17f229fa RM |
600 | $this->baseurl, $cat, $this->cm, |
601 | null, $page, $perpage, $showhidden, $showquestiontext, | |
602 | $this->contexts->having_cap('moodle/question:add')); | |
fd5e2ead | 603 | |
17f229fa RM |
604 | } |
605 | ||
606 | protected function print_choose_category_message($categoryandcontext) { | |
607 | echo "<p style=\"text-align:center;\"><b>"; | |
608 | print_string('selectcategoryabove', 'question'); | |
609 | echo "</b></p>"; | |
610 | } | |
611 | ||
612 | protected function get_current_category($categoryandcontext) { | |
613 | global $DB, $OUTPUT; | |
614 | list($categoryid, $contextid) = explode(',', $categoryandcontext); | |
615 | if (!$categoryid) { | |
616 | $this->print_choose_category_message($categoryandcontext); | |
617 | return false; | |
618 | } | |
619 | ||
620 | if (!$category = $DB->get_record('question_categories', | |
621 | array('id' => $categoryid, 'contextid' => $contextid))) { | |
622 | echo $OUTPUT->box_start('generalbox questionbank'); | |
623 | echo $OUTPUT->notification('Category not found!'); | |
624 | echo $OUTPUT->box_end(); | |
625 | return false; | |
626 | } | |
627 | ||
628 | return $category; | |
629 | } | |
630 | ||
631 | /** | |
632 | * prints category information | |
d30c6cdc | 633 | * @param \stdClass $category the category row from the database. |
17f229fa RM |
634 | * @deprecated since Moodle 2.7 MDL-40313. |
635 | * @see \core_question\bank\search\condition | |
636 | * @todo MDL-41978 This will be deleted in Moodle 2.8 | |
637 | */ | |
638 | protected function print_category_info($category) { | |
639 | $formatoptions = new \stdClass(); | |
640 | $formatoptions->noclean = true; | |
641 | $formatoptions->overflowdiv = true; | |
642 | echo '<div class="boxaligncenter">'; | |
643 | echo format_text($category->info, $category->infoformat, $formatoptions, $this->course->id); | |
644 | echo "</div>\n"; | |
645 | } | |
646 | ||
647 | /** | |
648 | * Prints a form to choose categories | |
649 | * @deprecated since Moodle 2.7 MDL-40313. | |
650 | * @see \core_question\bank\search\condition | |
651 | * @todo MDL-41978 This will be deleted in Moodle 2.8 | |
652 | */ | |
653 | protected function display_category_form($contexts, $pageurl, $current) { | |
654 | global $OUTPUT; | |
655 | ||
656 | debugging('display_category_form() is deprecated, please use ' . | |
657 | '\core_question\bank\search\condition instead.', DEBUG_DEVELOPER); | |
658 | // Get all the existing categories now. | |
659 | echo '<div class="choosecategory">'; | |
660 | $catmenu = question_category_options($contexts, false, 0, true); | |
661 | ||
662 | $select = new \single_select($this->baseurl, 'category', $catmenu, $current, null, 'catmenu'); | |
663 | $select->set_label(get_string('selectacategory', 'question')); | |
664 | echo $OUTPUT->render($select); | |
665 | echo "</div>\n"; | |
666 | } | |
667 | ||
668 | /** | |
669 | * Display the options form. | |
670 | * @param bool $recurse no longer used. | |
671 | * @param bool $showhidden no longer used. | |
672 | * @param bool $showquestiontext whether to show the question text. | |
673 | * @deprecated since Moodle 2.7 MDL-40313. | |
674 | * @see display_options_form | |
675 | * @todo MDL-41978 This will be deleted in Moodle 2.8 | |
676 | * @see \core_question\bank\search\condition | |
677 | */ | |
678 | protected function display_options($recurse, $showhidden, $showquestiontext) { | |
679 | debugging('display_options() is deprecated, please use display_options_form instead.', DEBUG_DEVELOPER); | |
d30c6cdc | 680 | $this->display_options_form($showquestiontext); |
17f229fa RM |
681 | } |
682 | ||
683 | /** | |
684 | * Print a single option checkbox. | |
685 | * @deprecated since Moodle 2.7 MDL-40313. | |
686 | * @see \core_question\bank\search\condition | |
687 | * @see html_writer::checkbox | |
688 | * @todo MDL-41978 This will be deleted in Moodle 2.8 | |
689 | */ | |
690 | protected function display_category_form_checkbox($name, $value, $label) { | |
691 | debugging('display_category_form_checkbox() is deprecated, ' . | |
692 | 'please use \core_question\bank\search\condition instead.', DEBUG_DEVELOPER); | |
693 | echo '<div><input type="hidden" id="' . $name . '_off" name="' . $name . '" value="0" />'; | |
694 | echo '<input type="checkbox" id="' . $name . '_on" name="' . $name . '" value="1"'; | |
695 | if ($value) { | |
696 | echo ' checked="checked"'; | |
697 | } | |
698 | echo ' onchange="getElementById(\'displayoptions\').submit(); return true;" />'; | |
699 | echo '<label for="' . $name . '_on">' . $label . '</label>'; | |
700 | echo "</div>\n"; | |
701 | } | |
702 | ||
703 | /** | |
704 | * Display the form with options for which questions are displayed and how they are displayed. | |
705 | * @param bool $showquestiontext Display the text of the question within the list. | |
d30c6cdc | 706 | * @param string $scriptpath path to the script displaying this page. |
84deaaf8 | 707 | * @param bool $showtextoption whether to include the 'Show question text' checkbox. |
17f229fa | 708 | */ |
84deaaf8 TH |
709 | protected function display_options_form($showquestiontext, $scriptpath = '/question/edit.php', |
710 | $showtextoption = true) { | |
17f229fa RM |
711 | global $PAGE; |
712 | ||
84deaaf8 TH |
713 | echo \html_writer::start_tag('form', array('method' => 'get', |
714 | 'action' => new \moodle_url($scriptpath), 'id' => 'displayoptions')); | |
715 | echo \html_writer::start_div(); | |
fff03332 RW |
716 | |
717 | $excludes = array('recurse', 'showhidden', 'qbshowtext'); | |
718 | // If the URL contains any tags then we need to prevent them | |
719 | // being added to the form as hidden elements because the tags | |
720 | // are managed separately. | |
721 | if ($this->baseurl->param('qtagids[0]')) { | |
722 | $index = 0; | |
723 | while ($this->baseurl->param("qtagids[{$index}]")) { | |
724 | $excludes[] = "qtagids[{$index}]"; | |
725 | $index++; | |
726 | } | |
727 | } | |
728 | echo \html_writer::input_hidden_params($this->baseurl, $excludes); | |
17f229fa RM |
729 | |
730 | foreach ($this->searchconditions as $searchcondition) { | |
d30c6cdc | 731 | echo $searchcondition->display_options(); |
17f229fa | 732 | } |
84deaaf8 TH |
733 | if ($showtextoption) { |
734 | $this->display_showtext_checkbox($showquestiontext); | |
735 | } | |
17f229fa | 736 | $this->display_advanced_search_form(); |
84deaaf8 TH |
737 | $go = \html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('go'))); |
738 | echo \html_writer::tag('noscript', \html_writer::div($go), array('class' => 'inline')); | |
739 | echo \html_writer::end_div(); | |
740 | echo \html_writer::end_tag('form'); | |
17f229fa | 741 | $PAGE->requires->yui_module('moodle-question-searchform', 'M.question.searchform.init'); |
17f229fa RM |
742 | } |
743 | ||
744 | /** | |
745 | * Print the "advanced" UI elements for the form to select which questions. Hidden by default. | |
746 | */ | |
747 | protected function display_advanced_search_form() { | |
748 | print_collapsible_region_start('', 'advancedsearch', get_string('advancedsearchoptions', 'question'), | |
749 | 'question_bank_advanced_search'); | |
750 | foreach ($this->searchconditions as $searchcondition) { | |
d30c6cdc | 751 | echo $searchcondition->display_options_adv(); |
17f229fa RM |
752 | } |
753 | print_collapsible_region_end(); | |
754 | } | |
755 | ||
756 | /** | |
757 | * Display the checkbox UI for toggling the display of the question text in the list. | |
758 | * @param bool $showquestiontext the current or default value for whether to display the text. | |
759 | */ | |
760 | protected function display_showtext_checkbox($showquestiontext) { | |
761 | echo '<div>'; | |
762 | echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'qbshowtext', | |
763 | 'value' => 0, 'id' => 'qbshowtext_off')); | |
f68616d7 LB |
764 | echo \html_writer::checkbox('qbshowtext', '1', $showquestiontext, ' ' . get_string('showquestiontext', 'question'), |
765 | array('id' => 'qbshowtext_on', 'class' => 'searchoptions mr-1')); | |
17f229fa RM |
766 | echo "</div>\n"; |
767 | } | |
768 | ||
f33dc783 RW |
769 | /** |
770 | * Display the header element for the question bank. | |
771 | */ | |
772 | protected function display_question_bank_header() { | |
773 | global $OUTPUT; | |
774 | echo $OUTPUT->heading(get_string('questionbank', 'question'), 2); | |
775 | } | |
776 | ||
17f229fa | 777 | protected function create_new_question_form($category, $canadd) { |
17f229fa RM |
778 | echo '<div class="createnewquestion">'; |
779 | if ($canadd) { | |
780 | create_new_question_button($category->id, $this->editquestionurl->params(), | |
781 | get_string('createnewquestion', 'question')); | |
782 | } else { | |
783 | print_string('nopermissionadd', 'question'); | |
784 | } | |
785 | echo '</div>'; | |
786 | } | |
787 | ||
788 | /** | |
789 | * Prints the table of questions in a category with interactions | |
790 | * | |
791 | * @param array $contexts Not used! | |
d30c6cdc | 792 | * @param \moodle_url $pageurl The URL to reload this page. |
17f229fa | 793 | * @param string $categoryandcontext 'categoryID,contextID'. |
d30c6cdc TH |
794 | * @param \stdClass $cm Not used! |
795 | * @param int $recurse Whether to include subcategories. | |
17f229fa RM |
796 | * @param int $page The number of the page to be displayed |
797 | * @param int $perpage Number of questions to show per page | |
d30c6cdc TH |
798 | * @param bool $showhidden Not used! This is now controlled in a different way. |
799 | * @param bool $showquestiontext Not used! This is now controlled in a different way. | |
17f229fa RM |
800 | * @param array $addcontexts contexts where the user is allowed to add new questions. |
801 | */ | |
802 | protected function display_question_list($contexts, $pageurl, $categoryandcontext, | |
803 | $cm = null, $recurse=1, $page=0, $perpage=100, $showhidden=false, | |
804 | $showquestiontext = false, $addcontexts = array()) { | |
d30c6cdc | 805 | global $OUTPUT; |
17f229fa | 806 | |
c7092fe4 EM |
807 | // This function can be moderately slow with large question counts and may time out. |
808 | // We probably do not want to raise it to unlimited, so randomly picking 5 minutes. | |
809 | // Note: We do not call this in the loop because quiz ob_ captures this function (see raise() PHP doc). | |
810 | \core_php_time_limit::raise(300); | |
811 | ||
17f229fa RM |
812 | $category = $this->get_current_category($categoryandcontext); |
813 | ||
17f229fa RM |
814 | list($categoryid, $contextid) = explode(',', $categoryandcontext); |
815 | $catcontext = \context::instance_by_id($contextid); | |
816 | ||
817 | $canadd = has_capability('moodle/question:add', $catcontext); | |
17f229fa RM |
818 | |
819 | $this->create_new_question_form($category, $canadd); | |
820 | ||
821 | $this->build_query(); | |
822 | $totalnumber = $this->get_question_count(); | |
823 | if ($totalnumber == 0) { | |
824 | return; | |
825 | } | |
61cc1e64 TH |
826 | $questionsrs = $this->load_page_questions($page, $perpage); |
827 | $questions = []; | |
828 | foreach ($questionsrs as $question) { | |
829 | $questions[$question->id] = $question; | |
830 | } | |
831 | $questionsrs->close(); | |
832 | foreach ($this->requiredcolumns as $name => $column) { | |
833 | $column->load_additional_data($questions); | |
834 | } | |
17f229fa RM |
835 | |
836 | echo '<div class="categorypagingbarcontainer">'; | |
d30c6cdc | 837 | $pageingurl = new \moodle_url('edit.php', $pageurl->params()); |
17f229fa RM |
838 | $pagingbar = new \paging_bar($totalnumber, $page, $perpage, $pageingurl); |
839 | $pagingbar->pagevar = 'qpage'; | |
840 | echo $OUTPUT->render($pagingbar); | |
841 | echo '</div>'; | |
842 | ||
843 | echo '<form method="post" action="edit.php">'; | |
844 | echo '<fieldset class="invisiblefieldset" style="display: block;">'; | |
845 | echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />'; | |
846 | echo \html_writer::input_hidden_params($this->baseurl); | |
847 | ||
fd5e2ead | 848 | echo '<div class="categoryquestionscontainer" id="questionscontainer">'; |
17f229fa RM |
849 | $this->start_table(); |
850 | $rowcount = 0; | |
851 | foreach ($questions as $question) { | |
852 | $this->print_table_row($question, $rowcount); | |
853 | $rowcount += 1; | |
854 | } | |
855 | $this->end_table(); | |
856 | echo "</div>\n"; | |
857 | ||
858 | echo '<div class="categorypagingbarcontainer pagingbottom">'; | |
859 | echo $OUTPUT->render($pagingbar); | |
860 | if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) { | |
861 | if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) { | |
a8524f33 EM |
862 | $url = new \moodle_url('edit.php', array_merge($pageurl->params(), |
863 | array('qperpage' => MAXIMUM_QUESTIONS_PER_PAGE))); | |
864 | if ($totalnumber > MAXIMUM_QUESTIONS_PER_PAGE) { | |
865 | $showall = '<a href="'.$url.'">'.get_string('showperpage', 'moodle', MAXIMUM_QUESTIONS_PER_PAGE).'</a>'; | |
866 | } else { | |
867 | $showall = '<a href="'.$url.'">'.get_string('showall', 'moodle', $totalnumber).'</a>'; | |
868 | } | |
17f229fa RM |
869 | } else { |
870 | $url = new \moodle_url('edit.php', array_merge($pageurl->params(), | |
871 | array('qperpage' => DEFAULT_QUESTIONS_PER_PAGE))); | |
872 | $showall = '<a href="'.$url.'">'.get_string('showperpage', 'moodle', DEFAULT_QUESTIONS_PER_PAGE).'</a>'; | |
873 | } | |
f4fe3968 | 874 | echo "<div class='paging'>{$showall}</div>"; |
17f229fa RM |
875 | } |
876 | echo '</div>'; | |
877 | ||
e1a2d0d9 CC |
878 | $this->display_bottom_controls($totalnumber, $recurse, $category, $catcontext, $addcontexts); |
879 | ||
880 | echo '</fieldset>'; | |
881 | echo "</form>\n"; | |
882 | } | |
883 | ||
884 | /** | |
885 | * Display the controls at the bottom of the list of questions. | |
886 | * @param int $totalnumber Total number of questions that might be shown (if it was not for paging). | |
887 | * @param bool $recurse Whether to include subcategories. | |
d30c6cdc TH |
888 | * @param \stdClass $category The question_category row from the database. |
889 | * @param \context $catcontext The context of the category being displayed. | |
e1a2d0d9 CC |
890 | * @param array $addcontexts contexts where the user is allowed to add new questions. |
891 | */ | |
892 | protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts) { | |
893 | $caneditall = has_capability('moodle/question:editall', $catcontext); | |
894 | $canuseall = has_capability('moodle/question:useall', $catcontext); | |
895 | $canmoveall = has_capability('moodle/question:moveall', $catcontext); | |
896 | ||
17f229fa RM |
897 | echo '<div class="modulespecificbuttonscontainer">'; |
898 | if ($caneditall || $canmoveall || $canuseall) { | |
899 | echo '<strong> '.get_string('withselected', 'question').':</strong><br />'; | |
900 | ||
17f229fa RM |
901 | // Print delete and move selected question. |
902 | if ($caneditall) { | |
cbd8641a JP |
903 | echo \html_writer::empty_tag('input', [ |
904 | 'type' => 'submit', | |
905 | 'class' => 'btn btn-secondary mr-1', | |
906 | 'name' => 'deleteselected', | |
907 | 'value' => get_string('delete'), | |
908 | 'data-action' => 'toggle', | |
909 | 'data-togglegroup' => 'qbank', | |
910 | 'data-toggle' => 'action', | |
911 | 'disabled' => true, | |
912 | ]); | |
17f229fa RM |
913 | } |
914 | ||
915 | if ($canmoveall && count($addcontexts)) { | |
cbd8641a JP |
916 | echo \html_writer::empty_tag('input', [ |
917 | 'type' => 'submit', | |
918 | 'class' => 'btn btn-secondary mr-1', | |
919 | 'name' => 'move', | |
920 | 'value' => get_string('moveto', 'question'), | |
921 | 'data-action' => 'toggle', | |
922 | 'data-togglegroup' => 'qbank', | |
923 | 'data-toggle' => 'action', | |
924 | 'disabled' => true, | |
925 | ]); | |
f4fe3968 | 926 | question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}"); |
17f229fa | 927 | } |
17f229fa RM |
928 | } |
929 | echo "</div>\n"; | |
17f229fa RM |
930 | } |
931 | ||
932 | protected function start_table() { | |
933 | echo '<table id="categoryquestions">' . "\n"; | |
934 | echo "<thead>\n"; | |
935 | $this->print_table_headers(); | |
936 | echo "</thead>\n"; | |
937 | echo "<tbody>\n"; | |
938 | } | |
939 | ||
940 | protected function end_table() { | |
941 | echo "</tbody>\n"; | |
942 | echo "</table>\n"; | |
943 | } | |
944 | ||
945 | protected function print_table_headers() { | |
946 | echo "<tr>\n"; | |
947 | foreach ($this->visiblecolumns as $column) { | |
948 | $column->display_header(); | |
949 | } | |
950 | echo "</tr>\n"; | |
951 | } | |
952 | ||
953 | protected function get_row_classes($question, $rowcount) { | |
954 | $classes = array(); | |
955 | if ($question->hidden) { | |
956 | $classes[] = 'dimmed_text'; | |
957 | } | |
958 | if ($question->id == $this->lastchangedid) { | |
aa100b76 | 959 | $classes[] = 'highlight text-dark'; |
17f229fa RM |
960 | } |
961 | $classes[] = 'r' . ($rowcount % 2); | |
962 | return $classes; | |
963 | } | |
964 | ||
965 | protected function print_table_row($question, $rowcount) { | |
966 | $rowclasses = implode(' ', $this->get_row_classes($question, $rowcount)); | |
967 | if ($rowclasses) { | |
968 | echo '<tr class="' . $rowclasses . '">' . "\n"; | |
969 | } else { | |
970 | echo "<tr>\n"; | |
971 | } | |
972 | foreach ($this->visiblecolumns as $column) { | |
973 | $column->display($question, $rowclasses); | |
974 | } | |
975 | echo "</tr>\n"; | |
976 | foreach ($this->extrarows as $row) { | |
977 | $row->display($question, $rowclasses); | |
978 | } | |
979 | } | |
980 | ||
981 | public function process_actions() { | |
d30c6cdc | 982 | global $DB; |
17f229fa RM |
983 | // Now, check for commands on this page and modify variables as necessary. |
984 | if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) { | |
985 | // Move selected questions to new category. | |
986 | $category = required_param('category', PARAM_SEQUENCE); | |
987 | list($tocategoryid, $contextid) = explode(',', $category); | |
988 | if (! $tocategory = $DB->get_record('question_categories', array('id' => $tocategoryid, 'contextid' => $contextid))) { | |
989 | print_error('cannotfindcate', 'question'); | |
990 | } | |
991 | $tocontext = \context::instance_by_id($contextid); | |
992 | require_capability('moodle/question:add', $tocontext); | |
993 | $rawdata = (array) data_submitted(); | |
994 | $questionids = array(); | |
995 | foreach ($rawdata as $key => $value) { // Parse input for question ids. | |
996 | if (preg_match('!^q([0-9]+)$!', $key, $matches)) { | |
997 | $key = $matches[1]; | |
998 | $questionids[] = $key; | |
999 | } | |
1000 | } | |
1001 | if ($questionids) { | |
1002 | list($usql, $params) = $DB->get_in_or_equal($questionids); | |
17f229fa RM |
1003 | $questions = $DB->get_records_sql(" |
1004 | SELECT q.*, c.contextid | |
1005 | FROM {question} q | |
1006 | JOIN {question_categories} c ON c.id = q.category | |
f4fe3968 | 1007 | WHERE q.id {$usql}", $params); |
17f229fa RM |
1008 | foreach ($questions as $question) { |
1009 | question_require_capability_on($question, 'move'); | |
1010 | } | |
1011 | question_move_questions_to_category($questionids, $tocategory->id); | |
1012 | redirect($this->baseurl->out(false, | |
f4fe3968 | 1013 | array('category' => "{$tocategoryid},{$contextid}"))); |
17f229fa RM |
1014 | } |
1015 | } | |
1016 | ||
1017 | if (optional_param('deleteselected', false, PARAM_BOOL)) { // Delete selected questions from the category. | |
1018 | // If teacher has already confirmed the action. | |
1019 | if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) { | |
1020 | $deleteselected = required_param('deleteselected', PARAM_RAW); | |
1021 | if ($confirm == md5($deleteselected)) { | |
1022 | if ($questionlist = explode(',', $deleteselected)) { | |
1023 | // For each question either hide it if it is in use or delete it. | |
1024 | foreach ($questionlist as $questionid) { | |
1025 | $questionid = (int)$questionid; | |
1026 | question_require_capability_on($questionid, 'edit'); | |
1027 | if (questions_in_use(array($questionid))) { | |
1028 | $DB->set_field('question', 'hidden', 1, array('id' => $questionid)); | |
1029 | } else { | |
1030 | question_delete_question($questionid); | |
1031 | } | |
1032 | } | |
1033 | } | |
1034 | redirect($this->baseurl); | |
1035 | } else { | |
1036 | print_error('invalidconfirm', 'question'); | |
1037 | } | |
1038 | } | |
1039 | } | |
1040 | ||
1041 | // Unhide a question. | |
1042 | if (($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) { | |
1043 | question_require_capability_on($unhide, 'edit'); | |
1044 | $DB->set_field('question', 'hidden', 0, array('id' => $unhide)); | |
1045 | ||
1046 | // Purge these questions from the cache. | |
b25e15b2 | 1047 | \question_bank::notify_question_edited($unhide); |
17f229fa RM |
1048 | |
1049 | redirect($this->baseurl); | |
1050 | } | |
1051 | } | |
1052 | ||
1053 | public function process_actions_needing_ui() { | |
1054 | global $DB, $OUTPUT; | |
1055 | if (optional_param('deleteselected', false, PARAM_BOOL)) { | |
1056 | // Make a list of all the questions that are selected. | |
1057 | $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted. | |
1058 | $questionlist = ''; // comma separated list of ids of questions to be deleted | |
1059 | $questionnames = ''; // string with names of questions separated by <br /> with | |
1060 | // an asterix in front of those that are in use | |
1061 | $inuse = false; // set to true if at least one of the questions is in use | |
1062 | foreach ($rawquestions as $key => $value) { // Parse input for question ids. | |
1063 | if (preg_match('!^q([0-9]+)$!', $key, $matches)) { | |
1064 | $key = $matches[1]; | |
1065 | $questionlist .= $key.','; | |
2cf7bde8 | 1066 | question_require_capability_on((int)$key, 'edit'); |
17f229fa RM |
1067 | if (questions_in_use(array($key))) { |
1068 | $questionnames .= '* '; | |
1069 | $inuse = true; | |
1070 | } | |
1071 | $questionnames .= $DB->get_field('question', 'name', array('id' => $key)) . '<br />'; | |
1072 | } | |
1073 | } | |
1074 | if (!$questionlist) { // No questions were selected. | |
1075 | redirect($this->baseurl); | |
1076 | } | |
1077 | $questionlist = rtrim($questionlist, ','); | |
1078 | ||
1079 | // Add an explanation about questions in use. | |
1080 | if ($inuse) { | |
1081 | $questionnames .= '<br />'.get_string('questionsinuse', 'question'); | |
1082 | } | |
1083 | $baseurl = new \moodle_url('edit.php', $this->baseurl->params()); | |
1084 | $deleteurl = new \moodle_url($baseurl, array('deleteselected' => $questionlist, 'confirm' => md5($questionlist), | |
1085 | 'sesskey' => sesskey())); | |
1086 | ||
65377780 AD |
1087 | $continue = new \single_button($deleteurl, get_string('delete'), 'post'); |
1088 | echo $OUTPUT->confirm(get_string('deletequestionscheck', 'question', $questionnames), $continue, $baseurl); | |
17f229fa RM |
1089 | |
1090 | return true; | |
1091 | } | |
d30c6cdc TH |
1092 | |
1093 | return false; | |
17f229fa RM |
1094 | } |
1095 | ||
1096 | /** | |
1097 | * Add another search control to this view. | |
d30c6cdc | 1098 | * @param condition $searchcondition the condition to add. |
17f229fa RM |
1099 | */ |
1100 | public function add_searchcondition($searchcondition) { | |
1101 | $this->searchconditions[] = $searchcondition; | |
1102 | } | |
1103 | } |