From 351176bb7186b6da39d36e0ba6e3f1eca6f39df2 Mon Sep 17 00:00:00 2001 From: safatshahin Date: Thu, 24 Jun 2021 22:49:04 +1000 Subject: [PATCH] MDL-71516 core_question: Create new plugin type - qbank This commit implements the qbank plugin type which includes the boilerplate for the qbank plugin, the qbank plugin management admin page and required core code addition. --- admin/qbankplugins.php | 64 ++++++++ admin/settings/plugins.php | 14 ++ lang/en/admin.php | 1 + lang/en/plugin.php | 2 + lang/en/question.php | 3 + lib/classes/plugin_manager.php | 5 + lib/classes/plugininfo/qbank.php | 147 ++++++++++++++++++ lib/components.json | 1 + question/bank/upgrade.txt | 2 + .../admin/manage_qbank_plugins_page.php | 143 +++++++++++++++++ 10 files changed, 382 insertions(+) create mode 100644 admin/qbankplugins.php create mode 100644 lib/classes/plugininfo/qbank.php create mode 100644 question/bank/upgrade.txt create mode 100644 question/classes/admin/manage_qbank_plugins_page.php diff --git a/admin/qbankplugins.php b/admin/qbankplugins.php new file mode 100644 index 00000000000..2143246931b --- /dev/null +++ b/admin/qbankplugins.php @@ -0,0 +1,64 @@ +. + +/** + * Question bank plugin settings. + * + * @package core + * @subpackage questionbank + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @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', array('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); + diff --git a/admin/settings/plugins.php b/admin/settings/plugins.php index 7c75aa2e2da..2ea6a867da0 100644 --- a/admin/settings/plugins.php +++ b/admin/settings/plugins.php @@ -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('questionbanks', 'question'))); + $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)) { diff --git a/lang/en/admin.php b/lang/en/admin.php index d45a8bd1dd9..1575e8c3e07 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -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'; diff --git a/lang/en/plugin.php b/lang/en/plugin.php index 0820cb8643b..14eb30a2e12 100644 --- a/lang/en/plugin.php +++ b/lang/en/plugin.php @@ -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'; +$string['type_qbank_plural'] = 'Question banks'; $string['updateavailable'] = 'There is a new version {$a} available!'; $string['updateavailable_moreinfo'] = 'More info...'; $string['updateavailable_release'] = 'Release {$a}'; diff --git a/lang/en/question.php b/lang/en/question.php index b7dde536a8f..26c98ad3e6a 100644 --- a/lang/en/question.php +++ b/lang/en/question.php @@ -494,3 +494,6 @@ $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['questionbanks'] = 'Question bank plugins'; +$string['qbanknotfound'] = 'The \'{$a}\' question bank doesn\'t exist or is not recognised.'; +$string['noquestionbanks'] = 'No question bank plugin found.'; diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index de8401c7e42..48293e1113b 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -1938,6 +1938,10 @@ class core_plugin_manager { 'checkbox', 'datetime', 'menu', 'social', 'text', 'textarea' ), + 'qbank' => array( + '' + ), + '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']; diff --git a/lib/classes/plugininfo/qbank.php b/lib/classes/plugininfo/qbank.php new file mode 100644 index 00000000000..608a0084630 --- /dev/null +++ b/lib/classes/plugininfo/qbank.php @@ -0,0 +1,147 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @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 + * @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', array('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 = array(); + foreach ($order as $qbankname) { + $sortedqbanks[$qbankname] = $qbank[$qbankname]; + } + return $sortedqbanks; + } + + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins(): ?array { + global $CFG; + $pluginmanager = \core_plugin_manager::instance(); + $plugins = $pluginmanager->get_installed_plugins('qbank'); + + if (!$plugins) { + return array(); + } + + $plugins = array_keys($plugins); + + // Filter to return only enabled plugins. + $enabled = array(); + 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_ready($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; + } + + /** + * Loads plugin settings to the settings tree + * + * This function usually includes settings.php file in plugins folder. + * Alternatively it can create a link to some settings page (instance of admin_externalpage) + * + * @param \part_of_admin_tree $adminroot + * @param string $parentnodename + * @param bool $hassiteconfig whether the current user has moodle/site:config capability + */ + 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); + } + } +} diff --git a/lib/components.json b/lib/components.json index a9a635fd6ef..73dea36498e 100644 --- a/lib/components.json +++ b/lib/components.json @@ -28,6 +28,7 @@ "repository": "repository", "portfolio": "portfolio", "search": "search\/engine", + "qbank": "question\/bank", "qbehaviour": "question\/behaviour", "qformat": "question\/format", "plagiarism": "plagiarism", diff --git a/question/bank/upgrade.txt b/question/bank/upgrade.txt new file mode 100644 index 00000000000..a7836ce8c92 --- /dev/null +++ b/question/bank/upgrade.txt @@ -0,0 +1,2 @@ +This file describes core qbank plugin changes in /question/bank/*, +information provided here is intended especially for developers. diff --git a/question/classes/admin/manage_qbank_plugins_page.php b/question/classes/admin/manage_qbank_plugins_page.php new file mode 100644 index 00000000000..da17242364a --- /dev/null +++ b/question/classes/admin/manage_qbank_plugins_page.php @@ -0,0 +1,143 @@ +. + +/** + * Manage question banks page. + * + * @package core_question + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_question\admin; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Class manage_qbank_plugins_page. + * + * @copyright 2021 Catalyst IT Australia Pty Ltd + * @author Safat Shahin + * @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(array('settings', 'name', 'enable', 'disable', 'default')); + $txt->uninstall = get_string('uninstallplugin', 'core_admin'); + + $table = new \html_table(); + $table->head = array($txt->name, $txt->enable, $txt->settings, $txt->uninstall); + $table->align = array('left', 'center', 'center', 'center', 'center'); + $table->attributes['class'] = 'manageqbanktable generaltable admintable'; + $table->data = array(); + + $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', + array('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, array('action' => 'disable')), + $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall'))); + } else { + $class = 'dimmed_text'; + $hideshow = \html_writer::link($url->out(false, array('action' => 'enable')), + $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('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(array($strtypename, $hideshow, $settings, $uninstall)); + if ($class) { + $row->attributes['class'] = $class; + } + $table->data[] = $row; + $count++; + } + $return .= \html_writer::table($table); + return highlight($query, $return); + } +}