Commit dfed4fd0 authored by safatshahin's avatar safatshahin Committed by Safat Shahin
Browse files

MDL-71516 core_question: Qbank api implementation



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: default avatarLuca Bösch <luca.boesch@bfh.ch>
Co-Authored-By: default avatarGuillermo Gomez Arias <guillermogomez@catalyst-au.net>

one more array fix
parent 351176bb
...@@ -36,7 +36,7 @@ $PAGE->set_context($syscontext); ...@@ -36,7 +36,7 @@ $PAGE->set_context($syscontext);
require_admin(); 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'); $plugins = core_plugin_manager::instance()->get_plugins_of_type('qbank');
$sortorder = array_flip(array_keys($plugins)); $sortorder = array_flip(array_keys($plugins));
...@@ -61,4 +61,3 @@ switch ($action) { ...@@ -61,4 +61,3 @@ switch ($action) {
core_plugin_manager::reset_caches(); core_plugin_manager::reset_caches();
redirect($return); redirect($return);
...@@ -406,7 +406,7 @@ if ($hassiteconfig) { ...@@ -406,7 +406,7 @@ if ($hassiteconfig) {
// Question bank settings. // Question bank settings.
if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) { if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) {
$ADMIN->add('modules', new admin_category('qbanksettings', $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 = new admin_settingpage('manageqbanks', new lang_string('manageqbanks', 'admin'));
$temp->add(new \core_question\admin\manage_qbank_plugins_page()); $temp->add(new \core_question\admin\manage_qbank_plugins_page());
$ADMIN->add('qbanksettings', $temp); $ADMIN->add('qbanksettings', $temp);
......
...@@ -192,8 +192,8 @@ $string['type_tool'] = 'Admin tool'; ...@@ -192,8 +192,8 @@ $string['type_tool'] = 'Admin tool';
$string['type_tool_plural'] = 'Admin tools'; $string['type_tool_plural'] = 'Admin tools';
$string['type_webservice'] = 'Webservice protocol'; $string['type_webservice'] = 'Webservice protocol';
$string['type_webservice_plural'] = 'Webservice protocols'; $string['type_webservice_plural'] = 'Webservice protocols';
$string['type_qbank'] = 'Question bank'; $string['type_qbank'] = 'Question bank plugin';
$string['type_qbank_plural'] = 'Question banks'; $string['type_qbank_plural'] = 'Question bank plugins';
$string['updateavailable'] = 'There is a new version {$a} available!'; $string['updateavailable'] = 'There is a new version {$a} available!';
$string['updateavailable_moreinfo'] = 'More info...'; $string['updateavailable_moreinfo'] = 'More info...';
$string['updateavailable_release'] = 'Release {$a}'; $string['updateavailable_release'] = 'Release {$a}';
......
...@@ -494,6 +494,5 @@ $string['whichtries'] = 'Which tries'; ...@@ -494,6 +494,5 @@ $string['whichtries'] = 'Which tries';
$string['withselected'] = 'With selected'; $string['withselected'] = 'With selected';
$string['xoutofmax'] = '{$a->mark} out of {$a->max}'; $string['xoutofmax'] = '{$a->mark} out of {$a->max}';
$string['yougotnright'] = 'You have correctly selected {$a->num}.'; $string['yougotnright'] = 'You have correctly selected {$a->num}.';
$string['questionbanks'] = 'Question bank plugins'; $string['qbanknotfound'] = 'The \'{$a}\' question bank plugin doesn\'t exist or is not recognised.';
$string['qbanknotfound'] = 'The \'{$a}\' question bank doesn\'t exist or is not recognised.';
$string['noquestionbanks'] = 'No question bank plugin found.'; $string['noquestionbanks'] = 'No question bank plugin found.';
...@@ -1938,9 +1938,9 @@ class core_plugin_manager { ...@@ -1938,9 +1938,9 @@ class core_plugin_manager {
'checkbox', 'datetime', 'menu', 'social', 'text', 'textarea' 'checkbox', 'datetime', 'menu', 'social', 'text', 'textarea'
), ),
'qbank' => array( 'qbank' => [
'' ''
), ],
'qbehaviour' => array( 'qbehaviour' => array(
'adaptive', 'adaptivenopenalty', 'deferredcbm', 'adaptive', 'adaptivenopenalty', 'deferredcbm',
......
...@@ -42,7 +42,7 @@ class qbank extends base { ...@@ -42,7 +42,7 @@ class qbank extends base {
} }
public static function get_manage_url(): \moodle_url { 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 { public static function get_plugins($type, $typerootdir, $typeclass, $pluginman): array {
...@@ -50,30 +50,26 @@ class qbank extends base { ...@@ -50,30 +50,26 @@ class qbank extends base {
$qbank = parent::get_plugins($type, $typerootdir, $typeclass, $pluginman); $qbank = parent::get_plugins($type, $typerootdir, $typeclass, $pluginman);
$order = array_keys($qbank); $order = array_keys($qbank);
$sortedqbanks = array(); $sortedqbanks = [];
foreach ($order as $qbankname) { foreach ($order as $qbankname) {
$sortedqbanks[$qbankname] = $qbank[$qbankname]; $sortedqbanks[$qbankname] = $qbank[$qbankname];
} }
return $sortedqbanks; 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 { public static function get_enabled_plugins(): ?array {
global $CFG; global $CFG;
$pluginmanager = \core_plugin_manager::instance(); $pluginmanager = \core_plugin_manager::instance();
$plugins = $pluginmanager->get_installed_plugins('qbank'); $plugins = $pluginmanager->get_installed_plugins('qbank');
if (!$plugins) { if (!$plugins) {
return array(); return [];
} }
$plugins = array_keys($plugins); $plugins = array_keys($plugins);
// Filter to return only enabled plugins. // Filter to return only enabled plugins.
$enabled = array(); $enabled = [];
foreach ($plugins as $plugin) { foreach ($plugins as $plugin) {
$qbankinfo = $pluginmanager->get_plugin_info('qbank_'.$plugin); $qbankinfo = $pluginmanager->get_plugin_info('qbank_'.$plugin);
$qbankavailable = $qbankinfo->get_status(); $qbankavailable = $qbankinfo->get_status();
...@@ -95,7 +91,7 @@ class qbank extends base { ...@@ -95,7 +91,7 @@ class qbank extends base {
* @param string $fullpluginname the name of the plugin * @param string $fullpluginname the name of the plugin
* @return bool * @return bool
*/ */
public static function is_ready($fullpluginname): bool { public static function is_plugin_enabled($fullpluginname): bool {
$pluginmanager = \core_plugin_manager::instance(); $pluginmanager = \core_plugin_manager::instance();
$qbankinfo = $pluginmanager->get_plugin_info($fullpluginname); $qbankinfo = $pluginmanager->get_plugin_info($fullpluginname);
if (empty($qbankinfo)) { if (empty($qbankinfo)) {
...@@ -109,16 +105,6 @@ class qbank extends base { ...@@ -109,16 +105,6 @@ class qbank extends base {
return true; 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 { 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. global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
$ADMIN = $adminroot; // May be used in settings.php. $ADMIN = $adminroot; // May be used in settings.php.
......
...@@ -1756,19 +1756,23 @@ function question_edit_url($context) { ...@@ -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 navigation_node $navigationnode The navigation node to add the question branch to
* @param object $context * @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 * @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; global $PAGE;
if ($context->contextlevel == CONTEXT_COURSE) { if ($context->contextlevel == CONTEXT_COURSE) {
$params = array('courseid'=>$context->instanceid); $params = ['courseid' => $context->instanceid];
} else if ($context->contextlevel == CONTEXT_MODULE) { } else if ($context->contextlevel == CONTEXT_MODULE) {
$params = array('cmid'=>$context->instanceid); $params = ['cmid' => $context->instanceid];
} else { } else {
return; return;
} }
...@@ -1778,24 +1782,84 @@ function question_extend_settings_navigation(navigation_node $navigationnode, $c ...@@ -1778,24 +1782,84 @@ function question_extend_settings_navigation(navigation_node $navigationnode, $c
} }
$questionnode = $navigationnode->add(get_string('questionbank', 'question'), $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( // Community/additional plugins have navigation node.
'/question/category.php', $params), navigation_node::TYPE_SETTING, null, 'categories'); $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( $contexts = new question_edit_contexts($context);
'/question/import.php', $params), navigation_node::TYPE_SETTING, null, 'import'); 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( foreach ($pluginnavigations as $key => $pluginnavigation) {
'/question/export.php', $params), navigation_node::TYPE_SETTING, null, 'export'); 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; return $questionnode;
......
<?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';
}
}
...@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die(); ...@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
* @copyright 2009 Tim Hunt * @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @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() { public function get_name() {
return 'questionnametext'; return 'questionnametext';
} }
......
...@@ -133,14 +133,15 @@ class question_category_list_item extends list_item { ...@@ -133,14 +133,15 @@ class question_category_list_item extends list_item {
} }
public function item_html($extraargs = array()){ public function item_html($extraargs = array()){
global $CFG, $OUTPUT; global $CFG, $PAGE, $OUTPUT;
$str = $extraargs['str']; $str = $extraargs['str'];
$category = $this->item; $category = $this->item;
$editqestions = get_string('editquestions', 'question'); $editqestions = get_string('editquestions', 'question');
// Each section adds html to be displayed as part of this list item. // 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); $questionbankurl->param('cat', $category->id . ',' . $category->contextid);
$item = ''; $item = '';
$text = format_string($category->name, true, ['context' => $this->parentlist->context]); $text = format_string($category->name, true, ['context' => $this->parentlist->context]);
......
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
namespace core_question\admin; namespace core_question\admin;
defined('MOODLE_INTERNAL') || die();
/** /**
* Class manage_qbank_plugins_page. * Class manage_qbank_plugins_page.
* *
...@@ -81,14 +79,14 @@ class manage_qbank_plugins_page extends \admin_setting { ...@@ -81,14 +79,14 @@ class manage_qbank_plugins_page extends \admin_setting {
if (empty($types)) { if (empty($types)) {
return get_string('noquestionbanks', 'question'); return get_string('noquestionbanks', 'question');
} }
$txt = get_strings(array('settings', 'name', 'enable', 'disable', 'default')); $txt = get_strings(['settings', 'name', 'enable', 'disable', 'default']);
$txt->uninstall = get_string('uninstallplugin', 'core_admin'); $txt->uninstall = get_string('uninstallplugin', 'core_admin');
$table = new \html_table(); $table = new \html_table();
$table->head = array($txt->name, $txt->enable, $txt->settings, $txt->uninstall); $table->head = [$txt->name, $txt->enable, $txt->settings, $txt->uninstall];
$table->align = array('left', 'center', 'center', 'center', 'center'); $table->align = ['left', 'center', 'center', 'center', 'center'];
$table->attributes['class'] = 'manageqbanktable generaltable admintable'; $table->attributes['class'] = 'manageqbanktable generaltable admintable';
$table->data = array(); $table->data = [];
$totalenabled = 0; $totalenabled = 0;
$count = 0; $count = 0;
...@@ -99,8 +97,7 @@ class manage_qbank_plugins_page extends \admin_setting { ...@@ -99,8 +97,7 @@ class manage_qbank_plugins_page extends \admin_setting {
} }
foreach ($types as $type) { foreach ($types as $type) {
$url = new \moodle_url('/admin/qbankplugins.php', $url = new \moodle_url('/admin/qbankplugins.php', ['sesskey' => sesskey(), 'name' => $type->name]);
array('sesskey' => sesskey(), 'name' => $type->name));
$class = ''; $class = '';
if ($pluginmanager->get_plugin_info('qbank_'.$type->name)->get_status() === if ($pluginmanager->get_plugin_info('qbank_'.$type->name)->get_status() ===
...@@ -111,12 +108,12 @@ class manage_qbank_plugins_page extends \admin_setting { ...@@ -111,12 +108,12 @@ class manage_qbank_plugins_page extends \admin_setting {
} }
if ($type->is_enabled()) { if ($type->is_enabled()) {
$hideshow = \html_writer::link($url->out(false, array('action' => 'disable')), $hideshow = \html_writer::link($url->out(false, ['action' => 'disable']),
$OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', array('class' => 'iconsmall'))); $OUTPUT->pix_icon('t/hide', $txt->disable, 'moodle', ['class' => 'iconsmall']));
} else { } else {
$class = 'dimmed_text'; $class = 'dimmed_text';
$hideshow = \html_writer::link($url->out(false, array('action' => 'enable')), $hideshow = \html_writer::link($url->out(false, ['action' => 'enable']),
$OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', array('class' => 'iconsmall'))); $OUTPUT->pix_icon('t/show', $txt->enable, 'moodle', ['class' => 'iconsmall']));
} }
$settings = ''; $settings = '';
...@@ -130,7 +127,7 @@ class manage_qbank_plugins_page extends \admin_setting { ...@@ -130,7 +127,7 @@ class manage_qbank_plugins_page extends \admin_setting {
$uninstall = \html_writer::link($uninstallurl, $txt->uninstall); $uninstall = \html_writer::link($uninstallurl, $txt->uninstall);
} }
$row = new \html_table_row(array($strtypename, $hideshow, $settings, $uninstall)); $row = new \html_table_row([$strtypename, $hideshow, $settings, $uninstall]);
if ($class) { if ($class) {
$row->attributes['class'] = $class; $row->attributes['class'] = $class;
} }
......
...@@ -46,7 +46,7 @@ class question_name_column extends column_base { ...@@ -46,7 +46,7 @@ class question_name_column extends column_base {
protected function label_for($question) { protected function label_for($question) {
if (is_null($this->checkboxespresent)) { 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) { if ($this->checkboxespresent) {
return 'checkq' . $question->id; return 'checkq' . $question->id;
......
...@@ -24,12 +24,12 @@ ...@@ -24,12 +24,12 @@
*/ */
namespace core_question\bank\search; namespace core_question\bank\search;
defined('MOODLE_INTERNAL') || die();
/** /**
* This class controls from which category questions are listed. * This class controls from which category questions are listed.
* *
* @copyright 2013 Ray Morris * @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 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
class category_condition extends condition { class category_condition extends condition {
...@@ -87,16 +87,25 @@ class category_condition extends condition { ...@@ -87,16 +87,25 @@ class category_condition extends condition {
if ($this->recurse) { if ($this->recurse) {
$categoryids = question_categorylist($this->category->id); $categoryids = question_categorylist($this->category->id);
} else { } else {
$categoryids = array($this->category->id); $categoryids = [$this->category->id];
} }
list($catidtest, $this->params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cat'); list($catidtest, $this->params) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'cat');
$this->where = 'q.category ' . $catidtest; $this->where = 'q.category ' . $catidtest;
} }
/**
* SQL fragment to add to the where clause.
*
* @return string