Skip to content

Commit

Permalink
MDL-71516 core_question: Qbank api implementation
Browse files Browse the repository at this point in the history
This commit implements the qbank api so that any plugin
can implement its own question bank. This api currently
works parallely with the moodle core classes and the
added qbank in the core, means the moment a plugin
is installed, that object is replaced with the object
from the plugin instead of core, which means the api
has flexibility till the plugins are integrated and the
plugins can be integrated in any order.

All the old classes are still there and not deprecated
as there is a different tracker for the changes to the
quiz and another tracker for class deprecation and
class renaming. Core question units tests are pointing
to the new api structure but the classes are pointing
to the location related to the plugin availability.

Co-Authored-By: Luca Bösch <luca.boesch@bfh.ch>
Co-Authored-By: Guillermo Gomez Arias <guillermogomez@catalyst-au.net>

one more array fix
  • Loading branch information
safatshahin committed Aug 17, 2021
1 parent 351176b commit dfed4fd
Show file tree
Hide file tree
Showing 56 changed files with 1,838 additions and 862 deletions.
3 changes: 1 addition & 2 deletions admin/qbankplugins.php
Expand Up @@ -36,7 +36,7 @@

require_admin();

$return = new moodle_url('/admin/settings.php', array('section' => 'manageqbanks'));
$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));
Expand All @@ -61,4 +61,3 @@
core_plugin_manager::reset_caches();

redirect($return);

2 changes: 1 addition & 1 deletion admin/settings/plugins.php
Expand Up @@ -406,7 +406,7 @@
// Question bank settings.
if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) {
$ADMIN->add('modules', new admin_category('qbanksettings',
new lang_string('questionbanks', 'question')));
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);
Expand Down
4 changes: 2 additions & 2 deletions lang/en/plugin.php
Expand Up @@ -192,8 +192,8 @@
$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['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}';
Expand Down
3 changes: 1 addition & 2 deletions lang/en/question.php
Expand Up @@ -494,6 +494,5 @@
$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['qbanknotfound'] = 'The \'{$a}\' question bank plugin doesn\'t exist or is not recognised.';
$string['noquestionbanks'] = 'No question bank plugin found.';
4 changes: 2 additions & 2 deletions lib/classes/plugin_manager.php
Expand Up @@ -1938,9 +1938,9 @@ public static function standard_plugins_list($type) {
'checkbox', 'datetime', 'menu', 'social', 'text', 'textarea'
),

'qbank' => array(
'qbank' => [
''
),
],

'qbehaviour' => array(
'adaptive', 'adaptivenopenalty', 'deferredcbm',
Expand Down
24 changes: 5 additions & 19 deletions lib/classes/plugininfo/qbank.php
Expand Up @@ -42,38 +42,34 @@ public function is_uninstall_allowed(): bool {
}

public static function get_manage_url(): \moodle_url {
return new \moodle_url('/admin/settings.php', array('section' => 'manageqbanks'));
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 = array();
$sortedqbanks = [];
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();
return [];
}

$plugins = array_keys($plugins);

// Filter to return only enabled plugins.
$enabled = array();
$enabled = [];
foreach ($plugins as $plugin) {
$qbankinfo = $pluginmanager->get_plugin_info('qbank_'.$plugin);
$qbankavailable = $qbankinfo->get_status();
Expand All @@ -95,7 +91,7 @@ public static function get_enabled_plugins(): ?array {
* @param string $fullpluginname the name of the plugin
* @return bool
*/
public static function is_ready($fullpluginname): bool {
public static function is_plugin_enabled($fullpluginname): bool {
$pluginmanager = \core_plugin_manager::instance();
$qbankinfo = $pluginmanager->get_plugin_info($fullpluginname);
if (empty($qbankinfo)) {
Expand All @@ -109,16 +105,6 @@ public static function is_ready($fullpluginname): bool {
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.
Expand Down
100 changes: 82 additions & 18 deletions lib/questionlib.php
Expand Up @@ -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;
}
Expand All @@ -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()
];
}
}

$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');
}
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');

// 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()
];
}
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');

$contexts = new question_edit_contexts($context);
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('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;
Expand Down
76 changes: 76 additions & 0 deletions mod/quiz/classes/question/bank/question_name_column.php
@@ -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';
}
}
Expand Up @@ -33,7 +33,7 @@
* @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';
}
Expand Down
5 changes: 3 additions & 2 deletions question/category_class.php
Expand Up @@ -133,14 +133,15 @@ public function set_icon_html($first, $last, $lastitem){
}

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]);
Expand Down

0 comments on commit dfed4fd

Please sign in to comment.