2021-06-24 15:58:09 +10:00
|
|
|
<?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
|
|
|
|
*/
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
namespace core_question\local\bank;
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
use core_question\bank\search\condition;
|
2021-06-03 21:05:04 +10:00
|
|
|
use qbank_editquestion\editquestion_helper;
|
2021-05-11 16:22:03 +10:00
|
|
|
use qbank_managecategories\helper;
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* This class prints a view of the question bank.
|
|
|
|
*
|
|
|
|
* including
|
2021-06-24 15:58:09 +10:00
|
|
|
* + 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
|
2021-06-25 00:08:16 +10:00
|
|
|
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
|
2021-06-24 15:58:09 +10:00
|
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
|
*/
|
|
|
|
class view {
|
2021-06-25 00:08:16 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Maximum number of sorts allowed.
|
|
|
|
*/
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public $cm;
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var object the course we are within.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public $course;
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected $searchconditions = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string url of the new question page.
|
|
|
|
*/
|
|
|
|
public $returnurl;
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* @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.
|
|
|
|
*
|
2021-06-24 15:58:09 +10:00
|
|
|
* @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.
|
2021-06-25 00:08:16 +10:00
|
|
|
$this->returnurl = $pageurl->out_as_local_url(false);
|
2021-06-03 21:05:04 +10:00
|
|
|
$this->editquestionurl = new \moodle_url('/question/bank/editquestion/question.php', ['returnurl' => $this->returnurl]);
|
2021-06-25 00:08:16 +10:00
|
|
|
if ($this->cm !== null) {
|
|
|
|
$this->editquestionurl->param('cmid', $this->cm->id);
|
2021-06-24 15:58:09 +10:00
|
|
|
} else {
|
|
|
|
$this->editquestionurl->param('courseid', $this->course->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->lastchangedid = optional_param('lastchanged', 0, PARAM_INT);
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
// Possibly the heading part can be removed.
|
2021-06-24 15:58:09 +10:00
|
|
|
$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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function init_search_conditions(): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
$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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* 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'
|
|
|
|
];
|
2021-06-24 15:58:09 +10:00
|
|
|
if (question_get_display_preference('qbshowtext', 0, PARAM_BOOL, new \moodle_url(''))) {
|
2021-06-25 00:08:16 +10:00
|
|
|
$corequestionbankcolumns[] = 'question_text_row';
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
foreach ($corequestionbankcolumns as $fullname) {
|
|
|
|
$shortname = $fullname;
|
2021-08-07 02:03:58 +10:00
|
|
|
if (class_exists('core_question\\local\\bank\\' . $fullname)) {
|
|
|
|
$fullname = 'core_question\\local\\bank\\' . $fullname;
|
|
|
|
$questionbankclasscolumns[$shortname] = new $fullname($this);
|
|
|
|
} else {
|
|
|
|
$questionbankclasscolumns[$shortname] = '';
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
}
|
|
|
|
$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;
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
|
2021-08-07 02:03:58 +10:00
|
|
|
// Mitigate the error in case of any regression.
|
|
|
|
foreach ($questionbankclasscolumns as $shortname => $questionbankclasscolumn) {
|
|
|
|
if (empty($questionbankclasscolumn)){
|
|
|
|
unset($questionbankclasscolumns[$shortname]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
return $questionbankclasscolumns;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads all the available columns.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function wanted_columns(): array {
|
|
|
|
$this->requiredcolumns = [];
|
2021-08-07 02:03:58 +10:00
|
|
|
$questionbankcolumns = $this->get_question_bank_plugins();
|
|
|
|
foreach ($questionbankcolumns as $classobject) {
|
|
|
|
if (empty($classobject)) {
|
|
|
|
continue;
|
2021-06-25 00:08:16 +10:00
|
|
|
}
|
2021-08-07 02:03:58 +10:00
|
|
|
$this->requiredcolumns[get_class($classobject)] = $classobject;
|
2021-06-25 00:08:16 +10:00
|
|
|
}
|
|
|
|
|
2021-06-24 15:58:09 +10:00
|
|
|
return $this->requiredcolumns;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* Check a column object from its name and get the object for sort.
|
2021-06-24 15:58:09 +10:00
|
|
|
*
|
2021-06-25 00:08:16 +10:00
|
|
|
* @param string $columnname
|
2021-06-24 15:58:09 +10:00
|
|
|
*/
|
|
|
|
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
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function heading_column(): string {
|
2021-05-31 14:21:39 +10:00
|
|
|
return 'qbank_viewquestionname\viewquestionname_column_helper';
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializing table columns
|
|
|
|
*
|
|
|
|
* @param array $wanted Collection of column names
|
|
|
|
* @param string $heading The name of column that is set as heading
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function init_columns($wanted, $heading = ''): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
// If we are using the edit menu column, allow it to absorb all the actions.
|
|
|
|
foreach ($wanted as $column) {
|
2021-08-07 02:03:58 +10:00
|
|
|
if ($column instanceof edit_menu_column) {
|
2021-06-24 15:58:09 +10:00
|
|
|
$wanted = $column->claim_menuable_columns($wanted);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now split columns into real columns and rows.
|
2021-06-25 00:08:16 +10:00
|
|
|
$this->visiblecolumns = [];
|
|
|
|
$this->extrarows = [];
|
2021-06-24 15:58:09 +10:00
|
|
|
foreach ($wanted as $column) {
|
|
|
|
if ($column->is_extra_row()) {
|
|
|
|
$this->extrarows[get_class($column)] = $column;
|
|
|
|
} else {
|
|
|
|
$this->visiblecolumns[get_class($column)] = $column;
|
|
|
|
}
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
|
2021-06-24 15:58:09 +10:00
|
|
|
if (array_key_exists($heading, $this->requiredcolumns)) {
|
|
|
|
$this->requiredcolumns[$heading]->set_as_heading();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* Checks if the column included in the output.
|
|
|
|
*
|
2021-06-24 15:58:09 +10:00
|
|
|
* @param string $colname a column internal name.
|
|
|
|
* @return bool is this column included in the output?
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public function has_column($colname): bool {
|
2021-06-24 15:58:09 +10:00
|
|
|
return isset($this->visiblecolumns[$colname]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* Get the count of the columns.
|
|
|
|
*
|
2021-06-24 15:58:09 +10:00
|
|
|
* @return int The number of columns in the table.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public function get_column_count(): int {
|
2021-06-24 15:58:09 +10:00
|
|
|
return count($this->visiblecolumns);
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Get course id.
|
|
|
|
* @return mixed
|
|
|
|
*/
|
2021-06-24 15:58:09 +10:00
|
|
|
public function get_courseid() {
|
|
|
|
return $this->course->id;
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Initialise sorting.
|
|
|
|
*/
|
|
|
|
protected function init_sort(): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
$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.
|
2021-06-25 00:08:16 +10:00
|
|
|
* @return array [$colname, $subsort].
|
2021-06-24 15:58:09 +10:00
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function parse_subsort($sort): array {
|
2021-06-24 15:58:09 +10:00
|
|
|
// Do the parsing.
|
|
|
|
if (strpos($sort, '-') !== false) {
|
|
|
|
list($colname, $subsort) = explode('-', $sort, 2);
|
|
|
|
} else {
|
|
|
|
$colname = $sort;
|
|
|
|
$subsort = '';
|
|
|
|
}
|
|
|
|
// Validate the column name.
|
2021-06-25 00:08:16 +10:00
|
|
|
$this->get_column_type($colname);
|
|
|
|
$column = $this->requiredcolumns[$colname];
|
2021-06-24 15:58:09 +10:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
return [$colname, $subsort];
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Initialise sort from parameters.
|
|
|
|
*/
|
|
|
|
protected function init_sort_from_params(): void {
|
|
|
|
$this->sort = [];
|
2021-06-24 15:58:09 +10:00
|
|
|
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);
|
2021-06-25 00:08:16 +10:00
|
|
|
$this->get_column_type($colname);
|
2021-06-24 15:58:09 +10:00
|
|
|
$this->sort[$sort] = $order;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Sort to parameters.
|
|
|
|
*
|
|
|
|
* @param array $sorts
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function sort_to_params($sorts): array {
|
|
|
|
$params = [];
|
2021-06-24 15:58:09 +10:00
|
|
|
$i = 0;
|
|
|
|
foreach ($sorts as $sort => $order) {
|
|
|
|
$i += 1;
|
|
|
|
if ($order < 0) {
|
|
|
|
$sort = '-' . $sort;
|
|
|
|
}
|
|
|
|
$params['qbs' . $i] = $sort;
|
|
|
|
}
|
|
|
|
return $params;
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* 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;
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* Gets the primary sort order according to the default sort.
|
|
|
|
*
|
2021-06-24 15:58:09 +10:00
|
|
|
* @param string $sort a column or column_subsort name.
|
|
|
|
* @return int the current sort order for this column -1, 0, 1
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public function get_primary_sort_order($sort): int {
|
2021-06-24 15:58:09 +10:00
|
|
|
$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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public function new_sort_url($sort, $newsortreverse): string {
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function build_query(): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
// Get the required tables and fields.
|
2021-06-25 00:08:16 +10:00
|
|
|
$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;
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
$fields = array_merge($fields, $column->get_required_fields());
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$fields = array_unique($fields);
|
|
|
|
|
|
|
|
// Build the order by clause.
|
2021-06-25 00:08:16 +10:00
|
|
|
$sorts = [];
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
2021-06-25 00:08:16 +10:00
|
|
|
$tests = ['q.parent = 0'];
|
|
|
|
$this->sqlparams = [];
|
2021-06-24 15:58:09 +10:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Get the number of questions.
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
protected function get_question_count(): int {
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function load_page_questions($page, $perpage): \moodle_recordset {
|
2021-06-24 15:58:09 +10:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Returns the base url.
|
|
|
|
*/
|
|
|
|
public function base_url(): \moodle_url {
|
2021-06-24 15:58:09 +10:00
|
|
|
return $this->baseurl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* Get the URL for editing a question as a moodle url.
|
2021-06-24 15:58:09 +10:00
|
|
|
*
|
|
|
|
* @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();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* Get the URL for duplicating a question as a moodle url.
|
2021-06-24 15:58:09 +10:00
|
|
|
*
|
|
|
|
* @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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public function get_most_specific_context(): \context {
|
2021-06-24 15:58:09 +10:00
|
|
|
return $this->contexts->lowest();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the URL to preview a question.
|
|
|
|
* @param \stdClass $questiondata the data defining the question.
|
|
|
|
* @return \moodle_url the URL.
|
2021-06-02 12:36:52 +10:00
|
|
|
* @deprecated since Moodle 4.0
|
|
|
|
* @see \qbank_previewquestion\helper::question_preview_url()
|
|
|
|
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
2021-06-24 15:58:09 +10:00
|
|
|
*/
|
|
|
|
public function preview_question_url($questiondata) {
|
2021-06-02 12:36:52 +10:00
|
|
|
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);
|
2021-06-24 15:58:09 +10:00
|
|
|
return question_preview_url($questiondata->id, null, null, null, null,
|
|
|
|
$this->get_most_specific_context());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-25 00:08:16 +10:00
|
|
|
* Shows the question bank interface.
|
2021-06-24 15:58:09 +10:00
|
|
|
*
|
|
|
|
* 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
|
2021-06-25 00:08:16 +10:00
|
|
|
* params: $tabname question bank edit tab name, for permission checking
|
|
|
|
* $pagevars current list of page variables
|
2021-06-24 15:58:09 +10:00
|
|
|
*
|
2021-06-25 00:08:16 +10:00
|
|
|
* @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'];
|
|
|
|
}
|
2021-06-24 15:58:09 +10:00
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
echo \html_writer::start_div('questionbankwindow boxwidthwide boxaligncenter');
|
|
|
|
|
|
|
|
// This one will become redundant after implementing bulk actions plugin.
|
2021-06-24 15:58:09 +10:00
|
|
|
if ($this->process_actions_needing_ui()) {
|
|
|
|
return;
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
|
2021-06-24 15:58:09 +10:00
|
|
|
$editcontexts = $this->contexts->having_one_edit_tab_cap($tabname);
|
2021-06-25 00:08:16 +10:00
|
|
|
|
|
|
|
// 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;
|
2021-06-24 15:58:09 +10:00
|
|
|
list(, $contextid) = explode(',', $cat);
|
|
|
|
$catcontext = \context::instance_by_id($contextid);
|
|
|
|
$thiscontext = $this->get_most_specific_context();
|
|
|
|
// Category selection form.
|
|
|
|
$this->display_question_bank_header();
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
// 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));
|
|
|
|
}
|
2021-06-24 15:58:09 +10:00
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 15:58:09 +10:00
|
|
|
$this->display_options_form($showquestiontext);
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* 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');
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Gets current selected category.
|
|
|
|
* @param string $categoryandcontext
|
|
|
|
* @return false|mixed|\stdClass
|
|
|
|
*/
|
2021-06-24 15:58:09 +10:00
|
|
|
protected function get_current_category($categoryandcontext) {
|
|
|
|
global $DB, $OUTPUT;
|
|
|
|
list($categoryid, $contextid) = explode(',', $categoryandcontext);
|
|
|
|
if (!$categoryid) {
|
2021-06-25 00:08:16 +10:00
|
|
|
$this->print_choose_category_message();
|
2021-06-24 15:58:09 +10:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$category = $DB->get_record('question_categories',
|
2021-06-25 00:08:16 +10:00
|
|
|
['id' => $categoryid, 'contextid' => $contextid])) {
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
2021-06-25 00:08:16 +10:00
|
|
|
*
|
2021-06-24 15:58:09 +10:00
|
|
|
* @param bool $showquestiontext Display the text of the question within the list.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function display_options_form($showquestiontext): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
global $PAGE;
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
// The html will be refactored in the filter feature implementation.
|
|
|
|
echo \html_writer::start_tag('form', ['method' => 'get',
|
2021-08-19 17:48:00 +02:00
|
|
|
'action' => new \moodle_url($this->baseurl), 'id' => 'displayoptions']);
|
2021-06-24 15:58:09 +10:00
|
|
|
echo \html_writer::start_div();
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
$excludes = ['recurse', 'showhidden', 'qbshowtext'];
|
2021-06-24 15:58:09 +10:00
|
|
|
// 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);
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
$advancedsearch = [];
|
|
|
|
|
2021-06-24 15:58:09 +10:00
|
|
|
foreach ($this->searchconditions as $searchcondition) {
|
2021-06-25 00:08:16 +10:00
|
|
|
if ($searchcondition->display_options_adv()) {
|
|
|
|
$advancedsearch[] = $searchcondition;
|
|
|
|
}
|
2021-06-24 15:58:09 +10:00
|
|
|
echo $searchcondition->display_options();
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
$this->display_showtext_checkbox($showquestiontext);
|
|
|
|
if (!empty($advancedsearch)) {
|
|
|
|
$this->display_advanced_search_form($advancedsearch);
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
|
|
|
|
$go = \html_writer::empty_tag('input', ['type' => 'submit', 'value' => get_string('go')]);
|
|
|
|
echo \html_writer::tag('noscript', \html_writer::div($go), ['class' => 'inline']);
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
2021-06-25 00:08:16 +10:00
|
|
|
*
|
|
|
|
* @param array $advancedsearch
|
2021-06-24 15:58:09 +10:00
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
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) {
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Display the header element for the question bank.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function display_question_bank_header(): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
global $OUTPUT;
|
|
|
|
echo $OUTPUT->heading(get_string('questionbank', 'question'), 2);
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Create a new question form.
|
|
|
|
*
|
|
|
|
* @param false|mixed|\stdClass $category
|
|
|
|
* @param bool $canadd
|
|
|
|
*/
|
|
|
|
protected function create_new_question_form($category, $canadd): void {
|
2021-06-03 21:05:04 +10:00
|
|
|
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);
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function display_question_list($pageurl, $categoryandcontext, $recurse = 1, $page = 0,
|
|
|
|
$perpage = 100, $addcontexts = []): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
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) {
|
2021-06-25 00:08:16 +10:00
|
|
|
if (!empty($question->id)) {
|
|
|
|
$questions[$question->id] = $question;
|
|
|
|
}
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
$questionsrs->close();
|
|
|
|
foreach ($this->requiredcolumns as $name => $column) {
|
|
|
|
$column->load_additional_data($questions);
|
|
|
|
}
|
|
|
|
|
2021-08-19 17:48:00 +02:00
|
|
|
$pageingurl = new \moodle_url($pageurl, $pageurl->params());
|
2021-06-24 15:58:09 +10:00
|
|
|
$pagingbar = new \paging_bar($totalnumber, $page, $perpage, $pageingurl);
|
|
|
|
$pagingbar->pagevar = 'qpage';
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
$this->display_top_pagnation($OUTPUT->render($pagingbar));
|
|
|
|
|
|
|
|
// This html will be refactored in the bulk actions implementation.
|
2021-08-19 17:48:00 +02:00
|
|
|
echo \html_writer::start_tag('form', ['action' => $pageurl, 'method' => 'post']);
|
2021-06-25 00:08:16 +10:00
|
|
|
echo \html_writer::start_tag('fieldset', ['class' => 'invisiblefieldset', 'style' => "display: block;"]);
|
|
|
|
echo \html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()]);
|
2021-06-24 15:58:09 +10:00
|
|
|
echo \html_writer::input_hidden_params($this->baseurl);
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
$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);
|
|
|
|
}
|
2021-06-24 15:58:09 +10:00
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* 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,
|
|
|
|
);
|
2021-06-24 15:58:09 +10:00
|
|
|
if ($totalnumber > DEFAULT_QUESTIONS_PER_PAGE) {
|
2021-06-25 00:08:16 +10:00
|
|
|
$displaydata['showall'] = true;
|
2021-06-24 15:58:09 +10:00
|
|
|
if ($perpage == DEFAULT_QUESTIONS_PER_PAGE) {
|
2021-08-19 17:48:00 +02:00
|
|
|
$url = new \moodle_url($pageurl, array_merge($pageurl->params(),
|
2021-06-25 00:08:16 +10:00
|
|
|
['qpage' => 0, 'qperpage' => MAXIMUM_QUESTIONS_PER_PAGE]));
|
2021-06-24 15:58:09 +10:00
|
|
|
if ($totalnumber > MAXIMUM_QUESTIONS_PER_PAGE) {
|
2021-06-25 00:08:16 +10:00
|
|
|
$displaydata['totalnumber'] = MAXIMUM_QUESTIONS_PER_PAGE;
|
2021-06-24 15:58:09 +10:00
|
|
|
} else {
|
2021-06-25 00:08:16 +10:00
|
|
|
$displaydata['biggertotal'] = false;
|
|
|
|
$displaydata['totalnumber'] = $totalnumber;
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
} else {
|
2021-08-19 17:48:00 +02:00
|
|
|
$url = new \moodle_url($pageurl, array_merge($pageurl->params(),
|
2021-06-25 00:08:16 +10:00
|
|
|
['qperpage' => DEFAULT_QUESTIONS_PER_PAGE]));
|
|
|
|
$displaydata['totalnumber'] = DEFAULT_QUESTIONS_PER_PAGE;
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
$displaydata['showallurl'] = $url;
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
echo $PAGE->get_renderer('core_question', 'bank')->render_question_pagination($displaydata);
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
$caneditall = has_capability('moodle/question:editall', $catcontext);
|
|
|
|
$canuseall = has_capability('moodle/question:useall', $catcontext);
|
|
|
|
$canmoveall = has_capability('moodle/question:moveall', $catcontext);
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
echo \html_writer::start_tag('div', ['class' => "modulespecificbuttonscontainer"]);
|
2021-06-24 15:58:09 +10:00
|
|
|
if ($caneditall || $canmoveall || $canuseall) {
|
2021-06-25 00:08:16 +10:00
|
|
|
$withselectedcontent = ' ' . get_string('withselected', 'question') . ':';
|
|
|
|
echo \html_writer::tag('strong', $withselectedcontent);
|
|
|
|
echo \html_writer::empty_tag('br');
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
// Print delete and move selected question.
|
|
|
|
if ($caneditall) {
|
|
|
|
echo \html_writer::empty_tag('input', [
|
2021-06-25 00:08:16 +10:00
|
|
|
'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,
|
2021-06-24 15:58:09 +10:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($canmoveall && count($addcontexts)) {
|
|
|
|
echo \html_writer::empty_tag('input', [
|
2021-06-25 00:08:16 +10:00
|
|
|
'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,
|
2021-06-24 15:58:09 +10:00
|
|
|
]);
|
2021-05-11 16:22:03 +10:00
|
|
|
helper::question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}");
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
|
|
|
|
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');
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Start of the table html.
|
|
|
|
*
|
|
|
|
* @deprecated since Moodle 4.0
|
|
|
|
* @see print_table()
|
2021-08-07 02:03:58 +10:00
|
|
|
* @todo Final deprecation of this function in moodle 4.4
|
2021-06-25 00:08:16 +10:00
|
|
|
*/
|
2021-06-24 15:58:09 +10:00
|
|
|
protected function start_table() {
|
2021-06-25 00:08:16 +10:00
|
|
|
debugging('Function start_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER);
|
2021-06-24 15:58:09 +10:00
|
|
|
echo '<table id="categoryquestions">' . "\n";
|
|
|
|
echo "<thead>\n";
|
|
|
|
$this->print_table_headers();
|
|
|
|
echo "</thead>\n";
|
|
|
|
echo "<tbody>\n";
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* End of the table html.
|
|
|
|
*
|
|
|
|
* @deprecated since Moodle 4.0
|
|
|
|
* @see print_table()
|
2021-08-07 02:03:58 +10:00
|
|
|
* @todo Final deprecation of this function in moodle 4.4
|
2021-06-25 00:08:16 +10:00
|
|
|
*/
|
2021-06-24 15:58:09 +10:00
|
|
|
protected function end_table() {
|
2021-06-25 00:08:16 +10:00
|
|
|
debugging('Function end_table() is deprecated, please use print_table() instead.', DEBUG_DEVELOPER);
|
2021-06-24 15:58:09 +10:00
|
|
|
echo "</tbody>\n";
|
|
|
|
echo "</table>\n";
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Print table headers from child classes.
|
|
|
|
*/
|
|
|
|
protected function print_table_headers(): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
foreach ($this->visiblecolumns as $column) {
|
|
|
|
$column->display_header();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Gets the classes for the row.
|
|
|
|
*
|
|
|
|
* @param \stdClass $question
|
|
|
|
* @param int $rowcount
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function get_row_classes($question, $rowcount): array {
|
|
|
|
$classes = [];
|
2021-06-24 15:58:09 +10:00
|
|
|
if ($question->hidden) {
|
|
|
|
$classes[] = 'dimmed_text';
|
|
|
|
}
|
|
|
|
if ($question->id == $this->lastchangedid) {
|
|
|
|
$classes[] = 'highlight text-dark';
|
|
|
|
}
|
|
|
|
$classes[] = 'r' . ($rowcount % 2);
|
|
|
|
return $classes;
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Prints the table row from child classes.
|
|
|
|
*
|
|
|
|
* @param \stdClass $question
|
|
|
|
* @param int $rowcount
|
|
|
|
*/
|
|
|
|
protected function print_table_row($question, $rowcount): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
$rowclasses = implode(' ', $this->get_row_classes($question, $rowcount));
|
2021-06-25 00:08:16 +10:00
|
|
|
$attributes = [];
|
2021-06-24 15:58:09 +10:00
|
|
|
if ($rowclasses) {
|
2021-06-25 00:08:16 +10:00
|
|
|
$attributes['class'] = $rowclasses;
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
echo \html_writer::start_tag('tr', $attributes);
|
2021-06-24 15:58:09 +10:00
|
|
|
foreach ($this->visiblecolumns as $column) {
|
|
|
|
$column->display($question, $rowclasses);
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
echo \html_writer::end_tag('tr');
|
2021-06-24 15:58:09 +10:00
|
|
|
foreach ($this->extrarows as $row) {
|
|
|
|
$row->display($question, $rowclasses);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Process actions for the selected action.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function process_actions(): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
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);
|
2021-06-25 00:08:16 +10:00
|
|
|
if (! $tocategory = $DB->get_record('question_categories',
|
|
|
|
['id' => $tocategoryid, 'contextid' => $contextid])) {
|
|
|
|
throw new \moodle_exception('cannotfindcate', 'question');
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
$tocontext = \context::instance_by_id($contextid);
|
|
|
|
require_capability('moodle/question:add', $tocontext);
|
|
|
|
$rawdata = (array) data_submitted();
|
2021-06-25 00:08:16 +10:00
|
|
|
$questionids = [];
|
|
|
|
foreach ($rawdata as $key => $value) {
|
|
|
|
// Parse input for question ids.
|
2021-06-24 15:58:09 +10:00
|
|
|
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);
|
2021-06-25 00:08:16 +10:00
|
|
|
redirect($this->baseurl->out(false, ['category' => "{$tocategoryid},{$contextid}"]));
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
if (optional_param('deleteselected', false, PARAM_BOOL)) {
|
|
|
|
// Delete selected questions from the category.
|
2021-06-24 15:58:09 +10:00
|
|
|
// 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');
|
2021-06-25 00:08:16 +10:00
|
|
|
if (questions_in_use([$questionid])) {
|
|
|
|
$DB->set_field('question', 'hidden', 1, ['id' => $questionid]);
|
2021-06-24 15:58:09 +10:00
|
|
|
} else {
|
|
|
|
question_delete_question($questionid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
redirect($this->baseurl);
|
|
|
|
} else {
|
2021-06-25 00:08:16 +10:00
|
|
|
throw new \moodle_exception('invalidconfirm', 'question');
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unhide a question.
|
|
|
|
if (($unhide = optional_param('unhide', '', PARAM_INT)) and confirm_sesskey()) {
|
|
|
|
question_require_capability_on($unhide, 'edit');
|
2021-06-25 00:08:16 +10:00
|
|
|
$DB->set_field('question', 'hidden', 0, ['id' => $unhide]);
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
// Purge these questions from the cache.
|
|
|
|
\question_bank::notify_question_edited($unhide);
|
|
|
|
|
|
|
|
redirect($this->baseurl);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 00:08:16 +10:00
|
|
|
/**
|
|
|
|
* Process actions with ui.
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function process_actions_needing_ui(): bool {
|
2021-06-24 15:58:09 +10:00
|
|
|
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.
|
2021-06-25 00:08:16 +10:00
|
|
|
$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.
|
2021-06-24 15:58:09 +10:00
|
|
|
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');
|
2021-06-25 00:08:16 +10:00
|
|
|
if (questions_in_use([$key])) {
|
2021-06-24 15:58:09 +10:00
|
|
|
$questionnames .= '* ';
|
|
|
|
$inuse = true;
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
$questionnames .= $DB->get_field('question', 'name', ['id' => $key]) . '<br />';
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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');
|
|
|
|
}
|
2021-08-19 17:48:00 +02:00
|
|
|
$baseurl = new \moodle_url($this->baseurl, $this->baseurl->params());
|
2021-06-25 00:08:16 +10:00
|
|
|
$deleteurl = new \moodle_url($baseurl, ['deleteselected' => $questionlist, 'confirm' => md5($questionlist),
|
|
|
|
'sesskey' => sesskey()]);
|
2021-06-24 15:58:09 +10:00
|
|
|
|
|
|
|
$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.
|
|
|
|
*/
|
2021-06-25 00:08:16 +10:00
|
|
|
public function add_searchcondition($searchcondition): void {
|
2021-06-24 15:58:09 +10:00
|
|
|
$this->searchconditions[] = $searchcondition;
|
|
|
|
}
|
2021-06-25 00:08:16 +10:00
|
|
|
|
2021-06-24 15:58:09 +10:00
|
|
|
}
|