Merge branch 'master_MDL-71516-qbank' of https://github.com/catalyst/moodle-MDL-70329

This commit is contained in:
Ilya Tregubov 2021-08-17 14:13:40 +02:00
commit c0fc0747a8
79 changed files with 3985 additions and 367 deletions

63
admin/qbankplugins.php Normal file
View File

@ -0,0 +1,63 @@
<?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/>.
/**
* Question bank plugin settings.
*
* @package core
* @subpackage questionbank
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->libdir.'/adminlib.php');
$action = required_param('action', PARAM_ALPHANUMEXT);
$name = required_param('name', PARAM_PLUGIN);
$syscontext = context_system::instance();
$PAGE->set_url('/admin/qbankplugins.php');
$PAGE->set_context($syscontext);
require_admin();
$return = new moodle_url('/admin/settings.php', ['section' => 'manageqbanks']);
$plugins = core_plugin_manager::instance()->get_plugins_of_type('qbank');
$sortorder = array_flip(array_keys($plugins));
if (!isset($plugins[$name])) {
throw new moodle_exception('qbanknotfound', 'question', $return, $name);
}
switch ($action) {
case 'disable':
if ($plugins[$name]->is_enabled()) {
set_config('disabled', 1, 'qbank_'. $name);
}
break;
case 'enable':
if (!$plugins[$name]->is_enabled()) {
unset_config('disabled', 'qbank_'. $name);
}
break;
}
core_plugin_manager::reset_caches();
redirect($return);

View File

@ -403,6 +403,20 @@ if ($hassiteconfig) {
}
}
// Question bank settings.
if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) {
$ADMIN->add('modules', new admin_category('qbanksettings',
new lang_string('type_qbank_plural', 'plugin')));
$temp = new admin_settingpage('manageqbanks', new lang_string('manageqbanks', 'admin'));
$temp->add(new \core_question\admin\manage_qbank_plugins_page());
$ADMIN->add('qbanksettings', $temp);
$plugins = core_plugin_manager::instance()->get_plugins_of_type('qbank');
foreach ($plugins as $plugin) {
/** @var \core\plugininfo\qbank $plugin */
$plugin->load_settings($ADMIN, 'qbanksettings', $hassiteconfig);
}
}
// Question type settings
if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) {

View File

@ -1515,6 +1515,7 @@ $string['webproxyinfo'] = 'Fill in the following options if your Moodle server c
$string['xmlrpcrecommended'] = 'The XMLRPC extension is useful for web services and Moodle networking.';
$string['yuicomboloading'] = 'YUI combo loading';
$string['ziprequired'] = 'The Zip PHP extension is now required by Moodle, info-ZIP binaries or PclZip library are not used anymore.';
$string['manageqbanks'] = 'Manage question bank plugins';
$string['caching'] = 'Caching';

View File

@ -192,6 +192,8 @@ $string['type_tool'] = 'Admin tool';
$string['type_tool_plural'] = 'Admin tools';
$string['type_webservice'] = 'Webservice protocol';
$string['type_webservice_plural'] = 'Webservice protocols';
$string['type_qbank'] = 'Question bank plugin';
$string['type_qbank_plural'] = 'Question bank plugins';
$string['updateavailable'] = 'There is a new version {$a} available!';
$string['updateavailable_moreinfo'] = 'More info...';
$string['updateavailable_release'] = 'Release {$a}';

View File

@ -494,3 +494,5 @@ $string['whichtries'] = 'Which tries';
$string['withselected'] = 'With selected';
$string['xoutofmax'] = '{$a->mark} out of {$a->max}';
$string['yougotnright'] = 'You have correctly selected {$a->num}.';
$string['qbanknotfound'] = 'The \'{$a}\' question bank plugin doesn\'t exist or is not recognised.';
$string['noquestionbanks'] = 'No question bank plugin found.';

View File

@ -1938,6 +1938,10 @@ class core_plugin_manager {
'checkbox', 'datetime', 'menu', 'social', 'text', 'textarea'
),
'qbank' => [
''
],
'qbehaviour' => array(
'adaptive', 'adaptivenopenalty', 'deferredcbm',
'deferredfeedback', 'immediatecbm', 'immediatefeedback',
@ -2241,6 +2245,7 @@ class core_plugin_manager {
$fix['mod'] = $types['mod'];
$fix['block'] = $types['block'];
$fix['qtype'] = $types['qtype'];
$fix['qbank'] = $types['qbank'];
$fix['qbehaviour'] = $types['qbehaviour'];
$fix['qformat'] = $types['qformat'];
$fix['filter'] = $types['filter'];

View File

@ -0,0 +1,133 @@
<?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/>.
/**
* Defines classes used for plugin info.
*
* @package core
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\plugininfo;
defined('MOODLE_INTERNAL') || die();
/**
* Base class for qbank plugins.
*
* @package core
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qbank extends base {
public function is_uninstall_allowed(): bool {
return true;
}
public static function get_manage_url(): \moodle_url {
return new \moodle_url('/admin/settings.php', ['section' => 'manageqbanks']);
}
public static function get_plugins($type, $typerootdir, $typeclass, $pluginman): array {
global $CFG;
$qbank = parent::get_plugins($type, $typerootdir, $typeclass, $pluginman);
$order = array_keys($qbank);
$sortedqbanks = [];
foreach ($order as $qbankname) {
$sortedqbanks[$qbankname] = $qbank[$qbankname];
}
return $sortedqbanks;
}
public static function get_enabled_plugins(): ?array {
global $CFG;
$pluginmanager = \core_plugin_manager::instance();
$plugins = $pluginmanager->get_installed_plugins('qbank');
if (!$plugins) {
return [];
}
$plugins = array_keys($plugins);
// Filter to return only enabled plugins.
$enabled = [];
foreach ($plugins as $plugin) {
$qbankinfo = $pluginmanager->get_plugin_info('qbank_'.$plugin);
$qbankavailable = $qbankinfo->get_status();
if ($qbankavailable === \core_plugin_manager::PLUGIN_STATUS_MISSING) {
continue;
}
$disabled = get_config('qbank_' . $plugin, 'disabled');
if (empty($disabled)) {
$enabled[$plugin] = $plugin;
}
}
return $enabled;
}
/**
* Checks if a qbank plugin is ready to be used.
* It checks the plugin status as well as the plugin is missing or not.
*
* @param string $fullpluginname the name of the plugin
* @return bool
*/
public static function is_plugin_enabled($fullpluginname): bool {
$pluginmanager = \core_plugin_manager::instance();
$qbankinfo = $pluginmanager->get_plugin_info($fullpluginname);
if (empty($qbankinfo)) {
return false;
}
$qbankavailable = $qbankinfo->get_status();
if ($qbankavailable === \core_plugin_manager::PLUGIN_STATUS_MISSING ||
!empty(get_config($fullpluginname, 'disabled'))) {
return false;
}
return true;
}
public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig): void {
global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
$ADMIN = $adminroot; // May be used in settings.php.
$plugininfo = $this; // Also can be used inside settings.php.
if (!$this->is_installed_and_upgraded()) {
return;
}
if (!$hassiteconfig) {
return;
}
$section = $this->get_settings_section_name();
$settings = null;
if (file_exists($this->full_path('settings.php'))) {
$settings = new \admin_settingpage($section, $this->displayname,
'moodle/site:config', $this->is_enabled() === false);
include($this->full_path('settings.php')); // This may also set $settings to null.
}
if ($settings) {
$ADMIN->add($parentnodename, $settings);
}
}
}

View File

@ -28,6 +28,7 @@
"repository": "repository",
"portfolio": "portfolio",
"search": "search\/engine",
"qbank": "question\/bank",
"qbehaviour": "question\/behaviour",
"qformat": "question\/format",
"plagiarism": "plagiarism",

View File

@ -1756,19 +1756,23 @@ function question_edit_url($context) {
}
/**
* Adds question bank setting links to the given navigation node if caps are met.
* Adds question bank setting links to the given navigation node if caps are met
* and loads the navigation from the plugins.
* Qbank plugins can extend the navigation_plugin_base and add their own navigation node,
* this method will help to autoload those nodes in the question bank navigation.
*
* @param navigation_node $navigationnode The navigation node to add the question branch to
* @param object $context
* @param string $baseurl the url of the base where the api is implemented from
* @return navigation_node Returns the question branch that was added
*/
function question_extend_settings_navigation(navigation_node $navigationnode, $context) {
function question_extend_settings_navigation(navigation_node $navigationnode, $context, $baseurl = '/question/edit.php') {
global $PAGE;
if ($context->contextlevel == CONTEXT_COURSE) {
$params = array('courseid'=>$context->instanceid);
$params = ['courseid' => $context->instanceid];
} else if ($context->contextlevel == CONTEXT_MODULE) {
$params = array('cmid'=>$context->instanceid);
$params = ['cmid' => $context->instanceid];
} else {
return;
}
@ -1778,24 +1782,84 @@ function question_extend_settings_navigation(navigation_node $navigationnode, $c
}
$questionnode = $navigationnode->add(get_string('questionbank', 'question'),
new moodle_url('/question/edit.php', $params), navigation_node::TYPE_CONTAINER, null, 'questionbank');
new moodle_url($baseurl, $params), navigation_node::TYPE_CONTAINER, null, 'questionbank');
$corenavigations = [
'questions' => [
'title' => get_string('questions', 'question'),
'url' => new moodle_url($baseurl)
],
'categories' => [
'title' => get_string('categories', 'question'),
'url' => new moodle_url('/question/category.php')
],
'import' => [
'title' => get_string('import', 'question'),
'url' => new moodle_url('/question/import.php')
],
'export' => [
'title' => get_string('export', 'question'),
'url' => new moodle_url('/question/export.php')
]
];
$plugins = \core_component::get_plugin_list_with_class('qbank', 'plugin_feature', 'plugin_feature.php');
foreach ($plugins as $componentname => $plugin) {
$pluginentrypoint = new $plugin();
$pluginentrypointobject = $pluginentrypoint->get_navigation_node();
// Don't need the plugins without navigation node.
if ($pluginentrypointobject === null) {
unset($plugins[$componentname]);
continue;
}
foreach ($corenavigations as $key => $corenavigation) {
if ($pluginentrypointobject->get_navigation_key() === $key) {
unset($plugins[$componentname]);
if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) {
unset($corenavigations[$key]);
break;
}
$corenavigations[$key] = [
'title' => $pluginentrypointobject->get_navigation_title(),
'url' => $pluginentrypointobject->get_navigation_url()
];
}
}
}
// Community/additional plugins have navigation node.
$pluginnavigations = [];
foreach ($plugins as $componentname => $plugin) {
$pluginentrypoin = new $plugin();
$pluginentrypointobject = $pluginentrypoin->get_navigation_node();
if (!\core\plugininfo\qbank::is_plugin_enabled($componentname)) {
unset($corenavigations[$key]);
continue;
}
$pluginnavigations[$pluginentrypointobject->get_navigation_key()] = [
'title' => $pluginentrypointobject->get_navigation_title(),
'url' => $pluginentrypointobject->get_navigation_url(),
'capabilities' => $pluginentrypointobject->get_navigation_capabilities()
];
}
$contexts = new question_edit_contexts($context);
if ($contexts->have_one_edit_tab_cap('questions')) {
$questionnode->add(get_string('questions', 'question'), new moodle_url(
'/question/edit.php', $params), navigation_node::TYPE_SETTING, null, 'questions');
foreach ($corenavigations as $key => $corenavigation) {
if ($contexts->have_one_edit_tab_cap($key)) {
$questionnode->add($corenavigation['title'], new moodle_url(
$corenavigation['url'], $params), navigation_node::TYPE_SETTING, null, $key);
}
}
if ($contexts->have_one_edit_tab_cap('categories')) {
$questionnode->add(get_string('categories', 'question'), new moodle_url(
'/question/category.php', $params), navigation_node::TYPE_SETTING, null, 'categories');
}
if ($contexts->have_one_edit_tab_cap('import')) {
$questionnode->add(get_string('import', 'question'), new moodle_url(
'/question/import.php', $params), navigation_node::TYPE_SETTING, null, 'import');
}
if ($contexts->have_one_edit_tab_cap('export')) {
$questionnode->add(get_string('export', 'question'), new moodle_url(
'/question/export.php', $params), navigation_node::TYPE_SETTING, null, 'export');
foreach ($pluginnavigations as $key => $pluginnavigation) {
if (is_array($pluginnavigation['capabilities'])) {
if (!$contexts->have_one_cap($pluginnavigation['capabilities'])) {
continue;
}
}
$questionnode->add($pluginnavigation['title'], new moodle_url(
$pluginnavigation['url'], $params), navigation_node::TYPE_SETTING, null, $key);
}
return $questionnode;

View File

@ -0,0 +1,76 @@
<?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/>.
/**
* A column type for the name of the question name.
*
* @package core_question
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\question\bank;
defined('MOODLE_INTERNAL') || die();
/**
* A column type for the name of the question name.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class question_name_column extends \core_question\bank\column_base {
protected $checkboxespresent = null;
public function get_name() {
return 'questionname';
}
protected function get_title() {
return get_string('question');
}
protected function label_for($question) {
if (is_null($this->checkboxespresent)) {
$this->checkboxespresent = $this->qbank->has_column('core_question\bank\checkbox_column');
}
if ($this->checkboxespresent) {
return 'checkq' . $question->id;
} else {
return '';
}
}
protected function display_content($question, $rowclasses) {
$labelfor = $this->label_for($question);
if ($labelfor) {
echo '<label for="' . $labelfor . '">';
}
echo format_string($question->name);
if ($labelfor) {
echo '</label>';
}
}
public function get_required_fields() {
return array('q.id', 'q.name');
}
public function is_sortable() {
return 'q.name';
}
}

View File

@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_name_text_column extends \core_question\bank\question_name_column {
class question_name_text_column extends question_name_column {
public function get_name() {
return 'questionnametext';
}

View File

@ -0,0 +1,2 @@
This file describes core qbank plugin changes in /question/bank/*,
information provided here is intended especially for developers.

View File

@ -133,14 +133,15 @@ class question_category_list_item extends list_item {
}
public function item_html($extraargs = array()){
global $CFG, $OUTPUT;
global $CFG, $PAGE, $OUTPUT;
$str = $extraargs['str'];
$category = $this->item;
$editqestions = get_string('editquestions', 'question');
// Each section adds html to be displayed as part of this list item.
$questionbankurl = new moodle_url('/question/edit.php', $this->parentlist->pageurl->params());
$nodeparent = $PAGE->settingsnav->find('questionbank', \navigation_node::TYPE_CONTAINER);
$questionbankurl = new moodle_url($nodeparent->action->get_path(), $this->parentlist->pageurl->params());
$questionbankurl->param('cat', $category->id . ',' . $category->contextid);
$item = '';
$text = format_string($category->name, true, ['context' => $this->parentlist->context]);

View File

@ -0,0 +1,140 @@
<?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/>.
/**
* Manage question banks page.
*
* @package core_question
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\admin;
/**
* Class manage_qbank_plugins_page.
*
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manage_qbank_plugins_page extends \admin_setting {
/**
* Class admin_page_manageqbanks constructor.
*/
public function __construct() {
$this->nosave = true;
parent::__construct('manageqbanks',
new \lang_string('manageqbanks', 'admin'), '', '');
}
public function get_setting(): bool {
return true;
}
public function get_defaultsetting(): bool {
return true;
}
public function write_setting($data): string {
// Do not write any setting.
return '';
}
public function is_related($query): bool {
if (parent::is_related($query)) {
return true;
}
$types = \core_plugin_manager::instance()->get_plugins_of_type('qbank');
foreach ($types as $type) {
if (strpos($type->component, $query) !== false ||
strpos(\core_text::strtolower($type->displayname), $query) !== false) {
return true;
}
}
return false;
}
public function output_html($data, $query = ''): string {
global $CFG, $OUTPUT;
$return = '';
$pluginmanager = \core_plugin_manager::instance();
$types = $pluginmanager->get_plugins_of_type('qbank');
if (empty($types)) {
return get_string('noquestionbanks', 'question');
}
$txt = get_strings(['settings', 'name', 'enable', 'disable', 'default']);
$txt->uninstall = get_string('uninstallplugin', 'core_admin');
$table = new \html_table();
$table->head = [$txt->name, $txt->enable, $txt->settings, $txt->uninstall];
$table->align = ['left', 'center', 'center', 'center', 'center'];
$table->attributes['class'] = 'manageqbanktable generaltable admintable';
$table->data = [];
$totalenabled = 0;
$count = 0;
foreach ($types as $type) {
if ($type->is_enabled() && $type->is_installed_and_upgraded()) {
$totalenabled++;
}
}
foreach ($types as $type) {
$url = new \moodle_url('/admin/qbankplugins.php', ['sesskey' => sesskey(), 'name' => $type->name]);
$class = '';
if ($pluginmanager->get_plugin_info('qbank_'.$type->name)->get_status() ===
\core_plugin_manager::PLUGIN_STATUS_MISSING) {
$strtypename = $type->displayname.' ('.get_string('missingfromdisk').')';
} else {
$strtypename = $type->displayname;
}
if ($type->is_enabled()) {
$hideshow = \html_writer::link($url->out(false, ['action' => 'disable']),
$OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', ['class' => 'iconsmall']));
} else {
$class = 'dimmed_text';
$hideshow = \html_writer::link($url->out(false, ['action' => 'enable']),
$OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', ['class' => 'iconsmall']));
}
$settings = '';
if ($type->get_settings_url()) {
$settings = \html_writer::link($type->get_settings_url(), $txt->settings);
}
$uninstall = '';
if ($uninstallurl = \core_plugin_manager::instance()->get_uninstall_url(
'qbank_'.$type->name, 'manage')) {
$uninstall = \html_writer::link($uninstallurl, $txt->uninstall);
}
$row = new \html_table_row([$strtypename, $hideshow, $settings, $uninstall]);
if ($class) {
$row->attributes['class'] = $class;
}
$table->data[] = $row;
$count++;
}
$return .= \html_writer::table($table);
return highlight($query, $return);
}
}

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
abstract class action_column_base extends column_base {

View File

@ -32,6 +32,7 @@ use core\output\checkbox_toggleall;
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
class checkbox_column extends column_base {

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Base class for representing a column in a {@link question_bank_view}.
* Base class for representing a column in a {@see question_bank_view}.
*
* @package core_question
* @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
@ -27,12 +27,14 @@ defined('MOODLE_INTERNAL') || die();
/**
* Base class for representing a column in a {@link question_bank_view}.
* Base class for representing a column in a {@see question_bank_view}.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
abstract class column_base {
/**
* @var view $qbank the question bank view we are helping to render.
*/
@ -42,10 +44,10 @@ abstract class column_base {
protected $isheading = false;
/**
* Constructor.
* @param view $qbank the question bank view we are helping to render.
* Constructor for the question bank view we are helping to render.
* @param view $qbank
*/
public function __construct(view $qbank) {
public function __construct($qbank) {
$this->qbank = $qbank;
$this->init();
}
@ -68,6 +70,27 @@ abstract class column_base {
return false;
}
/**
* Check if the row has an extra preference to view/hide.
*/
public function has_preference(): bool {
return false;
}
/**
* Get if the preference key of the row.
*/
public function get_preference_key(): string {
return '';
}
/**
* Get if the preference of the row.
*/
public function get_preference(): bool {
return false;
}
/**
* Output the column header cell.
*/
@ -272,7 +295,7 @@ abstract class column_base {
/**
* Load the tags for each question.
*
* Helper that can be used from {@link load_additional_data()};
* Helper that can be used from {@see load_additional_data()};
*
* @param array $questions
*/

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class copy_action_column extends menu_action_column_base {
/** @var string avoids repeated calls to get_string('duplicate'). */

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class creator_name_column extends column_base {
public function get_name() {

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class delete_action_column extends menu_action_column_base {
protected $strdelete;

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class edit_action_column extends menu_action_column_base {
protected $stredit;

View File

@ -36,6 +36,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
class edit_menu_column extends column_base {
/**

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class export_xml_action_column extends menu_action_column_base {
/** @var string avoids repeated calls to get_string('duplicate'). */

View File

@ -36,6 +36,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2019 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
abstract class menu_action_column_base extends action_column_base implements menuable_action {

View File

@ -43,6 +43,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2019 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
interface menuable_action {

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class modifier_name_column extends column_base {
public function get_name() {

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class preview_action_column extends action_column_base implements menuable_action {
/**

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class question_name_column extends column_base {
protected $checkboxespresent = null;
@ -45,7 +46,7 @@ class question_name_column extends column_base {
protected function label_for($question) {
if (is_null($this->checkboxespresent)) {
$this->checkboxespresent = $this->qbank->has_column('core_question\bank\checkbox_column');
$this->checkboxespresent = $this->qbank->has_column('core_question\local\bank\checkbox_column');
}
if ($this->checkboxespresent) {
return 'checkq' . $question->id;

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class question_name_idnumber_tags_column extends question_name_column {
public function get_name() {

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class question_text_row extends row_base {
protected $formatoptions;

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class question_type_column extends column_base {
public function get_name() {

View File

@ -41,6 +41,7 @@ namespace core_question\bank;
*
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
class random_question_loader {
/** @var \qubaid_condition which usages to consider previous attempts from. */

View File

@ -31,6 +31,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
abstract class row_base extends column_base {
public function is_extra_row() {

View File

@ -24,12 +24,12 @@
*/
namespace core_question\bank\search;
defined('MOODLE_INTERNAL') || die();
/**
* This class controls from which category questions are listed.
*
* @copyright 2013 Ray Morris
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category_condition extends condition {
@ -87,16 +87,25 @@ class category_condition extends condition {
if ($this->recurse) {
$categoryids = question_categorylist($this->category->id);
} else {
$categoryids = array($this->category->id);
$categoryids = [$this->category->id];
}
list($catidtest, $this->params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cat');
$this->where = 'q.category ' . $catidtest;
}
/**
* SQL fragment to add to the where clause.
*
* @return string
*/
public function where() {
return $this->where;
}
/**
* Return parameters to be bound to the above WHERE clause fragment.
* @return array parameter name => value.
*/
public function params() {
return $this->params;
}
@ -105,8 +114,14 @@ class category_condition extends condition {
* Called by question_bank_view to display the GUI for selecting a category
*/
public function display_options() {
$this->display_category_form($this->contexts, $this->baseurl, $this->cat);
$this->print_category_info($this->category);
global $PAGE;
$displaydata = [];
$catmenu = question_category_options($this->contexts, true, 0,
true, -1, false);
$displaydata['categoryselect'] = \html_writer::select($catmenu, 'category', $this->cat, [],
array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory'));
$displaydata['categorydesc'] = $this->print_category_info($this->category);
return $PAGE->get_renderer('core_question', 'bank')->render_category_condition($displaydata);
}
/**
@ -114,12 +129,12 @@ class category_condition extends condition {
* question_bank_view places this within the section that is hidden by default
*/
public function display_options_adv() {
echo \html_writer::start_div();
echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'recurse',
'value' => 0, 'id' => 'recurse_off'));
echo \html_writer::checkbox('recurse', '1', $this->recurse, get_string('includesubcategories', 'question'),
array('id' => 'recurse_on', 'class' => 'searchoptions mr-1'));
echo \html_writer::end_div() . "\n";
global $PAGE;
$displaydata = [];
if ($this->recurse) {
$displaydata['checked'] = 'checked="true"';
}
return $PAGE->get_renderer('core_question', 'bank')->render_category_condition_advanced($displaydata);
}
/**
@ -128,12 +143,15 @@ class category_condition extends condition {
* @param array $contexts of contexts that can be accessed from here.
* @param \moodle_url $pageurl the URL of this page.
* @param string $current 'categoryID,contextID'.
* @deprecated since Moodle 4.0
*/
protected function display_category_form($contexts, $pageurl, $current) {
debugging('Function display_category_form() is deprecated,
please use the core_question renderer instead.', DEBUG_DEVELOPER);
echo \html_writer::start_div('choosecategory');
$catmenu = question_category_options($contexts, true, 0, true, -1, false);
echo \html_writer::label(get_string('selectacategory', 'question'), 'id_selectacategory', true, array("class" => "mr-1"));
echo \html_writer::select($catmenu, 'category', $current, array(),
echo \html_writer::label(get_string('selectacategory', 'question'), 'id_selectacategory', true, ["class" => "mr-1"]);
echo \html_writer::select($catmenu, 'category', $current, [],
array('class' => 'searchoptions custom-select', 'id' => 'id_selectacategory'));
echo \html_writer::end_div() . "\n";
}
@ -151,8 +169,7 @@ class category_condition extends condition {
return false;
}
if (!$category = $DB->get_record('question_categories',
array('id' => $categoryid, 'contextid' => $contextid))) {
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();
@ -164,19 +181,17 @@ class category_condition extends condition {
/**
* Print the category description
* @param stdClass $category the category information form the database.
* @param \stdClass $category the category information form the database.
*/
protected function print_category_info($category) {
protected function print_category_info($category): string {
$formatoptions = new \stdClass();
$formatoptions->noclean = true;
$formatoptions->overflowdiv = true;
echo \html_writer::start_div('boxaligncenter categoryinfo pl-0');
if (isset($this->maxinfolength)) {
echo shorten_text(format_text($category->info, $category->infoformat, $formatoptions, $this->course->id),
$this->maxinfolength);
return shorten_text(format_text($category->info, $category->infoformat, $formatoptions, $this->course->id),
$this->maxinfolength);
} else {
echo format_text($category->info, $category->infoformat, $formatoptions, $this->course->id);
return format_text($category->info, $category->infoformat, $formatoptions, $this->course->id);
}
echo \html_writer::end_div() . "\n";
}
}

View File

@ -24,12 +24,11 @@
*/
namespace core_question\bank\search;
defined('MOODLE_INTERNAL') || die();
/**
* An abstract class for filtering/searching questions.
*
* See also {@link question_bank_view::init_search_conditions()}.
* See also {@see question_bank_view::init_search_conditions()}.
* @copyright 2013 Ray Morris
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@ -38,33 +37,33 @@ abstract class condition {
* Return an SQL fragment to be ANDed into the WHERE clause to filter which questions are shown.
* @return string SQL fragment. Must use named parameters.
*/
public abstract function where();
abstract public function where();
/**
* Return parameters to be bound to the above WHERE clause fragment.
* @return array parameter name => value.
*/
public function params() {
return array();
return [];
}
/**
* Display GUI for selecting criteria for this condition. Displayed when Show More is open.
*
* Compare display_options(), which displays always, whether Show More is open or not.
* @return string HTML form fragment
* @return bool|string HTML form fragment
*/
public function display_options_adv() {
return;
return false;
}
/**
* Display GUI for selecting criteria for this condition. Displayed always, whether Show More is open or not.
*
* Compare display_options_adv(), which displays when Show More is open.
* @return string HTML form fragment
* @return bool|string HTML form fragment
*/
public function display_options() {
return;
return false;
}
}

View File

@ -24,12 +24,12 @@
*/
namespace core_question\bank\search;
defined('MOODLE_INTERNAL') || die();
/**
* This class controls whether hidden / deleted questions are hidden in the list.
*
* @copyright 2013 Ray Morris
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class hidden_condition extends condition {
@ -50,6 +50,11 @@ class hidden_condition extends condition {
}
}
/**
* SQL fragment to add to the where clause.
*
* @return string
*/
public function where() {
return $this->where;
}
@ -58,11 +63,11 @@ class hidden_condition extends condition {
* Print HTML to display the "Also show old questions" checkbox
*/
public function display_options_adv() {
echo \html_writer::start_div();
echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'showhidden',
'value' => '0', 'id' => 'showhidden_off'));
echo \html_writer::checkbox('showhidden', '1', (! $this->hide), get_string('showhidden', 'question'),
array('id' => 'showhidden_on', 'class' => 'searchoptions mr-1'));
echo \html_writer::end_div() . "\n";
global $PAGE;
$displaydata = [];
if (!$this->hide) {
$displaydata['checked'] = 'checked="true"';
}
return $PAGE->get_renderer('core_question', 'bank')->render_hidden_condition_advanced($displaydata);
}
}

View File

@ -23,7 +23,6 @@
*/
namespace core_question\bank\search;
defined('MOODLE_INTERNAL') || die();
/**
* Question bank search class to allow searching/filtering by tags on a question.
@ -32,17 +31,23 @@ defined('MOODLE_INTERNAL') || die();
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_condition extends condition {
/** @var string SQL fragment to add to the where clause. */
protected $where;
/** @var string SQL fragment to add to the where clause. */
protected $contexts;
/** @var array List of IDs for tags that have been selected in the form. */
protected $selectedtagids;
/**
* Constructor.
* @param context[] $contexts List of contexts to show tags from
*
* @param array $contexts List of contexts to show tags from
* @param int[] $selectedtagids List of IDs for tags to filter by.
* @throws \coding_exception
* @throws \dml_exception
*/
public function __construct(array $contexts, array $selectedtagids = []) {
global $DB;
@ -118,6 +123,6 @@ class tag_condition extends condition {
'tagoptions' => $tagoptions
];
echo $OUTPUT->render_from_template('core_question/tag_condition', $context);
return $OUTPUT->render_from_template('core_question/tag_condition', $context);
}
}

View File

@ -30,6 +30,7 @@ defined('MOODLE_INTERNAL') || die();
*
* @copyright 2018 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to the plugin
*/
class tags_action_column extends action_column_base implements menuable_action {
/**
@ -39,6 +40,11 @@ class tags_action_column extends action_column_base implements menuable_action {
public function init() {
parent::init();
global $CFG;
if ($CFG->usetags) {
global $PAGE;
$PAGE->requires->js_call_amd('core_question/edit_tags', 'init', ['#questionscontainer']);
}
$this->managetags = get_string('managetags', 'tag');
}

View File

@ -48,6 +48,7 @@ use core_question\bank\search\condition;
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-72004 delete the class and add it to lib/db/renameclasses.php pointing to core_question/local/bank
*/
class view {
const MAX_SORTS = 3;

View File

@ -285,7 +285,7 @@ class core_question_external extends external_api {
// The user must be able to view all questions in the category that they are requesting.
$editcontexts->require_cap('moodle/question:viewall');
$loader = new \core_question\bank\random_question_loader(new qubaid_list([]));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
// Only load the properties we require from the DB.
$properties = \core_question\external\question_summary_exporter::get_mandatory_properties();
$questions = $loader->get_questions($categoryid, $includesubcategories, $tagids, $limit, $offset, $properties);

View File

@ -0,0 +1,60 @@
<?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/>.
/**
* A base class for actions that are an icon that lets you manipulate the question in some way.
*
* @package core_question
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* A base class for actions that are an icon that lets you manipulate the question in some way.
*
* @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
*/
abstract class action_column_base extends column_base {
protected function get_title(): string {
return '&#160;';
}
public function get_extra_classes(): array {
return ['iconcol'];
}
protected function print_icon($icon, $title, $url): void {
global $OUTPUT;
echo \html_writer::tag('a', $OUTPUT->pix_icon($icon, $title), ['title' => $title, 'href' => $url]);
}
public function get_extra_joins(): array {
return ['qc' => 'JOIN {question_categories} qc ON qc.id = q.category'];
}
public function get_required_fields(): array {
// Createdby is required for permission checks.
// Qtype so we can easily avoid applying actions to question types that
// are no longer installed.
return ['q.id', 'q.qtype', 'q.createdby', 'qc.contextid'];
}
}

View File

@ -0,0 +1,78 @@
<?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/>.
/**
* A column with a checkbox for each question with name q{questionid}.
*
* @package core_question
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
use core\output\checkbox_toggleall;
/**
* A column with a checkbox for each question with name q{questionid}.
*
* @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 checkbox_column extends column_base {
public function get_name(): string {
return 'checkbox';
}
protected function get_title() {
global $OUTPUT;
$mastercheckbox = new checkbox_toggleall('qbank', true, [
'id' => 'qbheadercheckbox',
'name' => 'qbheadercheckbox',
'value' => '1',
'label' => get_string('selectall'),
'labelclasses' => 'accesshide',
]);
return $OUTPUT->render($mastercheckbox);
}
protected function get_title_tip() {
return get_string('selectquestionsforbulk', 'question');
}
protected function display_content($question, $rowclasses): void {
global $OUTPUT;
$checkbox = new checkbox_toggleall('qbank', false, [
'id' => "checkq{$question->id}",
'name' => "q{$question->id}",
'value' => '1',
'label' => get_string('select'),
'labelclasses' => 'accesshide',
]);
echo $OUTPUT->render($checkbox);
}
public function get_required_fields(): array {
return ['q.id'];
}
}

View File

@ -0,0 +1,393 @@
<?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/>.
/**
* Base class for representing a column.
*
* @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;
/**
* Base class for representing a column.
*
* @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
*/
abstract class column_base {
/**
* @var view $qbank the question bank view we are helping to render.
*/
protected $qbank;
/** @var bool determine whether the column is td or th. */
protected $isheading = false;
/**
* Constructor.
* @param view $qbank the question bank view we are helping to render.
*/
public function __construct(view $qbank) {
$this->qbank = $qbank;
$this->init();
}
/**
* A chance for subclasses to initialise themselves, for example to load lang strings,
* without having to override the constructor.
*/
protected function init(): void {
}
/**
* Set the column as heading
*/
public function set_as_heading(): void {
$this->isheading = true;
}
/**
* Check if the column is an extra row of not.
*/
public function is_extra_row(): bool {
return false;
}
/**
* Check if the row has an extra preference to view/hide.
*/
public function has_preference(): bool {
return false;
}
/**
* Get if the preference key of the row.
*/
public function get_preference_key(): string {
return '';
}
/**
* Get if the preference of the row.
*/
public function get_preference(): bool {
return false;
}
/**
* Output the column header cell.
*/
public function display_header(): void {
global $PAGE;
$data = [];
$data['sortable'] = true;
$data['extraclasses'] = $this->get_classes();
$sortable = $this->is_sortable();
$name = get_class($this);
$title = $this->get_title();
$tip = $this->get_title_tip();
$links = [];
if (is_array($sortable)) {
if ($title) {
$data['title'] = $title;
}
foreach ($sortable as $subsort => $details) {
$links[] = $this->make_sort_link($name . '-' . $subsort,
$details['title'], isset($details['tip']) ? $details['tip'] : '', !empty($details['reverse']));
}
$data['sortlinks'] = implode(' / ', $links);
} else if ($sortable) {
$data['sortlinks'] = $this->make_sort_link($name, $title, $tip);
} else {
$data['sortable'] = false;
$data['tiptitle'] = $title;
if ($tip) {
$data['sorttip'] = true;
$data['tip'] = $tip;
}
}
$renderer = $PAGE->get_renderer('core_question', 'bank');
echo $renderer->render_column_header($data);
}
/**
* Title for this column. Not used if is_sortable returns an array.
*/
abstract protected function get_title();
/**
* Use this when get_title() returns
* something very short, and you want a longer version as a tool tip.
*
* @return string a fuller version of the name.
*/
protected function get_title_tip() {
return '';
}
/**
* Get a link that changes the sort order, and indicates the current sort state.
* @param string $sort the column to sort on.
* @param string $title the link text.
* @param string $tip the link tool-tip text. If empty, defaults to title.
* @param bool $defaultreverse whether the default sort order for this column is descending, rather than ascending.
* @return string
*/
protected function make_sort_link($sort, $title, $tip, $defaultreverse = false): string {
global $PAGE;
$sortdata = [];
$currentsort = $this->qbank->get_primary_sort_order($sort);
$newsortreverse = $defaultreverse;
if ($currentsort) {
$newsortreverse = $currentsort > 0;
}
if (!$tip) {
$tip = $title;
}
if ($newsortreverse) {
$tip = get_string('sortbyxreverse', '', $tip);
} else {
$tip = get_string('sortbyx', '', $tip);
}
$link = $title;
if ($currentsort) {
$link .= $this->get_sort_icon($currentsort < 0);
}
$sortdata['sorturl'] = $this->qbank->new_sort_url($sort, $newsortreverse);
$sortdata['sortcontent'] = $link;
$sortdata['sorttip'] = $tip;
$renderer = $PAGE->get_renderer('core_question', 'bank');
return $renderer->render_column_sort($sortdata);
}
/**
* Get an icon representing the corrent sort state.
* @param bool $reverse sort is descending, not ascending.
* @return string HTML image tag.
*/
protected function get_sort_icon($reverse): string {
global $OUTPUT;
if ($reverse) {
return $OUTPUT->pix_icon('t/sort_desc', get_string('desc'), '', ['class' => 'iconsort']);
} else {
return $OUTPUT->pix_icon('t/sort_asc', get_string('asc'), '', ['class' => 'iconsort']);
}
}
/**
* Output this column.
* @param object $question the row from the $question table, augmented with extra information.
* @param string $rowclasses CSS class names that should be applied to this row of output.
*/
public function display($question, $rowclasses): void {
$this->display_start($question, $rowclasses);
$this->display_content($question, $rowclasses);
$this->display_end($question, $rowclasses);
}
/**
* Output the opening column tag. If it is set as heading, it will use <th> tag instead of <td>
*
* @param \stdClass $question
* @param string $rowclasses
*/
protected function display_start($question, $rowclasses): void {
$tag = 'td';
$attr = ['class' => $this->get_classes()];
if ($this->isheading) {
$tag = 'th';
$attr['scope'] = 'row';
}
echo \html_writer::start_tag($tag, $attr);
}
/**
* The CSS classes to apply to every cell in this column.
*
* @return string
*/
protected function get_classes(): string {
$classes = $this->get_extra_classes();
$classes[] = $this->get_name();
return implode(' ', $classes);
}
/**
* Get the internal name for this column. Used as a CSS class name,
* and to store information about the current sort. Must match PARAM_ALPHA.
*
* @return string column name.
*/
abstract public function get_name();
/**
* Any extra class names you would like applied to every cell in this column.
*
* @return array
*/
public function get_extra_classes(): array {
return [];
}
/**
* Output the contents of this column.
* @param object $question the row from the $question table, augmented with extra information.
* @param string $rowclasses CSS class names that should be applied to this row of output.
*/
abstract protected function display_content($question, $rowclasses);
/**
* Output the closing column tag
*
* @param object $question
* @param string $rowclasses
*/
protected function display_end($question, $rowclasses): void {
$tag = 'td';
if ($this->isheading) {
$tag = 'th';
}
echo \html_writer::end_tag($tag);
}
/**
* Return an array 'table_alias' => 'JOIN clause' to bring in any data that
* this column required.
*
* The return values for all the columns will be checked. It is OK if two
* columns join in the same table with the same alias and identical JOIN clauses.
* If to columns try to use the same alias with different joins, you get an error.
* The only table included by default is the question table, which is aliased to 'q'.
*
* It is importnat that your join simply adds additional data (or NULLs) to the
* existing rows of the query. It must not cause additional rows.
*
* @return array 'table_alias' => 'JOIN clause'
*/
public function get_extra_joins(): array {
return [];
}
/**
* Use table alias 'q' for the question table, or one of the
* ones from get_extra_joins. Every field requested must specify a table prefix.
*
* @return array fields required.
*/
public function get_required_fields(): array {
return [];
}
/**
* If this column needs extra data (e.g. tags) then load that here.
*
* The extra data should be added to the question object in the array.
* Probably a good idea to check that another column has not already
* loaded the data you want.
*
* @param \stdClass[] $questions the questions that will be displayed.
*/
public function load_additional_data(array $questions) {
}
/**
* Load the tags for each question.
*
* Helper that can be used from {@see load_additional_data()};
*
* @param array $questions
*/
public function load_question_tags(array $questions): void {
$firstquestion = reset($questions);
if (isset($firstquestion->tags)) {
// Looks like tags are already loaded, so don't do it again.
return;
}
// Load the tags.
$tagdata = \core_tag_tag::get_items_tags('core_question', 'question',
array_keys($questions));
// Add them to the question objects.
foreach ($tagdata as $questionid => $tags) {
$questions[$questionid]->tags = $tags;
}
}
/**
* Can this column be sorted on? You can return either:
* + false for no (the default),
* + a field name, if sorting this column corresponds to sorting on that datbase field.
* + an array of subnames to sort on as follows
* return [
* 'firstname' => ['field' => 'uc.firstname', 'title' => get_string('firstname')],
* 'lastname' => ['field' => 'uc.lastname', 'title' => get_string('lastname')],
* ];
* As well as field, and field, you can also add 'revers' => 1 if you want the default sort
* order to be DESC.
* @return mixed as above.
*/
public function is_sortable() {
return false;
}
/**
* Helper method for building sort clauses.
* @param bool $reverse whether the normal direction should be reversed.
* @return string 'ASC' or 'DESC'
*/
protected function sortorder($reverse): string {
if ($reverse) {
return ' DESC';
} else {
return ' ASC';
}
}
/**
* Sorts the expressions.
*
* @param bool $reverse Whether to sort in the reverse of the default sort order.
* @param string $subsort if is_sortable returns an array of subnames, then this will be
* one of those. Otherwise will be empty.
* @return string some SQL to go in the order by clause.
*/
public function sort_expression($reverse, $subsort): string {
$sortable = $this->is_sortable();
if (is_array($sortable)) {
if (array_key_exists($subsort, $sortable)) {
return $sortable[$subsort]['field'] . $this->sortorder($reverse);
} else {
throw new \coding_exception('Unexpected $subsort type: ' . $subsort);
}
} else if ($sortable) {
return $sortable . $this->sortorder($reverse);
} else {
throw new \coding_exception('sort_expression called on a non-sortable column.');
}
}
}

View File

@ -0,0 +1,102 @@
<?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/>.
/**
* A question bank column which gathers together all the actions into a menu.
*
* @package core_question
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* A question bank column which gathers together all the actions into a menu.
*
* This question bank column, if added to the question bank, will
* replace all of the other columns which implement the
* {@see menuable_action} interface and replace them with a single
* column containing an Edit menu.
*
* @copyright 2019 The Open University
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class edit_menu_column extends column_base {
/**
* @var menuable_action[]
*/
protected $actions;
/**
* Set up the list of actions that should be shown in the menu.
*
* This takes a list of column object (the list from a question
* bank view). It extracts all the ones that should go in the menu
* and stores them for later use. Then it returns the remaining columns.
*
* @param column_base[] $allcolumns a set of columns.
* @return column_base[] the non-action columns from the set.
* @todo MDL-72004 changes for class renaming.
*/
public function claim_menuable_columns($allcolumns): array {
$remainingcolumns = [];
foreach ($allcolumns as $key => $column) {
if ($column instanceof menuable_action || $column instanceof \core_question\bank\menuable_action) {
$this->actions[$key] = $column;
} else {
$remainingcolumns[$key] = $column;
}
}
return $remainingcolumns;
}
protected function get_title() {
return get_string('actions');
}
public function get_name(): string {
return 'editmenu';
}
protected function display_content($question, $rowclasses): void {
global $OUTPUT;
$menu = new \action_menu();
$menu->set_menu_trigger(get_string('edit'));
$menu->set_alignment(\action_menu::TL, \action_menu::BL);
foreach ($this->actions as $actioncolumn) {
$action = $actioncolumn->get_action_menu_link($question);
if ($action) {
$menu->add($action);
}
}
$qtypeactions = \question_bank::get_qtype($question->qtype, false)
->get_extra_question_bank_actions($question);
foreach ($qtypeactions as $action) {
$menu->add($action);
}
echo $OUTPUT->render($menu);
}
public function get_required_fields():array {
return ['q.qtype'];
}
}

View File

@ -0,0 +1,55 @@
<?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/>.
/**
* Helper class for question bank and its plugins.
*
* All the functions which has a potential to be used by different features or
* plugins, should go here.
*
* @package core_question
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* Class helper
*
* @package core_question
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Check the status of a plugin and throw exception if not enabled and called manually.
*
* Any action plugin having a php script, should call this function for a safer enable/disable implementation.
*
* @param string $pluginname
* @return void
*/
public static function require_plugin_enabled(string $pluginname): void {
if (!\core\plugininfo\qbank::is_plugin_enabled($pluginname)) {
throw new \moodle_exception('The following plugin is either disabled or missing from disk: ' . $pluginname);
}
}
}

View File

@ -0,0 +1,69 @@
<?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/>.
/**
* Base class to make it easier to implement actions that are menuable_actions.
*
* @package core_question
* @copyright 2019 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* Base class to make it easier to implement actions that are menuable_actions.
*
* Use this class if your action is simple (defined by just a URL, label and icon).
* If your action is not simple enough to fit into the pattern that this
* class implements, then you will have to implement the menuable_action
* interface yourself.
*
* @copyright 2019 Tim Hunt
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class menu_action_column_base extends action_column_base implements menuable_action {
/**
* Get the information required to display this action either as a menu item or a separate action column.
*
* If this action cannot apply to this question (e.g. because the user does not have
* permission, then return [null, null, null].
*
* @param \stdClass $question the row from the $question table, augmented with extra information.
* @return array with three elements.
* $url - the URL to perform the action.
* $icon - the icon for this action. E.g. 't/delete'.
* $label - text label to display in the UI (either in the menu, or as a tool-tip on the icon)
*/
abstract protected function get_url_icon_and_label(\stdClass $question);
protected function display_content($question, $rowclasses): void {
[$url, $icon, $label] = $this->get_url_icon_and_label($question);
if ($url) {
$this->print_icon($icon, $label, $url);
}
}
public function get_action_menu_link(\stdClass $question): ?\action_menu_link {
[$url, $icon, $label] = $this->get_url_icon_and_label($question);
if (!$url) {
return null;
}
return new \action_menu_link_secondary($url, new \pix_icon($icon, ''), $label);
}
}

View File

@ -0,0 +1,54 @@
<?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/>.
/**
* Interface to indicate that a question bank column can go in the action menu.
*
* @package core_question
* @copyright 2019 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* Interface to indicate that a question bank column can go in the action menu.
*
* If a question bank column implements this interface, and if the {@see edit_menu_column}
* is present in the question bank view, then the 'column' will be shown as an entry in the
* edit menu instead of as a separate column.
*
* Probably most columns that want to implement this will be subclasses of
* {@see action_column_base}, and most such columns should probably implement
* this interface.
*
* If your column is a simple action, you can probably save duplicated code by
* using the base class action_column_menuable as an easy way to implement both
* action_column_base and this interface.
*
* @copyright 2019 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface menuable_action {
/**
* Return the appropriate action menu link, or null if it does not apply to this question.
*
* @param \stdClass $question data about the question being displayed in this row.
* @return \action_menu_link|null the action, if applicable to this question.
*/
public function get_action_menu_link(\stdClass $question): ?\action_menu_link;
}

View File

@ -0,0 +1,64 @@
<?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/>.
/**
* Base class class for navigation node.
*
* @package core_question
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* Class navigation_node_base is the base class for navigation node.
*
* @package core_question
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class navigation_node_base {
/**
* Title for this node.
*/
abstract public function get_navigation_title();
/**
* Key for this node.
*/
abstract public function get_navigation_key();
/**
* URL for this node
*/
abstract public function get_navigation_url();
/**
* Tab capabilities.
*
* If it has capabilities to be checked, it will return the array of capabilities.
*
* @return null|array
*/
public function get_navigation_capabilities(): ?array {
return null;
}
}

View File

@ -0,0 +1,59 @@
<?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/>.
/**
* Base class class for qbank plugins.
*
* Every qbank plugin must extent this class.
*
* @package core_question
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* Class plugin_features_base is the base class for qbank plugins.
*
* @package core_question
* @copyright 2021 Catalyst IT Australia Pty Ltd
* @author Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugin_features_base {
/**
* This method will return the array of objects to be rendered as a prt of question bank columns/actions.
*
* @param view $qbank
* @return array
*/
public function get_question_columns(view $qbank): ?array {
return [];
}
/**
* This method will return the object for the navigation node.
*
* @return null|object
*/
public function get_navigation_node(): ?object {
return null;
}
}

View File

@ -0,0 +1,310 @@
<?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/>.
/**
* A class for efficiently finds questions at random from the question bank.
*
* @package core_question
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* This class efficiently finds questions at random from the question bank.
*
* You can ask for questions at random one at a time. Each time you ask, you
* pass a category id, and whether to pick from that category and all subcategories
* or just that category.
*
* The number of teams each question has been used is tracked, and we will always
* return a question from among those elegible that has been used the fewest times.
* So, if there are questions that have not been used yet in the category asked for,
* one of those will be returned. However, within one instantiation of this class,
* we will never return a given question more than once, and we will never return
* questions passed into the constructor as $usedquestions.
*
* @copyright 2015 The Open University
* @author 2021 Safat Shahin <safatshahin@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class random_question_loader {
/** @var \qubaid_condition which usages to consider previous attempts from. */
protected $qubaids;
/** @var array qtypes that cannot be used by random questions. */
protected $excludedqtypes;
/** @var array categoryid & include subcategories => num previous uses => questionid => 1. */
protected $availablequestionscache = [];
/**
* @var array questionid => num recent uses. Questions that have been used,
* but that is not yet recorded in the DB.
*/
protected $recentlyusedquestions;
/**
* Constructor.
*
* @param \qubaid_condition $qubaids the usages to consider when counting previous uses of each question.
* @param array $usedquestions questionid => number of times used count. If we should allow for
* further existing uses of a question in addition to the ones in $qubaids.
*/
public function __construct(\qubaid_condition $qubaids, array $usedquestions = []) {
$this->qubaids = $qubaids;
$this->recentlyusedquestions = $usedquestions;
foreach (\question_bank::get_all_qtypes() as $qtype) {
if (!$qtype->is_usable_by_random()) {
$this->excludedqtypes[] = $qtype->name();
}
}
}
/**
* Pick a question at random from the given category, from among those with the fewest uses.
* If an array of tag ids are specified, then only the questions that are tagged with ALL those tags will be selected.
*
* It is up the the caller to verify that the cateogry exists. An unknown category
* behaves like an empty one.
*
* @param int $categoryid the id of a category in the question bank.
* @param bool $includesubcategories wether to pick a question from exactly
* that category, or that category and subcategories.
* @param array $tagids An array of tag ids. A question has to be tagged with all the provided tagids (if any)
* in order to be eligible for being picked.
* @return int|null the id of the question picked, or null if there aren't any.
*/
public function get_next_question_id($categoryid, $includesubcategories, $tagids = []): ?int {
$this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids);
$categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids);
if (empty($this->availablequestionscache[$categorykey])) {
return null;
}
reset($this->availablequestionscache[$categorykey]);
$lowestcount = key($this->availablequestionscache[$categorykey]);
reset($this->availablequestionscache[$categorykey][$lowestcount]);
$questionid = key($this->availablequestionscache[$categorykey][$lowestcount]);
$this->use_question($questionid);
return $questionid;
}
/**
* Get the key into {@see $availablequestionscache} for this combination of options.
*
* @param int $categoryid the id of a category in the question bank.
* @param bool $includesubcategories wether to pick a question from exactly
* that category, or that category and subcategories.
* @param array $tagids an array of tag ids.
* @return string the cache key.
*/
protected function get_category_key($categoryid, $includesubcategories, $tagids = []): string {
if ($includesubcategories) {
$key = $categoryid . '|1';
} else {
$key = $categoryid . '|0';
}
if (!empty($tagids)) {
$key .= '|' . implode('|', $tagids);
}
return $key;
}
/**
* Populate {@see $availablequestionscache} for this combination of options.
*
* @param int $categoryid The id of a category in the question bank.
* @param bool $includesubcategories Whether to pick a question from exactly
* that category, or that category and subcategories.
* @param array $tagids An array of tag ids. If an array is provided, then
* only the questions that are tagged with ALL the provided tagids will be loaded.
*/
protected function ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids = []): void {
global $DB;
$categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids);
if (isset($this->availablequestionscache[$categorykey])) {
// Data is already in the cache, nothing to do.
return;
}
// Load the available questions from the question bank.
if ($includesubcategories) {
$categoryids = question_categorylist($categoryid);
} else {
$categoryids = [$categoryid];
}
list($extraconditions, $extraparams) = $DB->get_in_or_equal($this->excludedqtypes,
SQL_PARAMS_NAMED, 'excludedqtype', false);
$questionidsandcounts = \question_bank::get_finder()->get_questions_from_categories_and_tags_with_usage_counts(
$categoryids, $this->qubaids, 'q.qtype ' . $extraconditions, $extraparams, $tagids);
if (!$questionidsandcounts) {
// No questions in this category.
$this->availablequestionscache[$categorykey] = [];
return;
}
// Put all the questions with each value of $prevusecount in separate arrays.
$idsbyusecount = [];
foreach ($questionidsandcounts as $questionid => $prevusecount) {
if (isset($this->recentlyusedquestions[$questionid])) {
// Recently used questions are never returned.
continue;
}
$idsbyusecount[$prevusecount][] = $questionid;
}
// Now put that data into our cache. For each count, we need to shuffle
// questionids, and make those the keys of an array.
$this->availablequestionscache[$categorykey] = [];
foreach ($idsbyusecount as $prevusecount => $questionids) {
shuffle($questionids);
$this->availablequestionscache[$categorykey][$prevusecount] = array_combine(
$questionids, array_fill(0, count($questionids), 1));
}
ksort($this->availablequestionscache[$categorykey]);
}
/**
* Update the internal data structures to indicate that a given question has
* been used one more time.
*
* @param int $questionid the question that is being used.
*/
protected function use_question($questionid): void {
if (isset($this->recentlyusedquestions[$questionid])) {
$this->recentlyusedquestions[$questionid] += 1;
} else {
$this->recentlyusedquestions[$questionid] = 1;
}
foreach ($this->availablequestionscache as $categorykey => $questionsforcategory) {
foreach ($questionsforcategory as $numuses => $questionids) {
if (!isset($questionids[$questionid])) {
continue;
}
unset($this->availablequestionscache[$categorykey][$numuses][$questionid]);
if (empty($this->availablequestionscache[$categorykey][$numuses])) {
unset($this->availablequestionscache[$categorykey][$numuses]);
}
}
}
}
/**
* Get the list of available question ids for the given criteria.
*
* @param int $categoryid The id of a category in the question bank.
* @param bool $includesubcategories Whether to pick a question from exactly
* that category, or that category and subcategories.
* @param array $tagids An array of tag ids. If an array is provided, then
* only the questions that are tagged with ALL the provided tagids will be loaded.
* @return int[] The list of question ids
*/
protected function get_question_ids($categoryid, $includesubcategories, $tagids = []): array {
$this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids);
$categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids);
$cachedvalues = $this->availablequestionscache[$categorykey];
$questionids = [];
foreach ($cachedvalues as $usecount => $ids) {
$questionids = array_merge($questionids, array_keys($ids));
}
return $questionids;
}
/**
* Check whether a given question is available in a given category. If so, mark it used.
* If an optional list of tag ids are provided, then the question must be tagged with
* ALL of the provided tags to be considered as available.
*
* @param int $categoryid the id of a category in the question bank.
* @param bool $includesubcategories wether to pick a question from exactly
* that category, or that category and subcategories.
* @param int $questionid the question that is being used.
* @param array $tagids An array of tag ids. Only the questions that are tagged with all the provided tagids can be available.
* @return bool whether the question is available in the requested category.
*/
public function is_question_available($categoryid, $includesubcategories, $questionid, $tagids = []): bool {
$this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids);
$categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids);
foreach ($this->availablequestionscache[$categorykey] as $questionids) {
if (isset($questionids[$questionid])) {
$this->use_question($questionid);
return true;
}
}
return false;
}
/**
* Get the list of available questions for the given criteria.
*
* @param int $categoryid The id of a category in the question bank.
* @param bool $includesubcategories Whether to pick a question from exactly
* that category, or that category and subcategories.
* @param array $tagids An array of tag ids. If an array is provided, then
* only the questions that are tagged with ALL the provided tagids will be loaded.
* @param int $limit Maximum number of results to return.
* @param int $offset Number of items to skip from the begging of the result set.
* @param string[] $fields The fields to return for each question.
* @return \stdClass[] The list of question records
*/
public function get_questions($categoryid, $includesubcategories, $tagids = [], $limit = 100, $offset = 0, $fields = []) {
global $DB;
$questionids = $this->get_question_ids($categoryid, $includesubcategories, $tagids);
if (empty($questionids)) {
return [];
}
if (empty($fields)) {
// Return all fields.
$fieldsstring = '*';
} else {
$fieldsstring = implode(',', $fields);
}
return $DB->get_records_list('question', 'id', $questionids, 'id', $fieldsstring, $offset, $limit);
}
/**
* Count the number of available questions for the given criteria.
*
* @param int $categoryid The id of a category in the question bank.
* @param bool $includesubcategories Whether to pick a question from exactly
* that category, or that category and subcategories.
* @param array $tagids An array of tag ids. If an array is provided, then
* only the questions that are tagged with ALL the provided tagids will be loaded.
* @return int The number of questions matching the criteria.
*/
public function count_questions($categoryid, $includesubcategories, $tagids = []): int {
$questionids = $this->get_question_ids($categoryid, $includesubcategories, $tagids);
return count($questionids);
}
}

View File

@ -0,0 +1,70 @@
<?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/>.
/**
* Base class for 'columns' that are actually displayed as a row following the main question row.
*
* @package core_question
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\local\bank;
/**
* Base class for 'columns' that are actually displayed as a row following the main question row.
*
* @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
*/
abstract class row_base extends column_base {
/**
* Check if the column is an extra row of not.
*/
public function is_extra_row(): bool {
return true;
}
/**
* Output the opening column tag. If it is set as heading, it will use <th> tag instead of <td>
*
* @param \stdClass $question
* @param string $rowclasses
*/
protected function display_start($question, $rowclasses): void {
if ($rowclasses) {
echo \html_writer::start_tag('tr', ['class' => $rowclasses]);
} else {
echo \html_writer::start_tag('tr');
}
echo \html_writer::start_tag('td',
['colspan' => $this->qbank->get_column_count(), 'class' => $this->get_name()]);
}
/**
* Output the closing column tag
*
* @param object $question
* @param string $rowclasses
*/
protected function display_end($question, $rowclasses): void {
echo \html_writer::end_tag('td');
echo \html_writer::end_tag('tr');
}
}

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,9 @@ if (($lastchanged = optional_param('lastchanged', 0, PARAM_INT)) !== 0) {
}
$PAGE->set_url($url);
$questionbank = new core_question\bank\view($contexts, $thispageurl, $COURSE, $cm);
$questionbank = new core_question\local\bank\view($contexts, $thispageurl, $COURSE, $cm);
// TODO MDL-72076 - this one will become redundant after implementing bulk actions UI.
$questionbank->process_actions();
$context = $contexts->lowest();
@ -49,11 +51,8 @@ echo $OUTPUT->header();
$renderer = $PAGE->get_renderer('core_question', 'bank');
echo $renderer->extra_horizontal_navigation();
echo '<div class="questionbankwindow boxwidthwide boxaligncenter">';
$questionbank->display('questions', $pagevars['qpage'], $pagevars['qperpage'],
$pagevars['cat'], $pagevars['recurse'], $pagevars['showhidden'],
$pagevars['qbshowtext'], $pagevars['qtagids']);
echo "</div>\n";
// Print the question area.
$questionbank->display($pagevars, 'questions');
// Log the view of this category.
list($categoryid, $contextid) = explode(',', $pagevars['cat']);

View File

@ -140,13 +140,14 @@ function question_can_delete_cat($todelete) {
/**
* Base class for representing a column in a {@link question_bank_view}.
* Base class for representing a column in a {@see question_bank_view}.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\column_base', 'question_bank_column_base', true);
class_alias('core_question\local\bank\column_base', 'question_bank_column_base', true);
/**
* A column with a checkbox for each question with name q{questionid}.
@ -154,8 +155,9 @@ class_alias('core_question\bank\column_base', 'question_bank_column_base', true)
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\checkbox_column', 'question_bank_checkbox_column', true);
class_alias('core_question\local\bank\checkbox_column', 'question_bank_checkbox_column', true);
/**
* A column type for the name of the question type.
@ -163,6 +165,7 @@ class_alias('core_question\bank\checkbox_column', 'question_bank_checkbox_column
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\question_type_column', 'question_bank_question_type_column', true);
@ -173,6 +176,7 @@ class_alias('core_question\bank\question_type_column', 'question_bank_question_t
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\question_name_column', 'question_bank_question_name_column', true);
@ -183,6 +187,7 @@ class_alias('core_question\bank\question_name_column', 'question_bank_question_n
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\creator_name_column', 'question_bank_creator_name_column', true);
@ -193,6 +198,7 @@ class_alias('core_question\bank\creator_name_column', 'question_bank_creator_nam
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\modifier_name_column', 'question_bank_modifier_name_column', true);
@ -203,8 +209,9 @@ class_alias('core_question\bank\modifier_name_column', 'question_bank_modifier_n
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\action_column_base', 'question_bank_action_column_base', true);
class_alias('core_question\local\bank\action_column_base', 'question_bank_action_column_base', true);
/**
@ -213,6 +220,7 @@ class_alias('core_question\bank\action_column_base', 'question_bank_action_colum
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\edit_action_column', 'question_bank_edit_action_column', true);
@ -222,6 +230,7 @@ class_alias('core_question\bank\edit_action_column', 'question_bank_edit_action_
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\copy_action_column', 'question_bank_copy_action_column', true);
@ -231,6 +240,7 @@ class_alias('core_question\bank\copy_action_column', 'question_bank_copy_action_
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\preview_action_column', 'question_bank_preview_action_column', true);
@ -241,6 +251,7 @@ class_alias('core_question\bank\preview_action_column', 'question_bank_preview_a
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\delete_action_column', 'question_bank_delete_action_column', true);
@ -250,8 +261,9 @@ class_alias('core_question\bank\delete_action_column', 'question_bank_delete_act
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\row_base', 'question_bank_row_base', true);
class_alias('core_question\local\bank\row_base', 'question_bank_row_base', true);
/**
* A column type for the name of the question name.
@ -259,6 +271,7 @@ class_alias('core_question\bank\row_base', 'question_bank_row_base', true);
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\question_text_row', 'question_bank_question_text_row', true);
@ -266,8 +279,9 @@ class_alias('core_question\bank\question_text_row', 'question_bank_question_text
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 2.7 MDL-40457
* @todo MDl-72004 delete the class alias, not done in MDL-71516 for any potential error from other plugins.
*/
class_alias('core_question\bank\view', 'question_bank_view', true);
class_alias('core_question\local\bank\view', 'question_bank_view', true);
/**
* Common setup for all pages for editing questions.

View File

@ -87,7 +87,7 @@ if ($previewid) {
// actually from the user point of view, it makes sense.
print_error('submissionoutofsequencefriendlymessage', 'question',
question_preview_url($question->id, $options->behaviour,
$options->maxmark, $options, $options->variant, $context), null, $e);
$options->maxmark, $options, $options->variant, $context), null, $e);
}
if ($quba->get_owning_context()->instanceid != $USER->id) {
@ -255,15 +255,15 @@ echo $quba->render_question($slot, $options, $displaynumber);
// Finish the question form.
echo html_writer::start_tag('div', array('id' => 'previewcontrols', 'class' => 'controls'));
echo html_writer::empty_tag('input', $restartdisabled + array('type' => 'submit',
'name' => 'restart', 'value' => get_string('restart', 'question'), 'class' => 'btn btn-secondary'));
'name' => 'restart', 'value' => get_string('restart', 'question'), 'class' => 'btn btn-secondary'));
echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit',
'name' => 'save', 'value' => get_string('save', 'question'), 'class' => 'btn btn-secondary',
'id' => 'id_save_question_preview'));
'name' => 'save', 'value' => get_string('save', 'question'), 'class' => 'btn btn-secondary',
'id' => 'id_save_question_preview'));
echo html_writer::empty_tag('input', $filldisabled + array('type' => 'submit',
'name' => 'fill', 'value' => get_string('fillincorrect', 'question'), 'class' => 'btn btn-secondary'));
'name' => 'fill', 'value' => get_string('fillincorrect', 'question'), 'class' => 'btn btn-secondary'));
echo html_writer::empty_tag('input', $finishdisabled + array('type' => 'submit',
'name' => 'finish', 'value' => get_string('submitandfinish', 'question'), 'class' => 'btn btn-secondary',
'id' => 'id_finish_question_preview'));
'name' => 'finish', 'value' => get_string('submitandfinish', 'question'), 'class' => 'btn btn-secondary',
'id' => 'id_finish_question_preview'));
echo html_writer::end_tag('div');
echo html_writer::end_tag('form');
@ -277,8 +277,17 @@ print_collapsible_region_end();
// Output a link to export this single question.
if (question_has_capability_on($question, 'view')) {
echo html_writer::link(question_get_export_single_question_url($question),
get_string('exportonequestion', 'question'));
if (class_exists('qbank_exporttoxml\\exporttoxml_helper')) {
if (\core\plugininfo\qbank::is_plugin_enabled('qbank_exporttoxml')) {
$exportfunction = '\\qbank_exporttoxml\\exporttoxml_helper::question_get_export_single_question_url';
echo html_writer::link($exportfunction($question),
get_string('exportonequestion', 'question'));
}
} else {
$exportfunction = 'question_get_export_single_question_url';
echo html_writer::link($exportfunction($question),
get_string('exportonequestion', 'question'));
}
}
// Log the preview of this question.
@ -290,10 +299,9 @@ $optionsform->display();
$PAGE->requires->js_module('core_question_engine');
$PAGE->requires->strings_for_js(array(
'closepreview',
'closepreview',
), 'question');
$PAGE->requires->yui_module('moodle-question-preview', 'M.question.preview.init');
$PAGE->requires->js_call_amd('core_form/submit', 'init', ['id_save_question_preview']);
$PAGE->requires->js_call_amd('core_form/submit', 'init', ['id_finish_question_preview']);
echo $OUTPUT->footer();

View File

@ -17,8 +17,7 @@
/**
* Renderers for outputting parts of the question bank.
*
* @package moodlecore
* @subpackage questionbank
* @package core_question
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@ -38,9 +37,10 @@ class core_question_bank_renderer extends plugin_renderer_base {
/**
* Display additional navigation if needed.
*
* @param string $active
* @return string
*/
public function extra_horizontal_navigation() {
public function extra_horizontal_navigation($active = null) {
// Horizontal navigation for question bank.
if ($questionnode = $this->page->settingsnav->find("questionbank", \navigation_node::TYPE_CONTAINER)) {
if ($children = $questionnode->children) {
@ -48,8 +48,11 @@ class core_question_bank_renderer extends plugin_renderer_base {
foreach ($children as $key => $node) {
$tabs[] = new \tabobject($node->key, $node->action, $node->text);
}
$active = $questionnode->find_active_node()->key;
return \html_writer::div(print_tabs([$tabs], $active, null, null, true), 'questionbank-navigation');
if (empty($active)) {
$active = $questionnode->find_active_node()->key;
}
return \html_writer::div(print_tabs([$tabs], $active, null, null, true),
'questionbank-navigation');
}
}
return '';
@ -68,6 +71,26 @@ class core_question_bank_renderer extends plugin_renderer_base {
return $this->image_icon('icon', $namestr, $qtype->plugin_name(), array('title' => $namestr));
}
/**
* Render the column headers.
*
* @param array $qbankheaderdata
* @return bool|string
*/
public function render_column_header($qbankheaderdata) {
return $this->render_from_template('core_question/column_header', $qbankheaderdata);
}
/**
* Render the column sort elements.
*
* @param array $sortdata
* @return bool|string
*/
public function render_column_sort($sortdata) {
return $this->render_from_template('core_question/column_sort', $sortdata);
}
/**
* Render a qbank_chooser.
*
@ -78,6 +101,56 @@ class core_question_bank_renderer extends plugin_renderer_base {
return $this->render_from_template('core_question/qbank_chooser', $qbankchooser->export_for_template($this));
}
/**
* Render category condition.
*
* @param array $displaydata
* @return bool|string
*/
public function render_category_condition($displaydata) {
return $this->render_from_template('core_question/category_condition', $displaydata);
}
/**
* Render category condition advanced.
*
* @param array $displaydata
* @return bool|string
*/
public function render_category_condition_advanced($displaydata) {
return $this->render_from_template('core_question/category_condition_advanced', $displaydata);
}
/**
* Render hidden condition advanced.
*
* @param array $displaydata
* @return bool|string
*/
public function render_hidden_condition_advanced($displaydata) {
return $this->render_from_template('core_question/hidden_condition_advanced', $displaydata);
}
/**
* Render question pagination.
*
* @param array $displaydata
* @return bool|string
*/
public function render_question_pagination($displaydata) {
return $this->render_from_template('core_question/question_pagination', $displaydata);
}
/**
* Render question showtext checkbox.
*
* @param array $displaydata
* @return bool|string
*/
public function render_showtext_checkbox($displaydata) {
return $this->render_from_template('core_question/showtext_checkbox', $displaydata);
}
/**
* Build the HTML for the question chooser javascript popup.
*
@ -89,7 +162,7 @@ class core_question_bank_renderer extends plugin_renderer_base {
*/
public function qbank_chooser($real, $fake, $course, $hiddenparams) {
debugging('Method core_question_bank_renderer::qbank_chooser() is deprecated, ' .
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
return '';
}
@ -101,7 +174,7 @@ class core_question_bank_renderer extends plugin_renderer_base {
*/
protected function qbank_chooser_types($types) {
debugging('Method core_question_bank_renderer::qbank_chooser_types() is deprecated, ' .
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
return '';
}
@ -115,7 +188,7 @@ class core_question_bank_renderer extends plugin_renderer_base {
*/
protected function qbank_chooser_qtype($qtype, $classes = array()) {
debugging('Method core_question_bank_renderer::qbank_chooser_qtype() is deprecated, ' .
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
return '';
}
@ -128,7 +201,8 @@ class core_question_bank_renderer extends plugin_renderer_base {
*/
protected function qbank_chooser_title($title, $identifier = null) {
debugging('Method core_question_bank_renderer::qbank_chooser_title() is deprecated, ' .
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
'see core_question_bank_renderer::render_qbank_chooser().', DEBUG_DEVELOPER);
return '';
}
}

View File

@ -0,0 +1,38 @@
{{!
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/>.
}}
{{!
@template core_question/category_condition
Example context (json):
{
"displaydata": [
{
"categoryselect": "html string",
"categorydesc": "html string"
}
]
}
}}
<div class="choosecategory">
<label class="mr-1" for="id_selectacategory">
{{#str}} selectacategory, question{{/str}}
</label>
{{{categoryselect}}}
</div>
<div class="boxaligncenter categoryinfo pl-0">
{{{categorydesc}}}
</div>

View File

@ -0,0 +1,33 @@
{{!
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/>.
}}
{{!
@template core_question/category_condition_advanced
Example context (json):
{
"displaydata": [
{
"checked": "checked attribute"
}
]
}
}}
<div class="category_condition_advanced">
<input type="hidden" name="recurse" value="0" id="recurse_off">
<input id="recurse_on" class="searchoptions mr-1" type="checkbox" value="1" name="recurse" {{{checked}}}>
<label for="recurse_on">{{#str}} includesubcategories, question{{/str}}</label>
</div>

View File

@ -0,0 +1,52 @@
{{!
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/>.
}}
{{!
@template core_question/column_header
Example context (json):
{
"qbankheaderdata": [
{
"extraclasses": "checkbox",
"sortable": false,
"tiptitle": "Element title",
"sorttip": true,
"tip": "Select questions for bulk actions",
"sortlinks": "sort / sort"
}
]
}
}}
<th class="header {{extraclasses}}" scope="col">
{{#title}}
<div class="title">{{title}}</div>
{{/title}}
{{^sortable}}
{{^sorttip}}
{{tiptitle}}
{{/sorttip}}
{{#sorttip}}
<span title="{{tip}}">{{{tiptitle}}}</span>
{{/sorttip}}
{{/sortable}}
{{#sortable}}
<div class="sorters">
{{{sortlinks}}}
</div>
{{/sortable}}
</th>

View File

@ -0,0 +1,34 @@
{{!
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/>.
}}
{{!
@template core_question/column_sort
Example context (json):
{
"sortdata": [
{
"sorturl": "url",
"sorttip": "Sort according to first name",
"sortcontent": "Sort title sort icon"
}
]
}
}}
<a href="{{{sorturl}}}" title="{{sorttip}}">
{{{sortcontent}}}
</a>

View File

@ -0,0 +1,33 @@
{{!
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/>.
}}
{{!
@template core_question/hidden_condition_advanced
Example context (json):
{
"displaydata": [
{
"checked": "checked attribute"
}
]
}
}}
<div class="hidden_condition_advanced">
<input type="hidden" name="showhidden" value="0" id="showhidden_off">
<input id="showhidden_on" class="searchoptions mr-1" type="checkbox" value="1" name="showhidden" {{{checked}}}>
<label for="showhidden_on">{{#str}} showhidden, question{{/str}}</label>
</div>

View File

@ -0,0 +1,48 @@
{{!
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/>.
}}
{{!
@template core_question/question_pagination
Example context (json):
{
"sortdata": [
{
"extraclasses": "classes",
"pagination": "pagination html",
"showall": "true/false",
"showallurl": "moodle url",
"biggertotal": "true/false",
"totalnumber": "number of questions"
}
]
}
}}
<div class="categorypagingbarcontainer {{extraclasses}}">
{{{pagination}}}
</div>
{{#showall}}
<div class="question-showall-text">
<a href="{{{showallurl}}}">
{{^biggertotal}}
{{#str}} showall, moodle, {{totalnumber}} {{/str}}
{{/biggertotal}}
{{#biggertotal}}
{{#str}} showperpage, moodle, {{totalnumber}} {{/str}}
{{/biggertotal}}
</a>
</div>
{{/showall}}

View File

@ -0,0 +1,33 @@
{{!
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/>.
}}
{{!
@template core_question/showtext_checkbox
Example context (json):
{
"sortdata": [
{
"checked": "true/false"
}
]
}
}}
<div>
<input type="hidden" name="qbshowtext" value="0" id="qbshowtext_off">
<input id="qbshowtext_on" class="searchoptions mr-1" type="checkbox" value="1" name="qbshowtext" {{#checked}} checked="checked" {{/checked}}>
<label for="qbshowtext_on">{{#str}} showquestiontext, question {{/str}}</label>
</div>

View File

@ -107,16 +107,16 @@ class core_question_backup_testcase extends advanced_testcase {
// Create a new course category and and a new course in that.
$category1 = $this->getDataGenerator()->create_category();
$course = $this->getDataGenerator()->create_course(array('category' => $category1->id));
$course = $this->getDataGenerator()->create_course(['category' => $category1->id]);
$courseshortname = $course->shortname;
$coursefullname = $course->fullname;
// Create 2 questions.
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
$context = context_coursecat::instance($category1->id);
$qcat = $qgen->create_question_category(array('contextid' => $context->id));
$question1 = $qgen->create_question('shortanswer', null, array('category' => $qcat->id, 'idnumber' => 'q1'));
$question2 = $qgen->create_question('shortanswer', null, array('category' => $qcat->id, 'idnumber' => 'q2'));
$qcat = $qgen->create_question_category(['contextid' => $context->id]);
$question1 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q1']);
$question2 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q2']);
// Tag the questions with 2 question tags and 2 course level question tags.
$qcontext = context::instance_by_id($qcat->contextid);
@ -127,7 +127,7 @@ class core_question_backup_testcase extends advanced_testcase {
core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag3', 'ctag4']);
// Create a quiz and add one of the questions to that.
$quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
quiz_add_quiz_question($question1->id, $quiz);
// Backup the course twice for future use.
@ -144,7 +144,7 @@ class core_question_backup_testcase extends advanced_testcase {
// The questions should remain in the question category they were which is
// a question category belonging to a course category context.
$questions = $DB->get_records('question', array('category' => $qcat->id), 'idnumber');
$questions = $DB->get_records('question', ['category' => $qcat->id], 'idnumber');
$this->assertCount(2, $questions);
// Retrieve tags for each question and check if they are assigned at the right context.
@ -179,10 +179,10 @@ class core_question_backup_testcase extends advanced_testcase {
// Create a new course category to restore the backup file into it.
$category2 = $this->getDataGenerator()->create_category();
$expectedwarnings = array(
$expectedwarnings = [
get_string('qcategory2coursefallback', 'backup', (object) ['name' => 'top']),
get_string('qcategory2coursefallback', 'backup', (object) ['name' => $qcat->name])
);
];
// Restore to a new course in the new course category.
$courseid3 = $this->restore_course($backupid2, $coursefullname, $courseshortname . '_3', $category2->id, $expectedwarnings);
@ -192,7 +192,7 @@ class core_question_backup_testcase extends advanced_testcase {
$questions = $DB->get_records_sql("SELECT q.*
FROM {question} q
JOIN {question_categories} qc ON q.category = qc.id
WHERE qc.contextid = ?", array($coursecontext3->id));
WHERE qc.contextid = ?", [$coursecontext3->id]);
$this->assertCount(2, $questions);
// Now, retrieve tags for each question and check if they are assigned at the right context.

View File

@ -59,9 +59,18 @@ class core_question_bank_view_testcase extends advanced_testcase {
$cache->delete($questiondata->id);
// Generate the view.
$view = new core_question\bank\view($contexts, new moodle_url('/'), $course);
$view = new core_question\local\bank\view($contexts, new moodle_url('/'), $course);
ob_start();
$view->display('editq', 0, 20, $cat->id . ',' . $context->id, false, false, false);
$pagevars = [
'qpage' => 0,
'qperpage' => 20,
'cat' => $cat->id . ',' . $context->id,
'recurse' => false,
'showhidden' => false,
'qbshowtext' => false
];
$view->display($pagevars, 'editq');
$html = ob_get_clean();
// Verify the output includes the expected question.
@ -91,9 +100,18 @@ class core_question_bank_view_testcase extends advanced_testcase {
$DB->set_field('question', 'qtype', 'unknownqtype', ['id' => $questiondata->id]);
// Generate the view.
$view = new core_question\bank\view($contexts, new moodle_url('/'), $course);
$view = new core_question\local\bank\view($contexts, new moodle_url('/'), $course);
ob_start();
$view->display('editq', 0, 20, $cat->id . ',' . $context->id, false, false, false);
$pagevars = [
'qpage' => 0,
'qperpage' => 20,
'cat' => $cat->id . ',' . $context->id,
'recurse' => false,
'showhidden' => false,
'qbshowtext' => false
];
$view->display($pagevars, 'editq');
$html = ob_get_clean();
// Mainly we are verifying that there was no fatal error.

View File

@ -50,8 +50,8 @@ class behat_question_base extends behat_base {
*/
protected function finish_adding_question($questiontypename, TableNode $questiondata) {
$this->execute('behat_forms::i_set_the_field_to', array($this->escape($questiontypename), 1));
$this->execute("behat_general::i_click_on", array('.submitbutton', "css_element"));
$this->execute('behat_forms::i_set_the_field_to', [$this->escape($questiontypename), 1]);
$this->execute("behat_general::i_click_on", ['.submitbutton', "css_element"]);
$this->execute("behat_forms::i_set_the_following_fields_to_these_values", $questiondata);
$this->execute("behat_forms::press_button", 'id_submitbutton');

View File

@ -44,16 +44,16 @@ class core_question_events_testcase extends advanced_testcase {
public function test_question_category_created() {
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
$contexts = new question_edit_contexts(context_module::instance($quiz->cmid));
$defaultcategoryobj = question_make_default_categories(array($contexts->lowest()));
$defaultcategoryobj = question_make_default_categories([$contexts->lowest()]);
$defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
$qcobject = new question_category_object(
1,
new moodle_url('/mod/quiz/edit.php', array('cmid' => $quiz->cmid)),
new moodle_url('/mod/quiz/edit.php', ['cmid' => $quiz->cmid]),
$contexts->having_one_edit_tab_cap('categories'),
$defaultcategoryobj->id,
$defaultcategory,
@ -69,7 +69,7 @@ class core_question_events_testcase extends advanced_testcase {
// Check that the event data is valid.
$this->assertInstanceOf('\core\event\question_category_created', $event);
$this->assertEquals(context_module::instance($quiz->cmid), $event->get_context());
$expected = array($course->id, 'quiz', 'addcategory', 'view.php?id=' . $quiz->cmid , $categoryid, $quiz->cmid);
$expected = [$course->id, 'quiz', 'addcategory', 'view.php?id=' . $quiz->cmid , $categoryid, $quiz->cmid];
$this->assertEventLegacyLogData($expected, $event);
$this->assertEventContextNotUsed($event);
}

View File

@ -57,7 +57,7 @@ class core_question_external_testcase extends externallib_advanced_testcase {
$this->student = self::getDataGenerator()->create_user();
// Users enrolments.
$this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
}
@ -73,7 +73,7 @@ class core_question_external_testcase extends externallib_advanced_testcase {
$quba = question_engine::make_questions_usage_by_activity('core_question_update_flag', context_system::instance());
$quba->set_preferred_behaviour('deferredfeedback');
$questiondata = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
$questiondata = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
$question = question_bank::load_question($questiondata->id);
$slot = $quba->add_question($question);
$qa = $quba->get_question_attempt($slot);

View File

@ -31,7 +31,7 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2018 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_core_question_column extends \core_question\bank\column_base {
class testable_core_question_column extends \core_question\local\bank\column_base {
/** @var array sortable columns. */
private $sortable = [];

View File

@ -45,14 +45,14 @@ class core_question_generator extends component_generator_base {
$this->categorycount++;
$defaults = array(
$defaults = [
'name' => 'Test question category ' . $this->categorycount,
'info' => '',
'infoformat' => FORMAT_HTML,
'stamp' => make_unique_id_code(),
'sortorder' => 999,
'idnumber' => null
);
];
$record = $this->datagenerator->combine_defaults_and_record($defaults, $record);
@ -175,12 +175,12 @@ class core_question_generator extends component_generator_base {
$qcat = $this->create_question_category(['contextid' => $context->id]);
$questions = array(
$questions = [
$this->create_question('shortanswer', null, ['category' => $qcat->id]),
$this->create_question('shortanswer', null, ['category' => $qcat->id]),
);
];
return array($category, $course, $qcat, $questions);
return [$category, $course, $qcat, $questions];
}
/**

View File

@ -46,8 +46,7 @@ class core_question_generator_testcase extends advanced_testcase {
// creates a Top category as well.
$this->assertEquals($count, $DB->count_records('question_categories'));
$cat = $generator->create_question_category(array(
'name' => 'My category', 'sortorder' => 1));
$cat = $generator->create_question_category(['name' => 'My category', 'sortorder' => 1]);
$this->assertSame('My category', $cat->name);
$this->assertSame(1, $cat->sortorder);
}
@ -61,8 +60,8 @@ class core_question_generator_testcase extends advanced_testcase {
$this->assertNull($questions[0]->idnumber);
$this->assertNull($questions[1]->idnumber);
// Check created idnumbers.
$qcat1 = $generator->create_question_category(array(
'name' => 'My category', 'sortorder' => 1, 'idnumber' => 'myqcat'));
$qcat1 = $generator->create_question_category([
'name' => 'My category', 'sortorder' => 1, 'idnumber' => 'myqcat']);
$this->assertSame('myqcat', $qcat1->idnumber);
$quest1 = $generator->update_question($questions[0], null, ['idnumber' => 'myquest']);
$this->assertSame('myquest', $quest1->idnumber);

View File

@ -57,14 +57,14 @@ class testable_qformat extends qformat_default {
class qformat_default_test extends advanced_testcase {
public function test_assemble_category_path() {
$format = new testable_qformat();
$pathsections = array(
$pathsections = [
'$course$',
"Tim's questions",
"Tricky things like / // and so on",
'Category name ending in /',
'/ and one that starts with one',
'<span lang="en" class="multilang">Matematically</span> <span lang="sv" class="multilang">Matematiskt (svenska)</span>'
);
];
$this->assertEquals('$course$/Tim\'s questions/Tricky things like // //// and so on/Category name ending in // / // and one that starts with one/<span lang="en" class="multilang">Matematically<//span> <span lang="sv" class="multilang">Matematiskt (svenska)<//span>',
$format->assemble_category_path($pathsections));
}
@ -72,20 +72,20 @@ class qformat_default_test extends advanced_testcase {
public function test_split_category_path() {
$format = new testable_qformat();
$path = '$course$/Tim\'s questions/Tricky things like // //// and so on/Category name ending in // / // and one that starts with one/<span lang="en" class="multilang">Matematically<//span> <span lang="sv" class="multilang">Matematiskt (svenska)<//span>';
$this->assertEquals(array(
$this->assertEquals([
'$course$',
"Tim's questions",
"Tricky things like / // and so on",
'Category name ending in /',
'/ and one that starts with one',
'<span lang="en" class="multilang">Matematically</span> <span lang="sv" class="multilang">Matematiskt (svenska)</span>'
), $format->split_category_path($path));
], $format->split_category_path($path));
}
public function test_split_category_path_cleans() {
$format = new testable_qformat();
$path = '<evil>Nasty <virus //> thing<//evil>';
$this->assertEquals(array('Nasty thing'), $format->split_category_path($path));
$this->assertEquals(['Nasty thing'], $format->split_category_path($path));
}
public function test_clean_question_name() {

View File

@ -42,7 +42,7 @@ class least_used_variant_strategy_testcase extends advanced_testcase {
$quba->set_preferred_behaviour('deferredfeedback');
$slot = $quba->add_question($question);
$quba->start_all_questions(new core_question\engine\variants\least_used_strategy(
$quba, new qubaid_list(array())));
$quba, new qubaid_list([])));
$this->assertEquals(1, $quba->get_variant($slot));
}
@ -54,7 +54,7 @@ class least_used_variant_strategy_testcase extends advanced_testcase {
$slot1 = $quba->add_question($question);
$slot2 = $quba->add_question($question);
$quba->start_all_questions(new core_question\engine\variants\least_used_strategy(
$quba, new qubaid_list(array())));
$quba, new qubaid_list([])));
$this->assertEquals($quba->get_variant($slot1), $quba->get_variant($slot2));
}
@ -64,7 +64,7 @@ class least_used_variant_strategy_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$questiondata = $generator->create_question('calculated', null, array('category' => $cat->id));
$questiondata = $generator->create_question('calculated', null, ['category' => $cat->id]);
// Create two dataset items.
$adefinitionid = $DB->get_field_sql("
@ -72,23 +72,23 @@ class least_used_variant_strategy_testcase extends advanced_testcase {
FROM {question_dataset_definitions} qdd
JOIN {question_datasets} qd ON qd.datasetdefinition = qdd.id
WHERE qd.question = ?
AND qdd.name = ?", array($questiondata->id, 'a'));
AND qdd.name = ?", [$questiondata->id, 'a']);
$bdefinitionid = $DB->get_field_sql("
SELECT qdd.id
FROM {question_dataset_definitions} qdd
JOIN {question_datasets} qd ON qd.datasetdefinition = qdd.id
WHERE qd.question = ?
AND qdd.name = ?", array($questiondata->id, 'b'));
$DB->set_field('question_dataset_definitions', 'itemcount', 2, array('id' => $adefinitionid));
$DB->set_field('question_dataset_definitions', 'itemcount', 2, array('id' => $bdefinitionid));
$DB->insert_record('question_dataset_items', array('definition' => $adefinitionid,
'itemnumber' => 1, 'value' => 3));
$DB->insert_record('question_dataset_items', array('definition' => $bdefinitionid,
'itemnumber' => 1, 'value' => 7));
$DB->insert_record('question_dataset_items', array('definition' => $adefinitionid,
'itemnumber' => 2, 'value' => 6));
$DB->insert_record('question_dataset_items', array('definition' => $bdefinitionid,
'itemnumber' => 2, 'value' => 4));
AND qdd.name = ?", [$questiondata->id, 'b']);
$DB->set_field('question_dataset_definitions', 'itemcount', 2, ['id' => $adefinitionid]);
$DB->set_field('question_dataset_definitions', 'itemcount', 2, ['id' => $bdefinitionid]);
$DB->insert_record('question_dataset_items', ['definition' => $adefinitionid,
'itemnumber' => 1, 'value' => 3]);
$DB->insert_record('question_dataset_items', ['definition' => $bdefinitionid,
'itemnumber' => 1, 'value' => 7]);
$DB->insert_record('question_dataset_items', ['definition' => $adefinitionid,
'itemnumber' => 2, 'value' => 6]);
$DB->insert_record('question_dataset_items', ['definition' => $bdefinitionid,
'itemnumber' => 2, 'value' => 4]);
$question = question_bank::load_question($questiondata->id);
@ -96,7 +96,7 @@ class least_used_variant_strategy_testcase extends advanced_testcase {
$quba1->set_preferred_behaviour('deferredfeedback');
$slot1 = $quba1->add_question($question);
$quba1->start_all_questions(new core_question\engine\variants\least_used_strategy(
$quba1, new qubaid_list(array())));
$quba1, new qubaid_list([])));
question_engine::save_questions_usage_by_activity($quba1);
$variant1 = $quba1->get_variant($slot1);
@ -105,7 +105,7 @@ class least_used_variant_strategy_testcase extends advanced_testcase {
$quba2->set_preferred_behaviour('deferredfeedback');
$slot2 = $quba2->add_question($question);
$quba2->start_all_questions(new core_question\engine\variants\least_used_strategy(
$quba1, new qubaid_list(array($quba1->get_id()))));
$quba1, new qubaid_list([$quba1->get_id()])));
question_engine::save_questions_usage_by_activity($quba2);
$variant2 = $quba2->get_variant($slot2);
@ -116,7 +116,7 @@ class least_used_variant_strategy_testcase extends advanced_testcase {
$quba3->set_preferred_behaviour('deferredfeedback');
$slot3 = $quba3->add_question($question);
$quba3->start_all_questions(new core_question\engine\variants\least_used_strategy(
$quba1, new qubaid_list(array($quba1->get_id(), $quba2->get_id()))));
$quba1, new qubaid_list([$quba1->get_id(), $quba2->get_id()])));
$variant3 = $quba3->get_variant($slot3);
$this->assertTrue($variant3 == $variant1 || $variant3 == $variant2);

View File

@ -305,17 +305,17 @@ class core_question_privacy_provider_testcase extends \core_privacy\tests\provid
// Q4 - Created by the other user, Modified by the other user.
// Q5 - Created by the UUT, Modified by the UUT, but in a different context.
$this->setUser($user);
$q1 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q2 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q1 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
$q2 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
$this->setUser($otheruser);
$questiongenerator->update_question($q2);
$q3 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q4 = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$q3 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
$q4 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
$this->setUser($user);
$questiongenerator->update_question($q3);
$q5 = $questiongenerator->create_question('shortanswer', null, array('category' => $othercat->id));
$q5 = $questiongenerator->create_question('shortanswer', null, ['category' => $othercat->id]);
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
$user,

View File

@ -44,7 +44,7 @@ class question_bank_column_testcase extends advanced_testcase {
public function test_column_header_multi_sort_no_tooltips() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$questionbank = new core_question\bank\view(
$questionbank = new core_question\local\bank\view(
new question_edit_contexts(context_course::instance($course->id)),
new moodle_url('/'),
$course
@ -67,8 +67,8 @@ class question_bank_column_testcase extends advanced_testcase {
$columnbase->display_header();
$output = ob_get_clean();
$this->assertStringContainsString(' title="Sort by Apple ascending">Apple</a>', $output);
$this->assertStringContainsString(' title="Sort by Banana ascending">Banana</a>', $output);
$this->assertStringContainsString(' title="Sort by Apple ascending">', $output);
$this->assertStringContainsString(' title="Sort by Banana ascending">', $output);
}
/**
@ -78,7 +78,7 @@ class question_bank_column_testcase extends advanced_testcase {
public function test_column_header_multi_sort_with_tooltips() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$questionbank = new core_question\bank\view(
$questionbank = new core_question\local\bank\view(
new question_edit_contexts(context_course::instance($course->id)),
new moodle_url('/'),
$course
@ -103,7 +103,7 @@ class question_bank_column_testcase extends advanced_testcase {
$columnbase->display_header();
$output = ob_get_clean();
$this->assertStringContainsString(' title="Sort by Apple Tooltips ascending">Apple</a>', $output);
$this->assertStringContainsString(' title="Sort by Banana Tooltips ascending">Banana</a>', $output);
$this->assertStringContainsString(' title="Sort by Apple Tooltips ascending">', $output);
$this->assertStringContainsString(' title="Sort by Banana Tooltips ascending">', $output);
}
}

View File

@ -15,7 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tests for the {@link core_question\bank\random_question_loader} class.
* Tests for the {@see core_question\local\bank\random_question_loader} class.
*
* @package core_question
* @copyright 2015 The Open University
@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die();
/**
* Tests for the {@link core_question\bank\random_question_loader} class.
* Tests for the {@see core_question\local\bank\random_question_loader} class.
*
* @copyright 2015 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@ -38,7 +38,7 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertNull($loader->get_next_question_id($cat->id, 0));
$this->assertNull($loader->get_next_question_id($cat->id, 1));
@ -46,7 +46,7 @@ class random_question_loader_testcase extends advanced_testcase {
public function test_unknown_category_behaves_like_empty() {
// It is up the caller to make sure the category id is valid.
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertNull($loader->get_next_question_id(-1, 1));
}
@ -55,8 +55,8 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$info = $generator->create_question('description', null, array('category' => $cat->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$info = $generator->create_question('description', null, ['category' => $cat->id]);
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertNull($loader->get_next_question_id($cat->id, 0));
}
@ -67,9 +67,9 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$DB->set_field('question', 'hidden', 1, array('id' => $question1->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$DB->set_field('question', 'hidden', 1, ['id' => $question1->id]);
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertNull($loader->get_next_question_id($cat->id, 0));
}
@ -79,8 +79,8 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$question1 = $generator->create_question('multianswer', null, array('category' => $cat->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$question1 = $generator->create_question('multianswer', null, ['category' => $cat->id]);
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 0));
$this->assertNull($loader->get_next_question_id($cat->id, 0));
@ -93,9 +93,9 @@ class random_question_loader_testcase extends advanced_testcase {
$cat = $generator->create_question_category();
$course = $this->getDataGenerator()->create_course();
$quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course));
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course]);
quiz_add_random_questions($quiz, 1, $cat->id, 1, false);
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertNull($loader->get_next_question_id($cat->id, 0));
}
@ -105,8 +105,8 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 1));
$this->assertNull($loader->get_next_question_id($cat->id, 0));
@ -117,15 +117,15 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$question2 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$question2 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$questionids = array();
$questionids = [];
$questionids[] = $loader->get_next_question_id($cat->id, 0);
$questionids[] = $loader->get_next_question_id($cat->id, 0);
sort($questionids);
$this->assertEquals(array($question1->id, $question2->id), $questionids);
$this->assertEquals([$question1->id, $question2->id], $questionids);
$this->assertNull($loader->get_next_question_id($cat->id, 1));
}
@ -135,10 +135,10 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat1 = $generator->create_question_category();
$cat2 = $generator->create_question_category(array('parent' => $cat1->id));
$question1 = $generator->create_question('shortanswer', null, array('category' => $cat1->id));
$question2 = $generator->create_question('shortanswer', null, array('category' => $cat2->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$cat2 = $generator->create_question_category(['parent' => $cat1->id]);
$question1 = $generator->create_question('shortanswer', null, ['category' => $cat1->id]);
$question2 = $generator->create_question('shortanswer', null, ['category' => $cat2->id]);
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$this->assertEquals($question2->id, $loader->get_next_question_id($cat2->id, 1));
$this->assertEquals($question1->id, $loader->get_next_question_id($cat1->id, 1));
@ -151,9 +151,9 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$question2 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()),
$question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$question2 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]),
array($question2->id => 2));
$this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 0));
@ -165,8 +165,8 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$question2 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$question1 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$question2 = $generator->create_question('shortanswer', null, ['category' => $cat->id]);
$quba = question_engine::make_questions_usage_by_activity('test', context_system::instance());
$quba->set_preferred_behaviour('deferredfeedback');
$question = question_bank::load_question($question2->id);
@ -175,7 +175,7 @@ class random_question_loader_testcase extends advanced_testcase {
$quba->start_all_questions();
question_engine::save_questions_usage_by_activity($quba);
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array($quba->get_id())));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array($quba->get_id())));
$this->assertEquals($question1->id, $loader->get_next_question_id($cat->id, 0));
$this->assertEquals($question2->id, $loader->get_next_question_id($cat->id, 0));
@ -187,7 +187,7 @@ class random_question_loader_testcase extends advanced_testcase {
$generator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $generator->create_question_category();
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array()));
$this->assertFalse($loader->is_question_available($cat->id, 0, 1));
$this->assertFalse($loader->is_question_available($cat->id, 1, 1));
@ -199,7 +199,7 @@ class random_question_loader_testcase extends advanced_testcase {
$cat = $generator->create_question_category();
$info = $generator->create_question('description', null, array('category' => $cat->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array()));
$this->assertFalse($loader->is_question_available($cat->id, 0, $info->id));
$this->assertFalse($loader->is_question_available($cat->id, 1, $info->id));
@ -211,7 +211,7 @@ class random_question_loader_testcase extends advanced_testcase {
$cat = $generator->create_question_category();
$question1 = $generator->create_question('shortanswer', null, array('category' => $cat->id));
$loader = new \core_question\bank\random_question_loader(new qubaid_list(array()));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list(array()));
$this->assertTrue($loader->is_question_available($cat->id, 0, $question1->id));
$this->assertFalse($loader->is_question_available($cat->id, 0, $question1->id));
@ -224,54 +224,54 @@ class random_question_loader_testcase extends advanced_testcase {
*/
public function get_questions_test_cases() {
return [
'empty category' => [
'categoryindex' => 'emptycat',
'includesubcategories' => false,
'usetagnames' => [],
'expectedquestionindexes' => []
],
'single category' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => [],
'expectedquestionindexes' => ['cat1q1', 'cat1q2']
],
'include sub category' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => [],
'expectedquestionindexes' => ['cat1q1', 'cat1q2', 'subcatq1', 'subcatq2']
],
'single category with tags' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => ['cat1'],
'expectedquestionindexes' => ['cat1q1']
],
'include sub category with tag on parent' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1'],
'expectedquestionindexes' => ['cat1q1']
],
'include sub category with tag on sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['subcat'],
'expectedquestionindexes' => ['subcatq1']
],
'include sub category with same tag on parent and sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['foo'],
'expectedquestionindexes' => ['cat1q1', 'subcatq1']
],
'include sub category with tag not matching' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1', 'cat2'],
'expectedquestionindexes' => []
]
'empty category' => [
'categoryindex' => 'emptycat',
'includesubcategories' => false,
'usetagnames' => [],
'expectedquestionindexes' => []
],
'single category' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => [],
'expectedquestionindexes' => ['cat1q1', 'cat1q2']
],
'include sub category' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => [],
'expectedquestionindexes' => ['cat1q1', 'cat1q2', 'subcatq1', 'subcatq2']
],
'single category with tags' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => ['cat1'],
'expectedquestionindexes' => ['cat1q1']
],
'include sub category with tag on parent' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1'],
'expectedquestionindexes' => ['cat1q1']
],
'include sub category with tag on sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['subcat'],
'expectedquestionindexes' => ['subcatq1']
],
'include sub category with same tag on parent and sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['foo'],
'expectedquestionindexes' => ['cat1q1', 'subcatq1']
],
'include sub category with tag not matching' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1', 'cat2'],
'expectedquestionindexes' => []
]
];
}
@ -301,20 +301,20 @@ class random_question_loader_testcase extends advanced_testcase {
* @param string[] $expectedquestionindexes The questions expected in the result
*/
public function test_get_questions_variations(
$categoryindex,
$includesubcategories,
$usetagnames,
$expectedquestionindexes
$categoryindex,
$includesubcategories,
$usetagnames,
$expectedquestionindexes
) {
$this->resetAfterTest();
$categories = [];
$questions = [];
$tagnames = [
'cat1',
'cat2',
'subcat',
'foo'
'cat1',
'cat2',
'subcat',
'foo'
];
$collid = core_tag_collection::get_default();
$tags = core_tag_tag::create_if_missing($collid, $tagnames);
@ -345,7 +345,7 @@ class random_question_loader_testcase extends advanced_testcase {
return $tags[$tagname]->id;
}, $usetagnames);
$loader = new \core_question\bank\random_question_loader(new qubaid_list([]));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$result = $loader->get_questions($category->id, $includesubcategories, $tagids);
// Generate the expected question set.
$expectedquestions = array_map(function($index) use ($questions) {
@ -370,7 +370,7 @@ class random_question_loader_testcase extends advanced_testcase {
$tagids = [];
$limit = 1;
$offset = 0;
$loader = new \core_question\bank\random_question_loader(new qubaid_list([]));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
list($category, $questions) = $this->create_category_and_questions($numberofquestions);
// Sort the questions by id to match the ordering of the get_questions
@ -387,11 +387,11 @@ class random_question_loader_testcase extends advanced_testcase {
for ($i = 0; $i < $numberofquestions; $i++) {
$result = $loader->get_questions(
$category->id,
$includesubcategories,
$tagids,
$limit,
$offset
$category->id,
$includesubcategories,
$tagids,
$limit,
$offset
);
$this->assertCount($limit, $result);
@ -413,16 +413,16 @@ class random_question_loader_testcase extends advanced_testcase {
$limit = 10;
$offset = 0;
$fields = ['id', 'name'];
$loader = new \core_question\bank\random_question_loader(new qubaid_list([]));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
list($category, $questions) = $this->create_category_and_questions(1);
$result = $loader->get_questions(
$category->id,
$includesubcategories,
$tagids,
$limit,
$offset,
$fields
$category->id,
$includesubcategories,
$tagids,
$limit,
$offset,
$fields
);
$expectedquestion = array_shift($questions);
@ -440,54 +440,54 @@ class random_question_loader_testcase extends advanced_testcase {
*/
public function count_questions_test_cases() {
return [
'empty category' => [
'categoryindex' => 'emptycat',
'includesubcategories' => false,
'usetagnames' => [],
'expectedcount' => 0
],
'single category' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => [],
'expectedcount' => 2
],
'include sub category' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => [],
'expectedcount' => 4
],
'single category with tags' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => ['cat1'],
'expectedcount' => 1
],
'include sub category with tag on parent' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1'],
'expectedcount' => 1
],
'include sub category with tag on sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['subcat'],
'expectedcount' => 1
],
'include sub category with same tag on parent and sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['foo'],
'expectedcount' => 2
],
'include sub category with tag not matching' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1', 'cat2'],
'expectedcount' => 0
]
'empty category' => [
'categoryindex' => 'emptycat',
'includesubcategories' => false,
'usetagnames' => [],
'expectedcount' => 0
],
'single category' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => [],
'expectedcount' => 2
],
'include sub category' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => [],
'expectedcount' => 4
],
'single category with tags' => [
'categoryindex' => 'cat1',
'includesubcategories' => false,
'usetagnames' => ['cat1'],
'expectedcount' => 1
],
'include sub category with tag on parent' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1'],
'expectedcount' => 1
],
'include sub category with tag on sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['subcat'],
'expectedcount' => 1
],
'include sub category with same tag on parent and sub' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['foo'],
'expectedcount' => 2
],
'include sub category with tag not matching' => [
'categoryindex' => 'cat1',
'includesubcategories' => true,
'usetagnames' => ['cat1', 'cat2'],
'expectedcount' => 0
]
];
}
@ -517,20 +517,20 @@ class random_question_loader_testcase extends advanced_testcase {
* @param int $expectedcount The number of questions expected in the result
*/
public function test_count_questions_variations(
$categoryindex,
$includesubcategories,
$usetagnames,
$expectedcount
$categoryindex,
$includesubcategories,
$usetagnames,
$expectedcount
) {
$this->resetAfterTest();
$categories = [];
$questions = [];
$tagnames = [
'cat1',
'cat2',
'subcat',
'foo'
'cat1',
'cat2',
'subcat',
'foo'
];
$collid = core_tag_collection::get_default();
$tags = core_tag_tag::create_if_missing($collid, $tagnames);
@ -561,7 +561,7 @@ class random_question_loader_testcase extends advanced_testcase {
return $tags[$tagname]->id;
}, $usetagnames);
$loader = new \core_question\bank\random_question_loader(new qubaid_list([]));
$loader = new \core_question\local\bank\random_question_loader(new qubaid_list([]));
$result = $loader->count_questions($category->id, $includesubcategories, $tagids);
// Ensure the result matches what was expected.

View File

@ -334,12 +334,20 @@ class question_dataset_dependent_items_form extends question_wizard_form {
// Submit buttons.
if ($this->noofitems > 0) {
$buttonarray = array();
$buttonarray = [];
$buttonarray[] = $mform->createElement(
'submit', 'savechanges', get_string('savechanges'));
$previewlink = $PAGE->get_renderer('core_question')->question_preview_link(
// Todo MDL-72004 changes for class renaming and default sort.
if (class_exists('qbank_previewquestion\\preview_action_column')) {
if (\core\plugininfo\qbank::is_plugin_enabled('qbank_previewquestion')) {
$previewlink = $PAGE->get_renderer('qbank_previewquestion')->question_preview_link(
$this->question->id, $this->categorycontext, true);
}
} else {
$previewlink = $PAGE->get_renderer('core_question')->question_preview_link(
$this->question->id, $this->categorycontext, true);
}
$buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink);
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);

View File

@ -233,7 +233,7 @@ abstract class question_edit_form extends question_wizard_form {
$a->user = get_string('unknown', 'question');
}
$mform->addElement('static', 'created', get_string('created', 'question'),
get_string('byandon', 'question', $a));
get_string('byandon', 'question', $a));
if (!empty($this->question->modifiedby)) {
$a = new stdClass();
$a->time = userdate($this->question->timemodified);
@ -254,10 +254,18 @@ abstract class question_edit_form extends question_wizard_form {
$buttonarray = array();
$buttonarray[] = $mform->createElement('submit', 'updatebutton',
get_string('savechangesandcontinueediting', 'question'));
get_string('savechangesandcontinueediting', 'question'));
if ($this->can_preview()) {
$previewlink = $PAGE->get_renderer('core_question')->question_preview_link(
$this->question->id, $this->context, true);
// Todo MDL-72004 changes for class renaming and default sort.
if (class_exists('qbank_previewquestion\\preview_action_column')) {
if (\core\plugininfo\qbank::is_plugin_enabled('qbank_previewquestion')) {
$previewlink = $PAGE->get_renderer('qbank_previewquestion')->question_preview_link(
$this->question->id, $this->context, true);
}
} else {
$previewlink = $PAGE->get_renderer('core_question')->question_preview_link(
$this->question->id, $this->context, true);
}
$buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink);
}
@ -267,7 +275,7 @@ abstract class question_edit_form extends question_wizard_form {
$this->add_action_buttons(true, get_string('savechanges'));
if ((!empty($this->question->id)) && (!($this->question->formoptions->canedit ||
$this->question->formoptions->cansaveasnew))) {
$this->question->formoptions->cansaveasnew))) {
$mform->hardFreezeAllVisibleExcept(array('categorymoveto', 'buttonar', 'currentgrp'));
}
}
@ -309,7 +317,7 @@ abstract class question_edit_form extends question_wizard_form {
$answeroptions[] = $mform->createElement('select', 'fraction',
get_string('gradenoun'), $gradeoptions);
$repeated[] = $mform->createElement('group', 'answeroptions',
$label, $answeroptions, null, false);
$label, $answeroptions, null, false);
$repeated[] = $mform->createElement('editor', 'feedback',
get_string('feedback', 'question'), array('rows' => 5), $this->editoroptions);
$repeatedoptions['answer']['type'] = PARAM_RAW;
@ -377,7 +385,7 @@ abstract class question_edit_form extends question_wizard_form {
// and the question isn't a course or activity level question then
// allow course tags to be added to the course.
$coursetagheader = get_string('questionformtagheader', 'core_question',
$editingcoursecontext->get_context_name(true));
$editingcoursecontext->get_context_name(true));
$mform->addElement('header', 'coursetagsheader', $coursetagheader);
$mform->addElement('autocomplete', 'coursetags', get_string('tags'), $tagstrings, $options);
@ -400,7 +408,7 @@ abstract class question_edit_form extends question_wizard_form {
protected function add_per_answer_fields(&$mform, $label, $gradeoptions,
$minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) {
$mform->addElement('header', 'answerhdr',
get_string('answers', 'question'), '');
get_string('answers', 'question'), '');
$mform->setExpanded('answerhdr', 1);
$answersoption = '';
$repeatedoptions = array();
@ -434,8 +442,8 @@ abstract class question_edit_form extends question_wizard_form {
$fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback');
foreach ($fields as $feedbackname) {
$element = $mform->addElement('editor', $feedbackname,
get_string($feedbackname, 'question'),
array('rows' => 5), $this->editoroptions);
get_string($feedbackname, 'question'),
array('rows' => 5), $this->editoroptions);
$mform->setType($feedbackname, PARAM_RAW);
// Using setValue() as setDefault() does not work for the editor class.
$element->setValue(array('text' => get_string($feedbackname.'default', 'question')));
@ -476,7 +484,7 @@ abstract class question_edit_form extends question_wizard_form {
if (count($optionelements)) {
$repeated[] = $mform->createElement('group', 'hintoptions',
get_string('hintnoptions', 'question'), $optionelements, null, false);
get_string('hintnoptions', 'question'), $optionelements, null, false);
}
return array($repeated, $repeatedoptions);
@ -620,13 +628,13 @@ abstract class question_edit_form extends question_wizard_form {
// Prepare the feedback editor to display files in draft area.
$draftitemid = file_get_submitted_draft_itemid('answer['.$key.']');
$question->answer[$key]['text'] = file_prepare_draft_area(
$draftitemid, // Draftid
$this->context->id, // context
'question', // component
'answer', // filarea
!empty($answer->id) ? (int) $answer->id : null, // itemid
$this->fileoptions, // options
$answer->answer // text.
$draftitemid, // Draftid.
$this->context->id, // Context.
'question', // Component.
'answer', // Filarea.
!empty($answer->id) ? (int) $answer->id : null, // Itemid.
$this->fileoptions, // Options.
$answer->answer // Text.
);
$question->answer[$key]['itemid'] = $draftitemid;
$question->answer[$key]['format'] = $answer->answerformat;
@ -651,13 +659,13 @@ abstract class question_edit_form extends question_wizard_form {
// Prepare the feedback editor to display files in draft area.
$draftitemid = file_get_submitted_draft_itemid('feedback['.$key.']');
$question->feedback[$key]['text'] = file_prepare_draft_area(
$draftitemid, // Draftid
$this->context->id, // context
'question', // component
'answerfeedback', // filarea
!empty($answer->id) ? (int) $answer->id : null, // itemid
$this->fileoptions, // options
$answer->feedback // text.
$draftitemid, // Draftid.
$this->context->id, // Context.
'question', // Component.
'answerfeedback', // Filarea.
!empty($answer->id) ? (int) $answer->id : null, // Itemid.
$this->fileoptions, // Options.
$answer->feedback // Text.
);
$question->feedback[$key]['itemid'] = $draftitemid;
$question->feedback[$key]['format'] = $answer->feedbackformat;
@ -742,13 +750,13 @@ abstract class question_edit_form extends question_wizard_form {
$draftid = file_get_submitted_draft_itemid($feedbackname);
$feedback = array();
$feedback['text'] = file_prepare_draft_area(
$draftid, // Draftid
$this->context->id, // context
'question', // component
$feedbackname, // filarea
!empty($question->id) ? (int) $question->id : null, // itemid
$this->fileoptions, // options
$question->options->$feedbackname // text.
$draftid, // Draftid.
$this->context->id, // Context.
'question', // Component.
$feedbackname, // Filarea.
!empty($question->id) ? (int) $question->id : null, // Itemid.
$this->fileoptions, // Options.
$question->options->$feedbackname // Text.
);
$feedbackformat = $feedbackname . 'format';
$feedback['format'] = $question->options->$feedbackformat;
@ -782,13 +790,13 @@ abstract class question_edit_form extends question_wizard_form {
// Prepare feedback editor to display files in draft area.
$draftitemid = file_get_submitted_draft_itemid('hint['.$key.']');
$question->hint[$key]['text'] = file_prepare_draft_area(
$draftitemid, // Draftid
$this->context->id, // context
'question', // component
'hint', // filarea
!empty($hint->id) ? (int) $hint->id : null, // itemid
$this->fileoptions, // options
$hint->hint // text.
$draftitemid, // Draftid.
$this->context->id, // Context.
'question', // Component.
'hint', // Filarea.
!empty($hint->id) ? (int) $hint->id : null, // Itemid.
$this->fileoptions, // Options.
$hint->hint // Text.
);
$question->hint[$key]['itemid'] = $draftitemid;
$question->hint[$key]['format'] = $hint->hintformat;

View File

@ -1,5 +1,12 @@
This files describes API changes for code that uses the question API.
=== 4.0 ==
1) Previously, the questionbank api classes were coupled in one place. Now the classes
are divided in two different parts, base classes and feature classes. All the base
classes are moved classes/local/bank and all the feature classes will be moved to
the plugin for that feature.
=== 3.9 ==
1) For years, the ..._questions_in_use callback has been the right way for plugins to