mirror of
https://github.com/moodle/moodle.git
synced 2025-03-07 01:10:02 +01:00
This implementation will introduct changes to the mod_quiz to implement the new qbank api for view. Major changes are introduced in the custom view and also all the legacy points to the old qbank are changed to qbank plugins where needed. It also deprecated and deletes classes and scripts which were kept not to break mod_quiz for the new api implementation.
1305 lines
48 KiB
PHP
1305 lines
48 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Class to print a view of the question bank.
|
|
*
|
|
* @package core_question
|
|
* @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
namespace core_question\local\bank;
|
|
|
|
use core_question\bank\search\condition;
|
|
use qbank_editquestion\editquestion_helper;
|
|
use qbank_managecategories\helper;
|
|
|
|
/**
|
|
* This class prints a view of the question bank.
|
|
*
|
|
* including
|
|
* + Some controls to allow users to to select what is displayed.
|
|
* + A list of questions as a table.
|
|
* + Further controls to do things with the questions.
|
|
*
|
|
* This class gives a basic view, and provides plenty of hooks where subclasses
|
|
* can override parts of the display.
|
|
*
|
|
* The list of questions presented as a table is generated by creating a list of
|
|
* core_question\bank\column objects, one for each 'column' to be displayed. These
|
|
* manage
|
|
* + outputting the contents of that column, given a $question object, but also
|
|
* + generating the right fragments of SQL to ensure the necessary data is present,
|
|
* and sorted in the right order.
|
|
* + outputting table headers.
|
|
*
|
|
* @copyright 2009 Tim Hunt
|
|
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class view {
|
|
|
|
/**
|
|
* Maximum number of sorts allowed.
|
|
*/
|
|
const MAX_SORTS = 3;
|
|
|
|
/**
|
|
* @var \moodle_url base URL for the current page. Used as the
|
|
* basis for making URLs for actions that reload the page.
|
|
*/
|
|
protected $baseurl;
|
|
|
|
/**
|
|
* @var \moodle_url used as a basis for URLs that edit a question.
|
|
*/
|
|
protected $editquestionurl;
|
|
|
|
/**
|
|
* @var \question_edit_contexts
|
|
*/
|
|
protected $contexts;
|
|
|
|
/**
|
|
* @var object|\cm_info|null if we are in a module context, the cm.
|
|
*/
|
|
public $cm;
|
|
|
|
/**
|
|
* @var object the course we are within.
|
|
*/
|
|
public $course;
|
|
|
|
/**
|
|
* @var \question_bank_column_base[] these are all the 'columns' that are
|
|
* part of the display. Array keys are the class name.
|
|
*/
|
|
protected $requiredcolumns;
|
|
|
|
/**
|
|
* @var \question_bank_column_base[] these are the 'columns' that are
|
|
* actually displayed as a column, in order. Array keys are the class name.
|
|
*/
|
|
protected $visiblecolumns;
|
|
|
|
/**
|
|
* @var \question_bank_column_base[] these are the 'columns' that are
|
|
* actually displayed as an additional row (e.g. question text), in order.
|
|
* Array keys are the class name.
|
|
*/
|
|
protected $extrarows;
|
|
|
|
/**
|
|
* @var array list of column class names for which columns to sort on.
|
|
*/
|
|
protected $sort;
|
|
|
|
/**
|
|
* @var int|null id of the a question to highlight in the list (if present).
|
|
*/
|
|
protected $lastchangedid;
|
|
|
|
/**
|
|
* @var string SQL to count the number of questions matching the current
|
|
* search conditions.
|
|
*/
|
|
protected $countsql;
|
|
|
|
/**
|
|
* @var string SQL to actually load the question data to display.
|
|
*/
|
|
protected $loadsql;
|
|
|
|
/**
|
|
* @var array params used by $countsql and $loadsql (which currently must be the same).
|
|
*/
|
|
protected $sqlparams;
|
|
|
|
/**
|
|
* @var condition[] search conditions.
|
|
*/
|
|
protected $searchconditions = [];
|
|
|
|
/**
|
|
* @var string url of the new question page.
|
|
*/
|
|
public $returnurl;
|
|
|
|
/**
|
|
* @var bool enable or disable filters while calling the API.
|
|
*/
|
|
public $enablefilters = true;
|
|
|
|
/**
|
|
* @var array to pass custom filters instead of the specified ones.
|
|
*/
|
|
public $customfilterobjects = null;
|
|
|
|
/**
|
|
* Constructor for view.
|
|
*
|
|
* @param \question_edit_contexts $contexts
|
|
* @param \moodle_url $pageurl
|
|
* @param object $course course settings
|
|
* @param object $cm (optional) activity settings.
|
|
*/
|
|
public function __construct($contexts, $pageurl, $course, $cm = null) {
|
|
$this->contexts = $contexts;
|
|
$this->baseurl = $pageurl;
|
|
$this->course = $course;
|
|
$this->cm = $cm;
|
|
|
|
// Create the url of the new question page to forward to.
|
|
$this->returnurl = $pageurl->out_as_local_url(false);
|
|
$this->editquestionurl = new \moodle_url('/question/bank/editquestion/question.php', ['returnurl' => $this->returnurl]);
|
|
if ($this->cm !== null) {
|
|
$this->editquestionurl->param('cmid', $this->cm->id);
|
|
} else {
|
|
$this->editquestionurl->param('courseid', $this->course->id);
|
|
}
|
|
|
|
$this->lastchangedid = optional_param('lastchanged', 0, PARAM_INT);
|
|
|
|
// Possibly the heading part can be removed.
|
|
$this->init_columns($this->wanted_columns(), $this->heading_column());
|
|
$this->init_sort();
|
|
$this->init_search_conditions();
|
|
}
|
|
|
|
/**
|
|
* Initialize search conditions from plugins
|
|
* local_*_get_question_bank_search_conditions() must return an array of
|
|
* \core_question\bank\search\condition objects.
|
|
*/
|
|
protected function init_search_conditions(): void {
|
|
$searchplugins = get_plugin_list_with_function('local', 'get_question_bank_search_conditions');
|
|
foreach ($searchplugins as $component => $function) {
|
|
foreach ($function($this) as $searchobject) {
|
|
$this->add_searchcondition($searchobject);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the list of qbank plugins with available objects for features.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function get_question_bank_plugins(): array {
|
|
$questionbankclasscolumns = [];
|
|
$newpluginclasscolumns = [];
|
|
$corequestionbankcolumns = [
|
|
'checkbox_column',
|
|
'question_type_column',
|
|
'question_name_idnumber_tags_column',
|
|
'edit_menu_column',
|
|
'edit_action_column',
|
|
'copy_action_column',
|
|
'tags_action_column',
|
|
'preview_action_column',
|
|
'delete_action_column',
|
|
'export_xml_action_column',
|
|
'creator_name_column',
|
|
'modifier_name_column'
|
|
];
|
|
if (question_get_display_preference('qbshowtext', 0, PARAM_BOOL, new \moodle_url(''))) {
|
|
$corequestionbankcolumns[] = 'question_text_row';
|
|
}
|
|
|
|
foreach ($corequestionbankcolumns as $fullname) {
|
|
$shortname = $fullname;
|
|
if (class_exists('core_question\\local\\bank\\' . $fullname)) {
|
|
$fullname = 'core_question\\local\\bank\\' . $fullname;
|
|
$questionbankclasscolumns[$shortname] = new $fullname($this);
|
|
} else {
|
|
$questionbankclasscolumns[$shortname] = '';
|
|
}
|
|
}
|
|
$plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php');
|
|
foreach ($plugins as $componentname => $plugin) {
|
|
$pluginentrypointobject = new $plugin();
|
|
$pluginobjects = $pluginentrypointobject->get_question_columns($this);
|
|
// Don't need the plugins without column objects.
|
|
if (empty($pluginobjects)) {
|
|
unset($plugins[$componentname]);
|
|
continue;
|
|
}
|
|
foreach ($pluginobjects as $pluginobject) {
|
|
$classname = new \ReflectionClass(get_class($pluginobject));
|
|
foreach ($corequestionbankcolumns as $key => $corequestionbankcolumn) {
|
|
if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) {
|
|
unset($questionbankclasscolumns[$classname->getShortName()]);
|
|
continue;
|
|
}
|
|
// Check if it has custom preference selector to view/hide.
|
|
if ($pluginobject->has_preference()) {
|
|
if (!$pluginobject->get_preference()) {
|
|
continue;
|
|
}
|
|
}
|
|
if ($corequestionbankcolumn === $classname->getShortName()) {
|
|
$questionbankclasscolumns[$classname->getShortName()] = $pluginobject;
|
|
} else {
|
|
// Any community plugin for column/action.
|
|
$newpluginclasscolumns[$classname->getShortName()] = $pluginobject;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// New plugins added at the end of the array, will change in sorting feature.
|
|
foreach ($newpluginclasscolumns as $key => $newpluginclasscolumn) {
|
|
$questionbankclasscolumns[$key] = $newpluginclasscolumn;
|
|
}
|
|
|
|
// Mitigate the error in case of any regression.
|
|
foreach ($questionbankclasscolumns as $shortname => $questionbankclasscolumn) {
|
|
if (empty($questionbankclasscolumn)){
|
|
unset($questionbankclasscolumns[$shortname]);
|
|
}
|
|
}
|
|
|
|
return $questionbankclasscolumns;
|
|
}
|
|
|
|
/**
|
|
* Loads all the available columns.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function wanted_columns(): array {
|
|
$this->requiredcolumns = [];
|
|
$questionbankcolumns = $this->get_question_bank_plugins();
|
|
foreach ($questionbankcolumns as $classobject) {
|
|
if (empty($classobject)) {
|
|
continue;
|
|
}
|
|
$this->requiredcolumns[get_class($classobject)] = $classobject;
|
|
}
|
|
|
|
return $this->requiredcolumns;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check a column object from its name and get the object for sort.
|
|
*
|
|
* @param string $columnname
|
|
*/
|
|
protected function get_column_type($columnname) {
|
|
if (empty($this->requiredcolumns[$columnname])) {
|
|
$this->requiredcolumns[$columnname] = new $columnname($this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specify the column heading
|
|
*
|
|
* @return string Column name for the heading
|
|
*/
|
|
protected function heading_column(): string {
|
|
return 'qbank_viewquestionname\viewquestionname_column_helper';
|
|
}
|
|
|
|
/**
|
|
* Initializing table columns
|
|
*
|
|
* @param array $wanted Collection of column names
|
|
* @param string $heading The name of column that is set as heading
|
|
*/
|
|
protected function init_columns($wanted, $heading = ''): void {
|
|
// If we are using the edit menu column, allow it to absorb all the actions.
|
|
foreach ($wanted as $column) {
|
|
if ($column instanceof edit_menu_column) {
|
|
$wanted = $column->claim_menuable_columns($wanted);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now split columns into real columns and rows.
|
|
$this->visiblecolumns = [];
|
|
$this->extrarows = [];
|
|
foreach ($wanted as $column) {
|
|
if ($column->is_extra_row()) {
|
|
$this->extrarows[get_class($column)] = $column;
|
|
} else {
|
|
$this->visiblecolumns[get_class($column)] = $column;
|
|
}
|
|
}
|
|
|
|
if (array_key_exists($heading, $this->requiredcolumns)) {
|
|
$this->requiredcolumns[$heading]->set_as_heading();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the column included in the output.
|
|
*
|
|
* @param string $colname a column internal name.
|
|
* @return bool is this column included in the output?
|
|
*/
|
|
public function has_column($colname): bool {
|
|
return isset($this->visiblecolumns[$colname]);
|
|
}
|
|
|
|
/**
|
|
* Get the count of the columns.
|
|
*
|
|
* @return int The number of columns in the table.
|
|
*/
|
|
public function get_column_count(): int {
|
|
return count($this->visiblecolumns);
|
|
}
|
|
|
|
/**
|
|
* Get course id.
|
|
* @return mixed
|
|
*/
|
|
public function get_courseid() {
|
|
return $this->course->id;
|
|
}
|
|
|
|
/**
|
|
* Initialise sorting.
|
|
*/
|
|
protected function init_sort(): void {
|
|
$this->init_sort_from_params();
|
|
if (empty($this->sort)) {
|
|
$this->sort = $this->default_sort();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deal with a sort name of the form columnname, or colname_subsort by
|
|
* breaking it up, validating the bits that are present, and returning them.
|
|
* If there is no subsort, then $subsort is returned as ''.
|
|
*
|
|
* @param string $sort the sort parameter to process.
|
|
* @return array [$colname, $subsort].
|
|
*/
|
|
protected function parse_subsort($sort): array {
|
|
// Do the parsing.
|
|
if (strpos($sort, '-') !== false) {
|
|
list($colname, $subsort) = explode('-', $sort, 2);
|
|
} else {
|
|
$colname = $sort;
|
|
$subsort = '';
|
|
}
|
|
// Validate the column name.
|
|
$this->get_column_type($colname);
|
|
$column = $this->requiredcolumns[$colname];
|
|
if (!isset($column) || !$column->is_sortable()) {
|
|
for ($i = 1; $i <= self::MAX_SORTS; $i++) {
|
|
$this->baseurl->remove_params('qbs' . $i);
|
|
}
|
|
throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $colname);
|
|
}
|
|
// Validate the subsort, if present.
|
|
if ($subsort) {
|
|
$subsorts = $column->is_sortable();
|
|
if (!is_array($subsorts) || !isset($subsorts[$subsort])) {
|
|
throw new \moodle_exception('unknownsortcolumn', '', $link = $this->baseurl->out(), $sort);
|
|
}
|
|
}
|
|
return [$colname, $subsort];
|
|
}
|
|
|
|
/**
|
|
* Initialise sort from parameters.
|
|
*/
|
|
protected function init_sort_from_params(): void {
|
|
$this->sort = [];
|
|
for ($i = 1; $i <= self::MAX_SORTS; $i++) {
|
|
if (!$sort = optional_param('qbs' . $i, '', PARAM_TEXT)) {
|
|
break;
|
|
}
|
|
// Work out the appropriate order.
|
|
$order = 1;
|
|
if ($sort[0] == '-') {
|
|
$order = -1;
|
|
$sort = substr($sort, 1);
|
|
if (!$sort) {
|
|
break;
|
|
}
|
|
}
|
|
// Deal with subsorts.
|
|
list($colname) = $this->parse_subsort($sort);
|
|
$this->get_column_type($colname);
|
|
$this->sort[$sort] = $order;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sort to parameters.
|
|
*
|
|
* @param array $sorts
|
|
* @return array
|
|
*/
|
|
protected function sort_to_params($sorts): array {
|
|
$params = [];
|
|
$i = 0;
|
|
foreach ($sorts as $sort => $order) {
|
|
$i += 1;
|
|
if ($order < 0) {
|
|
$sort = '-' . $sort;
|
|
}
|
|
$params['qbs' . $i] = $sort;
|
|
}
|
|
return $params;
|
|
}
|
|
|
|
/**
|
|
* Default sort for question data.
|
|
* @return int[]
|
|
*/
|
|
protected function default_sort(): array {
|
|
$defaultsort = [];
|
|
if (class_exists('\\qbank_viewquestiontype\\question_type_column')) {
|
|
$sort = 'qbank_viewquestiontype\question_type_column';
|
|
}
|
|
$defaultsort[$sort] = 1;
|
|
if (class_exists('\\qbank_viewquestionname\\question_name_idnumber_tags_column')) {
|
|
$sort = 'qbank_viewquestionname\question_name_idnumber_tags_column';
|
|
}
|
|
$defaultsort[$sort . '-name'] = 1;
|
|
|
|
return $defaultsort;
|
|
}
|
|
|
|
/**
|
|
* Gets the primary sort order according to the default sort.
|
|
*
|
|
* @param string $sort a column or column_subsort name.
|
|
* @return int the current sort order for this column -1, 0, 1
|
|
*/
|
|
public function get_primary_sort_order($sort): int {
|
|
$order = reset($this->sort);
|
|
$primarysort = key($this->sort);
|
|
if ($sort == $primarysort) {
|
|
return $order;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a URL to redisplay the page with a new sort for the question bank.
|
|
*
|
|
* @param string $sort the column, or column_subsort to sort on.
|
|
* @param bool $newsortreverse whether to sort in reverse order.
|
|
* @return string The new URL.
|
|
*/
|
|
public function new_sort_url($sort, $newsortreverse): string {
|
|
if ($newsortreverse) {
|
|
$order = -1;
|
|
} else {
|
|
$order = 1;
|
|
}
|
|
// Tricky code to add the new sort at the start, removing it from where it was before, if it was present.
|
|
$newsort = array_reverse($this->sort);
|
|
if (isset($newsort[$sort])) {
|
|
unset($newsort[$sort]);
|
|
}
|
|
$newsort[$sort] = $order;
|
|
$newsort = array_reverse($newsort);
|
|
if (count($newsort) > self::MAX_SORTS) {
|
|
$newsort = array_slice($newsort, 0, self::MAX_SORTS, true);
|
|
}
|
|
return $this->baseurl->out(true, $this->sort_to_params($newsort));
|
|
}
|
|
|
|
/**
|
|
* Create the SQL query to retrieve the indicated questions, based on
|
|
* \core_question\bank\search\condition filters.
|
|
*/
|
|
protected function build_query(): void {
|
|
// Get the required tables and fields.
|
|
$joins = [];
|
|
$fields = ['q.hidden', 'q.category'];
|
|
if (!empty($this->requiredcolumns)) {
|
|
foreach ($this->requiredcolumns as $column) {
|
|
$extrajoins = $column->get_extra_joins();
|
|
foreach ($extrajoins as $prefix => $join) {
|
|
if (isset($joins[$prefix]) && $joins[$prefix] != $join) {
|
|
throw new \coding_exception('Join ' . $join . ' conflicts with previous join ' . $joins[$prefix]);
|
|
}
|
|
$joins[$prefix] = $join;
|
|
}
|
|
$fields = array_merge($fields, $column->get_required_fields());
|
|
}
|
|
}
|
|
$fields = array_unique($fields);
|
|
|
|
// Build the order by clause.
|
|
$sorts = [];
|
|
foreach ($this->sort as $sort => $order) {
|
|
list($colname, $subsort) = $this->parse_subsort($sort);
|
|
$sorts[] = $this->requiredcolumns[$colname]->sort_expression($order < 0, $subsort);
|
|
}
|
|
|
|
// Build the where clause.
|
|
$tests = ['q.parent = 0'];
|
|
$this->sqlparams = [];
|
|
foreach ($this->searchconditions as $searchcondition) {
|
|
if ($searchcondition->where()) {
|
|
$tests[] = '((' . $searchcondition->where() .'))';
|
|
}
|
|
if ($searchcondition->params()) {
|
|
$this->sqlparams = array_merge($this->sqlparams, $searchcondition->params());
|
|
}
|
|
}
|
|
// Build the SQL.
|
|
$sql = ' FROM {question} q ' . implode(' ', $joins);
|
|
$sql .= ' WHERE ' . implode(' AND ', $tests);
|
|
$this->countsql = 'SELECT count(1)' . $sql;
|
|
$this->loadsql = 'SELECT ' . implode(', ', $fields) . $sql . ' ORDER BY ' . implode(', ', $sorts);
|
|
}
|
|
|
|
/**
|
|
* Get the number of questions.
|
|
* @return int
|
|
*/
|
|
protected function get_question_count(): int {
|
|
global $DB;
|
|
return $DB->count_records_sql($this->countsql, $this->sqlparams);
|
|
}
|
|
|
|
/**
|
|
* Load the questions we need to display.
|
|
*
|
|
* @param int $page page to display.
|
|
* @param int $perpage number of questions per page.
|
|
* @return \moodle_recordset questionid => data about each question.
|
|
*/
|
|
protected function load_page_questions($page, $perpage): \moodle_recordset {
|
|
global $DB;
|
|
$questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, $page * $perpage, $perpage);
|
|
if (empty($questions)) {
|
|
$questions->close();
|
|
// No questions on this page. Reset to page 0.
|
|
$questions = $DB->get_recordset_sql($this->loadsql, $this->sqlparams, 0, $perpage);
|
|
}
|
|
return $questions;
|
|
}
|
|
|
|
/**
|
|
* Returns the base url.
|
|
*/
|
|
public function base_url(): \moodle_url {
|
|
return $this->baseurl;
|
|
}
|
|
|
|
/**
|
|
* Get the URL for editing a question as a moodle url.
|
|
*
|
|
* @param int $questionid the question id.
|
|
* @return \moodle_url the URL, HTML-escaped.
|
|
*/
|
|
public function edit_question_moodle_url($questionid) {
|
|
return new \moodle_url($this->editquestionurl, ['id' => $questionid]);
|
|
}
|
|
|
|
/**
|
|
* Get the URL for editing a question as a HTML-escaped string.
|
|
*
|
|
* @param int $questionid the question id.
|
|
* @return string the URL, HTML-escaped.
|
|
*/
|
|
public function edit_question_url($questionid) {
|
|
return $this->edit_question_moodle_url($questionid)->out();
|
|
}
|
|
|
|
/**
|
|
* Get the URL for duplicating a question as a moodle url.
|
|
*
|
|
* @param int $questionid the question id.
|
|
* @return \moodle_url the URL.
|
|
*/
|
|
public function copy_question_moodle_url($questionid) {
|
|
return new \moodle_url($this->editquestionurl, ['id' => $questionid, 'makecopy' => 1]);
|
|
}
|
|
|
|
/**
|
|
* Get the URL for duplicating a given question.
|
|
* @param int $questionid the question id.
|
|
* @return string the URL, HTML-escaped.
|
|
*/
|
|
public function copy_question_url($questionid) {
|
|
return $this->copy_question_moodle_url($questionid)->out();
|
|
}
|
|
|
|
/**
|
|
* Get the context we are displaying the question bank for.
|
|
* @return \context context object.
|
|
*/
|
|
public function get_most_specific_context(): \context {
|
|
return $this->contexts->lowest();
|
|
}
|
|
|
|
/**
|
|
* Get the URL to preview a question.
|
|
* @param \stdClass $questiondata the data defining the question.
|
|
* @return \moodle_url the URL.
|
|
* @deprecated since Moodle 4.0
|
|
* @see \qbank_previewquestion\helper::question_preview_url()
|
|
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
|
*/
|
|
public function preview_question_url($questiondata) {
|
|
debugging('Function preview_question_url() has been deprecated and moved to qbank_previewquestion plugin,
|
|
please use qbank_previewquestion\helper::question_preview_url() instead.', DEBUG_DEVELOPER);
|
|
return question_preview_url($questiondata->id, null, null, null, null,
|
|
$this->get_most_specific_context());
|
|
}
|
|
|
|
/**
|
|
* Shows the question bank interface.
|
|
*
|
|
* The function also processes a number of actions:
|
|
*
|
|
* Actions affecting the question pool:
|
|
* move Moves a question to a different category
|
|
* deleteselected Deletes the selected questions from the category
|
|
* Other actions:
|
|
* category Chooses the category
|
|
* params: $tabname question bank edit tab name, for permission checking
|
|
* $pagevars current list of page variables
|
|
*
|
|
* @param string $tabname
|
|
* @param array $pagevars
|
|
*/
|
|
public function display($pagevars, $tabname): void {
|
|
|
|
$page = $pagevars['qpage'];
|
|
$perpage = $pagevars['qperpage'];
|
|
$cat = $pagevars['cat'];
|
|
$recurse = $pagevars['recurse'];
|
|
$showhidden = $pagevars['showhidden'];
|
|
$showquestiontext = $pagevars['qbshowtext'];
|
|
$tagids = [];
|
|
if (!empty($pagevars['qtagids'])) {
|
|
$tagids = $pagevars['qtagids'];
|
|
}
|
|
|
|
echo \html_writer::start_div('questionbankwindow boxwidthwide boxaligncenter');
|
|
|
|
// This one will become redundant after implementing bulk actions plugin.
|
|
if ($this->process_actions_needing_ui()) {
|
|
return;
|
|
}
|
|
|
|
$editcontexts = $this->contexts->having_one_edit_tab_cap($tabname);
|
|
|
|
// Show the filters and search options.
|
|
$this->wanted_filters($cat, $tagids, $showhidden, $recurse, $editcontexts, $showquestiontext);
|
|
|
|
// Continues with list of questions.
|
|
$this->display_question_list($this->baseurl, $cat, null, $page, $perpage,
|
|
$this->contexts->having_cap('moodle/question:add'));
|
|
echo \html_writer::end_div();
|
|
|
|
}
|
|
|
|
/**
|
|
* The filters for the question bank.
|
|
*
|
|
* @param string $cat 'categoryid,contextid'
|
|
* @param array $tagids current list of selected tags
|
|
* @param bool $showhidden whether deleted questions should be displayed
|
|
* @param int $recurse Whether to include subcategories
|
|
* @param array $editcontexts parent contexts
|
|
* @param bool $showquestiontext whether the text of each question should be shown in the list
|
|
*/
|
|
public function wanted_filters($cat, $tagids, $showhidden, $recurse, $editcontexts, $showquestiontext): void {
|
|
global $CFG;
|
|
list(, $contextid) = explode(',', $cat);
|
|
$catcontext = \context::instance_by_id($contextid);
|
|
$thiscontext = $this->get_most_specific_context();
|
|
// Category selection form.
|
|
$this->display_question_bank_header();
|
|
|
|
// Display tag filter if usetags setting is enabled/enablefilters is true.
|
|
if ($this->enablefilters) {
|
|
if (is_array($this->customfilterobjects)) {
|
|
foreach ($this->customfilterobjects as $filterobjects) {
|
|
$this->searchconditions[] = $filterobjects;
|
|
}
|
|
} else {
|
|
if ($CFG->usetags) {
|
|
array_unshift($this->searchconditions,
|
|
new \core_question\bank\search\tag_condition([$catcontext, $thiscontext], $tagids));
|
|
}
|
|
|
|
array_unshift($this->searchconditions, new \core_question\bank\search\hidden_condition(!$showhidden));
|
|
array_unshift($this->searchconditions, new \core_question\bank\search\category_condition(
|
|
$cat, $recurse, $editcontexts, $this->baseurl, $this->course));
|
|
}
|
|
}
|
|
$this->display_options_form($showquestiontext);
|
|
}
|
|
|
|
/**
|
|
* Print the text if category id not available.
|
|
*/
|
|
protected function print_choose_category_message(): void {
|
|
echo \html_writer::start_tag('p', ['style' => "\"text-align:center;\""]);
|
|
echo \html_writer::tag('b', get_string('selectcategoryabove', 'question'));
|
|
echo \html_writer::end_tag('p');
|
|
}
|
|
|
|
/**
|
|
* Gets current selected category.
|
|
* @param string $categoryandcontext
|
|
* @return false|mixed|\stdClass
|
|
*/
|
|
protected function get_current_category($categoryandcontext) {
|
|
global $DB, $OUTPUT;
|
|
list($categoryid, $contextid) = explode(',', $categoryandcontext);
|
|
if (!$categoryid) {
|
|
$this->print_choose_category_message();
|
|
return false;
|
|
}
|
|
|
|
if (!$category = $DB->get_record('question_categories',
|
|
['id' => $categoryid, 'contextid' => $contextid])) {
|
|
echo $OUTPUT->box_start('generalbox questionbank');
|
|
echo $OUTPUT->notification('Category not found!');
|
|
echo $OUTPUT->box_end();
|
|
return false;
|
|
}
|
|
|
|
return $category;
|
|
}
|
|
|
|
/**
|
|
* Display the form with options for which questions are displayed and how they are displayed.
|
|
*
|
|
* @param bool $showquestiontext Display the text of the question within the list.
|
|
*/
|
|
protected function display_options_form($showquestiontext): void {
|
|
global $PAGE;
|
|
|
|
// The html will be refactored in the filter feature implementation.
|
|
echo \html_writer::start_tag('form', ['method' => 'get',
|
|
'action' => new \moodle_url($this->baseurl), 'id' => 'displayoptions']);
|
|
echo \html_writer::start_div();
|
|
|
|
$excludes = ['recurse', 'showhidden', 'qbshowtext'];
|
|
// If the URL contains any tags then we need to prevent them
|
|
// being added to the form as hidden elements because the tags
|
|
// are managed separately.
|
|
if ($this->baseurl->param('qtagids[0]')) {
|
|
$index = 0;
|
|
while ($this->baseurl->param("qtagids[{$index}]")) {
|
|
$excludes[] = "qtagids[{$index}]";
|
|
$index++;
|
|
}
|
|
}
|
|
echo \html_writer::input_hidden_params($this->baseurl, $excludes);
|
|
|
|
$advancedsearch = [];
|
|
|
|
foreach ($this->searchconditions as $searchcondition) {
|
|
if ($searchcondition->display_options_adv()) {
|
|
$advancedsearch[] = $searchcondition;
|
|
}
|
|
echo $searchcondition->display_options();
|
|
}
|
|
$this->display_showtext_checkbox($showquestiontext);
|
|
if (!empty($advancedsearch)) {
|
|
$this->display_advanced_search_form($advancedsearch);
|
|
}
|
|
|
|
$go = \html_writer::empty_tag('input', ['type' => 'submit', 'value' => get_string('go')]);
|
|
echo \html_writer::tag('noscript', \html_writer::div($go), ['class' => 'inline']);
|
|
echo \html_writer::end_div();
|
|
echo \html_writer::end_tag('form');
|
|
$PAGE->requires->yui_module('moodle-question-searchform', 'M.question.searchform.init');
|
|
}
|
|
|
|
/**
|
|
* Print the "advanced" UI elements for the form to select which questions. Hidden by default.
|
|
*
|
|
* @param array $advancedsearch
|
|
*/
|
|
protected function display_advanced_search_form($advancedsearch): void {
|
|
print_collapsible_region_start('', 'advancedsearch',
|
|
get_string('advancedsearchoptions', 'question'),
|
|
'question_bank_advanced_search');
|
|
foreach ($advancedsearch as $searchcondition) {
|
|
echo $searchcondition->display_options_adv();
|
|
}
|
|
print_collapsible_region_end();
|
|
}
|
|
|
|
/**
|
|
* Display the checkbox UI for toggling the display of the question text in the list.
|
|
* @param bool $showquestiontext the current or default value for whether to display the text.
|
|
*/
|
|
protected function display_showtext_checkbox($showquestiontext): void {
|
|
global $PAGE;
|
|
$displaydata = [
|
|
'checked' => $showquestiontext
|
|
];
|
|
if (class_exists('qbank_viewquestiontext\\question_text_row')) {
|
|
if (\core\plugininfo\qbank::is_plugin_enabled('qbank_viewquestiontext')) {
|
|
echo $PAGE->get_renderer('core_question', 'bank')->render_showtext_checkbox($displaydata);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the header element for the question bank.
|
|
*/
|
|
protected function display_question_bank_header(): void {
|
|
global $OUTPUT;
|
|
echo $OUTPUT->heading(get_string('questionbank', 'question'), 2);
|
|
}
|
|
|
|
/**
|
|
* Create a new question form.
|
|
*
|
|
* @param false|mixed|\stdClass $category
|
|
* @param bool $canadd
|
|
*/
|
|
protected function create_new_question_form($category, $canadd): void {
|
|
if (\core\plugininfo\qbank::is_plugin_enabled('qbank_editquestion')) {
|
|
echo editquestion_helper::create_new_question_button($category->id,
|
|
$this->requiredcolumns['qbank_editquestion\edit_action_column']->editquestionurl->params(), $canadd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints the table of questions in a category with interactions
|
|
*
|
|
* @param \moodle_url $pageurl The URL to reload this page.
|
|
* @param string $categoryandcontext 'categoryID,contextID'.
|
|
* @param int $recurse Whether to include subcategories.
|
|
* @param int $page The number of the page to be displayed
|
|
* @param int $perpage Number of questions to show per page
|
|
* @param array $addcontexts contexts where the user is allowed to add new questions.
|
|
*/
|
|
protected function display_question_list($pageurl, $categoryandcontext, $recurse = 1, $page = 0,
|
|
$perpage = 100, $addcontexts = []): void {
|
|
global $OUTPUT;
|
|
// This function can be moderately slow with large question counts and may time out.
|
|
// We probably do not want to raise it to unlimited, so randomly picking 5 minutes.
|
|
// Note: We do not call this in the loop because quiz ob_ captures this function (see raise() PHP doc).
|
|
\core_php_time_limit::raise(300);
|
|
|
|
$category = $this->get_current_category($categoryandcontext);
|
|
|
|
list($categoryid, $contextid) = explode(',', $categoryandcontext);
|
|
$catcontext = \context::instance_by_id($contextid);
|
|
|
|
$canadd = has_capability('moodle/question:add', $catcontext);
|
|
|
|
$this->create_new_question_form($category, $canadd);
|
|
|
|
$this->build_query();
|
|
$totalnumber = $this->get_question_count();
|
|
if ($totalnumber == 0) {
|
|
return;
|
|
}
|
|
$questionsrs = $this->load_page_questions($page, $perpage);
|
|
$questions = [];
|
|
foreach ($questionsrs as $question) {
|
|
if (!empty($question->id)) {
|
|
$questions[$question->id] = $question;
|
|
}
|
|
}
|
|
$questionsrs->close();
|
|
foreach ($this->requiredcolumns as $name => $column) {
|
|
$column->load_additional_data($questions);
|
|
}
|
|
|
|
$pageingurl = new \moodle_url($pageurl, $pageurl->params());
|
|
$pagingbar = new \paging_bar($totalnumber, $page, $perpage, $pageingurl);
|
|
$pagingbar->pagevar = 'qpage';
|
|
|
|
$this->display_top_pagnation($OUTPUT->render($pagingbar));
|
|
|
|
// This html will be refactored in the bulk actions implementation.
|
|
echo \html_writer::start_tag('form', ['action' => $pageurl, 'method' => 'post']);
|
|
echo \html_writer::start_tag('fieldset', ['class' => 'invisiblefieldset', 'style' => "display: block;"]);
|
|
echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]);
|
|
echo \html_writer::input_hidden_params($this->baseurl);
|
|
|
|
$this->display_questions($questions);
|
|
|
|
$this->display_bottom_pagination($OUTPUT->render($pagingbar), $totalnumber, $perpage, $pageurl);
|
|
|
|
$this->display_bottom_controls($totalnumber, $recurse, $category, $catcontext, $addcontexts);
|
|
|
|
echo \html_writer::end_tag('fieldset');
|
|
echo \html_writer::end_tag('form');
|
|
}
|
|
|
|
/**
|
|
* Display the top pagination bar.
|
|
*
|
|
* @param object $pagination
|
|
*/
|
|
protected function display_top_pagnation($pagination): void {
|
|
global $PAGE;
|
|
$displaydata = [
|
|
'pagination' => $pagination
|
|
];
|
|
echo $PAGE->get_renderer('core_question', 'bank')->render_question_pagination($displaydata);
|
|
}
|
|
|
|
/**
|
|
* Display bottom pagination bar.
|
|
*
|
|
* @param string $pagination
|
|
* @param int $totalnumber
|
|
* @param int $perpage
|
|
* @param \moodle_url $pageurl
|
|
*/
|
|
protected function display_bottom_pagination($pagination, $totalnumber, $perpage, $pageurl): void {
|
|
global $PAGE;
|
|
$displaydata = array (
|
|
'extraclasses' => 'pagingbottom',
|
|
'pagination' => $pagination,
|
|
'biggertotal' => true,
|
|
);
|
|
if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) {
|
|
$displaydata['showall'] = true;
|
|
if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) {
|
|
$url = new \moodle_url($pageurl, array_merge($pageurl->params(),
|
|
['qpage' => 0, 'qperpage' => MAXIMUM_QUESTIONS_PER_PAGE]));
|
|
if ($totalnumber > MAXIMUM_QUESTIONS_PER_PAGE) {
|
|
$displaydata['totalnumber'] = MAXIMUM_QUESTIONS_PER_PAGE;
|
|
} else {
|
|
$displaydata['biggertotal'] = false;
|
|
$displaydata['totalnumber'] = $totalnumber;
|
|
}
|
|
} else {
|
|
$url = new \moodle_url($pageurl, array_merge($pageurl->params(),
|
|
['qperpage' => DEFAULT_QUESTIONS_PER_PAGE]));
|
|
$displaydata['totalnumber'] = DEFAULT_QUESTIONS_PER_PAGE;
|
|
}
|
|
$displaydata['showallurl'] = $url;
|
|
}
|
|
echo $PAGE->get_renderer('core_question', 'bank')->render_question_pagination($displaydata);
|
|
}
|
|
|
|
/**
|
|
* Display the controls at the bottom of the list of questions.
|
|
* @param int $totalnumber Total number of questions that might be shown (if it was not for paging).
|
|
* @param bool $recurse Whether to include subcategories.
|
|
* @param \stdClass $category The question_category row from the database.
|
|
* @param \context $catcontext The context of the category being displayed.
|
|
* @param array $addcontexts contexts where the user is allowed to add new questions.
|
|
*/
|
|
protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts): void {
|
|
$caneditall = has_capability('moodle/question:editall', $catcontext);
|
|
$canuseall = has_capability('moodle/question:useall', $catcontext);
|
|
$canmoveall = has_capability('moodle/question:moveall', $catcontext);
|
|
|
|
echo \html_writer::start_tag('div', ['class' => "modulespecificbuttonscontainer"]);
|
|
if ($caneditall || $canmoveall || $canuseall) {
|
|
$withselectedcontent = ' ' . get_string('withselected', 'question') . ':';
|
|
echo \html_writer::tag('strong', $withselectedcontent);
|
|
echo \html_writer::empty_tag('br');
|
|
|
|
// Print delete and move selected question.
|
|
if ($caneditall) {
|
|
echo \html_writer::empty_tag('input', [
|
|
'type' => 'submit',
|
|
'class' => 'btn btn-secondary mr-1',
|
|
'name' => 'deleteselected',
|
|
'value' => get_string('delete'),
|
|
'data-action' => 'toggle',
|
|
'data-togglegroup' => 'qbank',
|
|
'data-toggle' => 'action',
|
|
'disabled' => true,
|
|
]);
|
|
}
|
|
|
|
if ($canmoveall && count($addcontexts)) {
|
|
echo \html_writer::empty_tag('input', [
|
|
'type' => 'submit',
|
|
'class' => 'btn btn-secondary mr-1',
|
|
'name' => 'move',
|
|
'value' => get_string('moveto', 'question'),
|
|
'data-action' => 'toggle',
|
|
'data-togglegroup' => 'qbank',
|
|
'data-toggle' => 'action',
|
|
'disabled' => true,
|
|
]);
|
|
helper::question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}");
|
|
}
|
|
}
|
|
|
|
echo \html_writer::end_tag('div');
|
|
}
|
|
|
|
/**
|
|
* Display the questions.
|
|
*
|
|
* @param array $questions
|
|
*/
|
|
protected function display_questions($questions): void {
|
|
echo \html_writer::start_tag('div',
|
|
['class' => 'categoryquestionscontainer', 'id' => 'questionscontainer']);
|
|
$this->print_table($questions);
|
|
echo \html_writer::end_tag('div');
|
|
}
|
|
|
|
/**
|
|
* Prints the actual table with question.
|
|
*
|
|
* @param array $questions
|
|
*/
|
|
protected function print_table($questions): void {
|
|
// Start of the table.
|
|
echo \html_writer::start_tag('table', ['id' => 'categoryquestions']);
|
|
|
|
// Prints the table header.
|
|
echo \html_writer::start_tag('thead');
|
|
echo \html_writer::start_tag('tr');
|
|
$this->print_table_headers();
|
|
echo \html_writer::end_tag('tr');
|
|
echo \html_writer::end_tag('thead');
|
|
|
|
// Prints the table row or content.
|
|
echo \html_writer::start_tag('tbody');
|
|
$rowcount = 0;
|
|
foreach ($questions as $question) {
|
|
$this->print_table_row($question, $rowcount);
|
|
$rowcount += 1;
|
|
}
|
|
echo \html_writer::end_tag('tbody');
|
|
|
|
// End of the table.
|
|
echo \html_writer::end_tag('table');
|
|
}
|
|
|
|
/**
|
|
* Start of the table html.
|
|
*
|
|
* @deprecated since Moodle 4.0
|
|
* @see print_table()
|
|
* @todo Final deprecation of this function in moodle 4.4
|
|
*/
|
|
protected function start_table() {
|
|
debugging('Function start_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER);
|
|
echo '<table id="categoryquestions">' . "\n";
|
|
echo "<thead>\n";
|
|
$this->print_table_headers();
|
|
echo "</thead>\n";
|
|
echo "<tbody>\n";
|
|
}
|
|
|
|
/**
|
|
* End of the table html.
|
|
*
|
|
* @deprecated since Moodle 4.0
|
|
* @see print_table()
|
|
* @todo Final deprecation of this function in moodle 4.4
|
|
*/
|
|
protected function end_table() {
|
|
debugging('Function end_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER);
|
|
echo "</tbody>\n";
|
|
echo "</table>\n";
|
|
}
|
|
|
|
/**
|
|
* Print table headers from child classes.
|
|
*/
|
|
protected function print_table_headers(): void {
|
|
foreach ($this->visiblecolumns as $column) {
|
|
$column->display_header();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the classes for the row.
|
|
*
|
|
* @param \stdClass $question
|
|
* @param int $rowcount
|
|
* @return array
|
|
*/
|
|
protected function get_row_classes($question, $rowcount): array {
|
|
$classes = [];
|
|
if ($question->hidden) {
|
|
$classes[] = 'dimmed_text';
|
|
}
|
|
if ($question->id == $this->lastchangedid) {
|
|
$classes[] = 'highlight text-dark';
|
|
}
|
|
$classes[] = 'r' . ($rowcount % 2);
|
|
return $classes;
|
|
}
|
|
|
|
/**
|
|
* Prints the table row from child classes.
|
|
*
|
|
* @param \stdClass $question
|
|
* @param int $rowcount
|
|
*/
|
|
protected function print_table_row($question, $rowcount): void {
|
|
$rowclasses = implode(' ', $this->get_row_classes($question, $rowcount));
|
|
$attributes = [];
|
|
if ($rowclasses) {
|
|
$attributes['class'] = $rowclasses;
|
|
}
|
|
echo \html_writer::start_tag('tr', $attributes);
|
|
foreach ($this->visiblecolumns as $column) {
|
|
$column->display($question, $rowclasses);
|
|
}
|
|
echo \html_writer::end_tag('tr');
|
|
foreach ($this->extrarows as $row) {
|
|
$row->display($question, $rowclasses);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process actions for the selected action.
|
|
*
|
|
*/
|
|
public function process_actions(): void {
|
|
global $DB;
|
|
// Now, check for commands on this page and modify variables as necessary.
|
|
if (optional_param('move', false, PARAM_BOOL) and confirm_sesskey()) {
|
|
// Move selected questions to new category.
|
|
$category = required_param('category', PARAM_SEQUENCE);
|
|
list($tocategoryid, $contextid) = explode(',', $category);
|
|
if (! $tocategory = $DB->get_record('question_categories',
|
|
['id' => $tocategoryid, 'contextid' => $contextid])) {
|
|
throw new \moodle_exception('cannotfindcate', 'question');
|
|
}
|
|
$tocontext = \context::instance_by_id($contextid);
|
|
require_capability('moodle/question:add', $tocontext);
|
|
$rawdata = (array) data_submitted();
|
|
$questionids = [];
|
|
foreach ($rawdata as $key => $value) {
|
|
// Parse input for question ids.
|
|
if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
|
|
$key = $matches[1];
|
|
$questionids[] = $key;
|
|
}
|
|
}
|
|
if ($questionids) {
|
|
list($usql, $params) = $DB->get_in_or_equal($questionids);
|
|
$questions = $DB->get_records_sql("
|
|
SELECT q.*, c.contextid
|
|
FROM {question} q
|
|
JOIN {question_categories} c ON c.id = q.category
|
|
WHERE q.id {$usql}", $params);
|
|
foreach ($questions as $question) {
|
|
question_require_capability_on($question, 'move');
|
|
}
|
|
question_move_questions_to_category($questionids, $tocategory->id);
|
|
redirect($this->baseurl->out(false, ['category' => "{$tocategoryid},{$contextid}"]));
|
|
}
|
|
}
|
|
|
|
if (optional_param('deleteselected', false, PARAM_BOOL)) {
|
|
// Delete selected questions from the category.
|
|
// If teacher has already confirmed the action.
|
|
if (($confirm = optional_param('confirm', '', PARAM_ALPHANUM)) and confirm_sesskey()) {
|
|
$deleteselected = required_param('deleteselected', PARAM_RAW);
|
|
if ($confirm == md5($deleteselected)) {
|
|
if ($questionlist = explode(',', $deleteselected)) {
|
|
// For each question either hide it if it is in use or delete it.
|
|
foreach ($questionlist as $questionid) {
|
|
$questionid = (int)$questionid;
|
|
question_require_capability_on($questionid, 'edit');
|
|
if (questions_in_use([$questionid])) {
|
|
$DB->set_field('question', 'hidden', 1, ['id' => $questionid]);
|
|
} else {
|
|
question_delete_question($questionid);
|
|
}
|
|
}
|
|
}
|
|
redirect($this->baseurl);
|
|
} else {
|
|
throw new \moodle_exception('invalidconfirm', 'question');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unhide a question.
|
|
if (($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) {
|
|
question_require_capability_on($unhide, 'edit');
|
|
$DB->set_field('question', 'hidden', 0, ['id' => $unhide]);
|
|
|
|
// Purge these questions from the cache.
|
|
\question_bank::notify_question_edited($unhide);
|
|
|
|
redirect($this->baseurl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process actions with ui.
|
|
* @return bool
|
|
*/
|
|
public function process_actions_needing_ui(): bool {
|
|
global $DB, $OUTPUT;
|
|
if (optional_param('deleteselected', false, PARAM_BOOL)) {
|
|
// Make a list of all the questions that are selected.
|
|
$rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted.
|
|
$questionlist = ''; // Comma separated list of ids of questions to be deleted.
|
|
$questionnames = ''; // String with names of questions separated by <br/> with an asterix
|
|
// in front of those that are in use.
|
|
$inuse = false; // Set to true if at least one of the questions is in use.
|
|
foreach ($rawquestions as $key => $value) { // Parse input for question ids.
|
|
if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
|
|
$key = $matches[1];
|
|
$questionlist .= $key.',';
|
|
question_require_capability_on((int)$key, 'edit');
|
|
if (questions_in_use([$key])) {
|
|
$questionnames .= '* ';
|
|
$inuse = true;
|
|
}
|
|
$questionnames .= $DB->get_field('question', 'name', ['id' => $key]) . '<br />';
|
|
}
|
|
}
|
|
if (!$questionlist) { // No questions were selected.
|
|
redirect($this->baseurl);
|
|
}
|
|
$questionlist = rtrim($questionlist, ',');
|
|
|
|
// Add an explanation about questions in use.
|
|
if ($inuse) {
|
|
$questionnames .= '<br />'.get_string('questionsinuse', 'question');
|
|
}
|
|
$baseurl = new \moodle_url($this->baseurl, $this->baseurl->params());
|
|
$deleteurl = new \moodle_url($baseurl, ['deleteselected' => $questionlist, 'confirm' => md5($questionlist),
|
|
'sesskey' => sesskey()]);
|
|
|
|
$continue = new \single_button($deleteurl, get_string('delete'), 'post');
|
|
echo $OUTPUT->confirm(get_string('deletequestionscheck', 'question', $questionnames), $continue, $baseurl);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add another search control to this view.
|
|
* @param condition $searchcondition the condition to add.
|
|
*/
|
|
public function add_searchcondition($searchcondition): void {
|
|
$this->searchconditions[] = $searchcondition;
|
|
}
|
|
|
|
}
|