This commit is contained in:
Mihail Geshoski
2025-12-23 16:58:28 +08:00
16 changed files with 322 additions and 44 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
define("qbank_managecategories/categorymanager",["exports","core/reactive","core/str","qbank_managecategories/mutations","qbank_managecategories/events","core/ajax","core/notification","core_form/modalform"],(function(_exports,_reactive,_str,_mutations,_events,_ajax,_notification,_modalform){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.categorymanager=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_modalform=_interopRequireDefault(_modalform);const SELECTORS_CATEGORY_LIST=".qbank_managecategories-categorylist",SELECTORS_CONTEXT=".qbank_managecategories-categorylist[data-contextid]",SELECTORS_CATEGORY_ITEM=".qbank_managecategories-item[data-categoryid]",SELECTORS_CATEGORY_ROOT="#categoryroot",SELECTORS_SHOWDESCRIPTIONS_TOGGLE="#showdescriptions-toggle",SELECTORS_ADD_EDIT_BUTTON='[data-action="addeditcategory"]',CLASSES_DRAGHANDLE="draghandle",CLASSES_DANGER="alert-danger";class CategoryManager extends _reactive.Reactive{moveCategory(categoryId,targetParentId){let precedingSiblingId=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const call={methodname:"qbank_managecategories_move_category",args:{pagecontextid:this.state.page.contextid,categoryid:categoryId,targetparentid:targetParentId,precedingsiblingid:precedingSiblingId}};_ajax.default.call([call])[0].then((stateUpdates=>(this.stateManager.processUpdates(stateUpdates),stateUpdates))).catch((error=>{var _document$getElements;_notification.default.addNotification({message:error.message,type:"error"}),null===(_document$getElements=document.getElementsByClassName(CLASSES_DANGER)[0])||void 0===_document$getElements||_document$getElements.scrollIntoView()}))}getTitle(isEdit){return(0,_str.get_string)(isEdit?"editcategory":"addcategory","question")}getSave(isEdit){return isEdit?(0,_str.get_string)("savechanges","core"):(0,_str.get_string)("addcategory","question")}showEditModal(e){const addEditButton=e.target.closest(SELECTORS_ADD_EDIT_BUTTON);if(!addEditButton)return;if(!addEditButton.dataset.actiontype)return;e.preventDefault();const title=categorymanager.getTitle("edit"===addEditButton.dataset.actiontype),save=categorymanager.getSave("edit"===addEditButton.dataset.actiontype),cmid=addEditButton.dataset.cmid,courseid=addEditButton.dataset.courseid,questioncount=addEditButton.dataset.questioncount;let contextid=addEditButton.dataset.contextid,categoryid=null,sortorder=null,parent=null;const categoryItem=e.target.closest(SELECTORS_CATEGORY_ITEM);if(categoryItem){contextid=categoryItem.dataset.contextid,categoryid=categoryItem.dataset.categoryid,sortorder=categoryItem.dataset.sortorder;const parentContext=categoryItem.closest(SELECTORS_CONTEXT);parent=categoryItem.dataset.parent+","+parentContext.dataset.contextid}const modalForm=new _modalform.default({formClass:"qbank_managecategories\\form\\question_category_edit_form",args:{cmid:cmid,courseid:courseid,questioncount:questioncount,contextid:contextid,categoryid:categoryid,sortorder:sortorder,parent:parent},modalConfig:{title:title,large:!0},saveButtonText:save,returnFocus:addEditButton});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(response=>{categorymanager.stateManager.processUpdates(response.detail)})),modalForm.show()}}const categorymanager=new CategoryManager({name:"qtype_managecategories_categorymanager",eventName:_events.eventTypes.qbankManagecategoriesStateUpdated,eventDispatch:_events.notifyQbankManagecategoriesStateUpdated,mutations:_mutations.mutations});_exports.categorymanager=categorymanager;_exports.init=()=>{(async reactive=>{const stateData={page:{contextid:document.querySelector(SELECTORS_CATEGORY_ROOT).dataset.contextid,showdescriptions:document.querySelector(SELECTORS_SHOWDESCRIPTIONS_TOGGLE).checked},categories:[],categoryLists:[]};document.querySelectorAll(SELECTORS_CATEGORY_ITEM).forEach((item=>{stateData.categories.push({id:item.dataset.categoryid,name:item.dataset.categoryname,parent:item.dataset.parent,contextid:item.dataset.contextid,sortorder:item.dataset.sortorder,draghandle:item.classList.contains(CLASSES_DRAGHANDLE)})})),document.querySelectorAll(SELECTORS_CATEGORY_LIST).forEach((categoryList=>{stateData.categoryLists.push({id:categoryList.dataset.categoryid,childCount:categoryList.querySelectorAll(SELECTORS_CATEGORY_ITEM).length})})),reactive.setInitialState(stateData)})(categorymanager)}}));
define("qbank_managecategories/categorymanager",["exports","core/reactive","core/str","qbank_managecategories/mutations","qbank_managecategories/events","core/ajax","core/notification","core_form/modalform"],(function(_exports,_reactive,_str,_mutations,_events,_ajax,_notification,_modalform){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.categorymanager=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_modalform=_interopRequireDefault(_modalform);const SELECTORS_CATEGORY_LIST=".qbank_managecategories-categorylist",SELECTORS_CONTEXT=".qbank_managecategories-categorylist[data-contextid]",SELECTORS_CATEGORY_ITEM=".qbank_managecategories-item[data-categoryid]",SELECTORS_CATEGORY_ROOT="#categoryroot",SELECTORS_SHOWDESCRIPTIONS_TOGGLE="#showdescriptions-toggle",SELECTORS_ADD_EDIT_BUTTON='[data-action="addeditcategory"]',CLASSES_DRAGHANDLE="draghandle",CLASSES_DANGER="alert-danger";class CategoryManager extends _reactive.Reactive{moveCategory(categoryId,targetParentId){let precedingSiblingId=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const call={methodname:"qbank_managecategories_move_category",args:{pagecontextid:this.state.page.contextid,categoryid:categoryId,targetparentid:targetParentId,precedingsiblingid:precedingSiblingId}};_ajax.default.call([call])[0].then((stateUpdates=>(this.stateManager.processUpdates(stateUpdates),stateUpdates))).catch((error=>{var _document$getElements;_notification.default.addNotification({message:error.message,type:"error"}),null===(_document$getElements=document.getElementsByClassName(CLASSES_DANGER)[0])||void 0===_document$getElements||_document$getElements.scrollIntoView()}))}getTitle(isEdit){return(0,_str.get_string)(isEdit?"editcategory":"addcategory","question")}getSave(isEdit){return isEdit?(0,_str.get_string)("savechanges","core"):(0,_str.get_string)("addcategory","question")}showEditModal(e){const addEditButton=e.target.closest(SELECTORS_ADD_EDIT_BUTTON);if(!addEditButton)return;if(!addEditButton.dataset.actiontype)return;e.preventDefault();const title=categorymanager.getTitle("edit"===addEditButton.dataset.actiontype),save=categorymanager.getSave("edit"===addEditButton.dataset.actiontype),cmid=addEditButton.dataset.cmid,courseid=addEditButton.dataset.courseid,questioncount=addEditButton.dataset.questioncount;let contextid=addEditButton.dataset.contextid,categoryid=null,sortorder=null,parent=null;const categoryItem=e.target.closest(SELECTORS_CATEGORY_ITEM);if(categoryItem){contextid=categoryItem.dataset.contextid,categoryid=categoryItem.dataset.categoryid,sortorder=categoryItem.dataset.sortorder;const parentContext=categoryItem.closest(SELECTORS_CONTEXT);parent=categoryItem.dataset.parent+","+parentContext.dataset.contextid}const modalForm=new _modalform.default({formClass:"qbank_managecategories\\form\\question_category_edit_form",args:{cmid:cmid,courseid:courseid,questioncount:questioncount,contextid:contextid,categoryid:categoryid,sortorder:sortorder,parent:parent},modalConfig:{title:title,large:!0},saveButtonText:save,returnFocus:addEditButton});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(response=>{categorymanager.stateManager.processUpdates(response.detail)})),modalForm.show()}updateCategoryName(categoryId,newName){categorymanager.stateManager.setReadOnly(!1),categorymanager.stateManager.processUpdate("categories","put",{id:categoryId,name:newName}),categorymanager.stateManager.setReadOnly(!0)}}const categorymanager=new CategoryManager({name:"qtype_managecategories_categorymanager",eventName:_events.eventTypes.qbankManagecategoriesStateUpdated,eventDispatch:_events.notifyQbankManagecategoriesStateUpdated,mutations:_mutations.mutations});_exports.categorymanager=categorymanager;_exports.init=()=>{(async reactive=>{const stateData={page:{contextid:document.querySelector(SELECTORS_CATEGORY_ROOT).dataset.contextid,showdescriptions:document.querySelector(SELECTORS_SHOWDESCRIPTIONS_TOGGLE).checked},categories:[],categoryLists:[]};document.querySelectorAll(SELECTORS_CATEGORY_ITEM).forEach((item=>{stateData.categories.push({id:item.dataset.categoryid,name:item.dataset.categoryname,parent:item.dataset.parent,contextid:item.dataset.contextid,sortorder:item.dataset.sortorder,draghandle:item.classList.contains(CLASSES_DRAGHANDLE)})})),document.querySelectorAll(SELECTORS_CATEGORY_LIST).forEach((categoryList=>{stateData.categoryLists.push({id:categoryList.dataset.categoryid,childCount:categoryList.querySelectorAll(SELECTORS_CATEGORY_ITEM).length})})),reactive.setInitialState(stateData)})(categorymanager)}}));
//# sourceMappingURL=categorymanager.min.js.map
File diff suppressed because one or more lines are too long
@@ -25,6 +25,7 @@ import {categorymanager} from 'qbank_managecategories/categorymanager';
import Templates from 'core/templates';
import Modal from "core/modal";
import {get_string as getString} from "core/str";
import {eventTypes as inplaceEditableEventTypes} from 'core/local/inplace_editable/events';
export default class extends BaseComponent {
@@ -35,6 +36,7 @@ export default class extends BaseComponent {
CATEGORY_ITEM: '.qbank_managecategories-item[data-categoryid]',
CATEGORY_CONTENTS: '.qbank_managecategories-item > .container',
EDIT_BUTTON: '[data-action="addeditcategory"]',
EDITABLE_CATEGORY_NAME: '.inplaceeditable[data-itemtype="categoryname"]',
MOVE_BUTTON: '[role="menuitem"][data-actiontype="move"]',
CONTEXT: '.qbank_managecategories-categorylist[data-contextid]',
MODAL_CATEGORY_ITEM: '.modal_category_item[data-movingcategoryid]',
@@ -59,6 +61,10 @@ export default class extends BaseComponent {
this.addEventListener(this.getElement(this.selectors.EDIT_BUTTON), 'click', categorymanager.showEditModal);
const moveButton = this.getElement(this.selectors.MOVE_BUTTON);
this.addEventListener(moveButton, 'click', this.showMoveModal);
this.addEventListener(this.getElement(), inplaceEditableEventTypes.elementUpdated, (e) => {
const editable = e.target.closest(this.selectors.EDITABLE_CATEGORY_NAME);
categorymanager.updateCategoryName(editable.dataset.itemid, editable.dataset.value);
});
}
destroy() {
@@ -192,6 +198,8 @@ export default class extends BaseComponent {
// When the template context is added or updated, re-render the content.
{watch: `categories[${this.element.dataset.categoryid}].templatecontext:created`, handler: this.rerender},
{watch: `categories[${this.element.dataset.categoryid}].templatecontext:updated`, handler: this.rerender},
// When the name is updated, update it in the element data set.
{watch: `categories[${this.element.dataset.categoryid}].name:updated`, handler: this.updateName},
// When a new category is created, check whether we need to add a child list to this category.
{watch: `categories:created`, handler: this.checkChildList},
];
@@ -459,4 +467,14 @@ export default class extends BaseComponent {
]
});
}
/**
* Update the name in the category item element's data, for building move targets.
*
* @param {Object} args
* @param {Object} args.element
*/
updateName({element}) {
this.getElement().dataset.categoryname = element.name;
}
}
@@ -209,6 +209,12 @@ class CategoryManager extends Reactive {
// Show the form.
modalForm.show();
}
updateCategoryName(categoryId, newName) {
categorymanager.stateManager.setReadOnly(false);
categorymanager.stateManager.processUpdate('categories', 'put', {id: categoryId, name: newName});
categorymanager.stateManager.setReadOnly(true);
}
}
export const categorymanager = new CategoryManager({
@@ -91,6 +91,16 @@ class category implements renderable, templatable {
$questionbankurl = new moodle_url('/question/edit.php', $params);
$questionbankurl->param('cat', helper::combine_id_context($this->category));
$categoryname = format_string($this->category->name, true, ['context' => $this->context, 'escape' => false]);
$categorylink = new category_link(
$categoryname,
$questionbankurl,
$this->category->questioncount,
);
$editablename = new editable_name(
$this->category,
$output->render($categorylink),
$canmanagecategory,
);
$idnumber = null;
if ($this->category->idnumber !== null && $this->category->idnumber !== '') {
$idnumber = $this->category->idnumber;
@@ -212,10 +222,10 @@ class category implements renderable, templatable {
$itemdata = [
'categoryid' => $this->category->id,
'contextid' => $this->category->contextid,
'questionbankurl' => $questionbankurl->out(false),
'categoryname' => $categoryname,
'categorylink' => $categorylink->export_for_template($output),
'editablename' => $editablename->export_for_template($output),
'idnumber' => $idnumber,
'questioncount' => $this->category->questioncount,
'categorydesc' => $categorydesc,
'editactionmenu' => $menu->export_for_template($output),
'draghandle' => $canmanagecategory && $this->canreorder,
@@ -227,7 +237,4 @@ class category implements renderable, templatable {
];
return $itemdata;
}
}
@@ -0,0 +1,58 @@
<?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/>.
namespace qbank_managecategories\output;
use core\output\renderable;
use core\output\renderer_base;
use core\output\templatable;
use core\url;
/**
* Category name and question count link.
*
* @package qbank_managecategories
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category_link implements renderable, templatable {
/**
* Constructor.
*
* @param string $name The question bank name.
* @param url $questionbankurl The URL for the question bank link.
* @param int $questioncount The number of questions in the bank.
*/
public function __construct(
/** @var string $name The question bank name. */
protected string $name,
/** @var url $questionbankurl The URL for the question bank link. */
protected url $questionbankurl,
/** @var int $questioncount The number of questions in the bank. */
protected int $questioncount
) {
}
#[\Override]
public function export_for_template(renderer_base $output): array {
return [
'categoryname' => $this->name,
'questionbankurl' => $this->questionbankurl->out(),
'questioncount' => $this->questioncount,
];
}
}
@@ -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/>.
namespace qbank_managecategories\output;
use core\context;
use core\output\inplace_editable;
use core\output\named_templatable;
use core\output\renderable;
use core\url;
use core_external\external_api;
use core_question\category_manager;
use core_question\output\question_category_selector;
use qbank_managecategories\helper;
/**
* Category name inplace editable
*
* @package qbank_managecategories
* @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class editable_name extends inplace_editable implements named_templatable, renderable {
/**
* Constructor.
*
* @param \stdClass $category The category record we are editing.
* @param string $categorylink The link to the question category.
* @param bool $editable Whether the user has permission to edit this category.
*/
public function __construct(\stdClass $category, string $categorylink, bool $editable) {
parent::__construct(
'qbank_managecategories',
'categoryname',
$category->id,
$editable,
$categorylink,
$category->name,
get_string('editcategorynamehint', 'qbank_managecategories'),
get_string('editcategoryname', 'qbank_managecategories', $category->name),
);
}
#[\Override]
public function get_template_name(\renderer_base $renderer): string {
return 'core/inplace_editable';
}
/**
* Save the new name and return the updated output component.
*
* @param int $categoryid The ID of the category to update
* @param string $newname The new name to save.
* @return self
*/
public static function callback(int $categoryid, string $newname): self {
global $DB, $OUTPUT;
$context = context::instance_by_id($DB->get_field('question_categories', 'contextid', ['id' => $categoryid]));
external_api::validate_context($context);
$manager = new category_manager();
$manager->update_category($categoryid, '', $newname);
$updatedcategory = $DB->get_record('question_categories', ['id' => $categoryid]);
$questionbankurl = new url(
'/question/edit.php',
[
'cmid' => $context->instanceid,
'cat' => helper::combine_id_context($updatedcategory),
],
);
$categoryname = format_string($updatedcategory->name, true, ['context' => $context, 'escape' => false]);
$questioncountsql = question_category_selector::question_count_sql(categoryparam: '?');
$questioncount = $DB->get_field_sql($questioncountsql, [$categoryid]);
$categorylink = new category_link(
$categoryname,
$questionbankurl,
$questioncount,
);
// Prepare the element for the output.
// The $editable argument is always true because `update_category()` throws an exception otherwise.
return new self($updatedcategory, $OUTPUT->render($categorylink), true);
}
}
@@ -30,6 +30,8 @@ $string['categorymoved'] = 'The category has been moved successfully.';
$string['confirmdelete'] = 'Delete category "{$a}"?. Any child categories will be moved to this category\'s parent.';
$string['descriptionnotdisplayed'] = 'Categories description not displayed';
$string['displaydescription'] = 'Categories description displayed';
$string['editcategoryname'] = 'New name for {$a}';
$string['editcategorynamehint'] = 'Edit category name';
$string['error:category'] = 'You must select a valid category';
$string['idnumberexists'] = 'ID number already in use. Change it to move or update category.';
$string['lastcategoryinthiscontext'] = 'This category is the only one in this context. You need to add another category if this category is to be moved.';
@@ -44,3 +44,17 @@ function qbank_managecategories_user_preferences(): array {
],
];
}
/**
* In place editing callback for categories.
*
* @param string $itemtype type of the item.
* @param int $itemid id of the category being edited.
* @param string $newvalue the new value for the edited field.
* @return \core\output\inplace_editable
*/
function qbank_managecategories_inplace_editable(string $itemtype, int $itemid, string $newvalue): \core\output\inplace_editable {
if ($itemtype === 'categoryname') {
return \qbank_managecategories\output\editable_name::callback($itemid, $newvalue);
}
}
@@ -33,28 +33,34 @@
Example context (json):
{
"categoryid": "1",
"questionbankurl": "question/edit.php?cmid=123",
"categoryname": "Default for Miscellaneous & < > \" '",
"categoryname": {
"questionbankurl": "question/edit.php?cmid=123",
"categoryname": "Default for Miscellaneous & < > \" '",
"questioncount": "1"
},
"idnumber": "1",
"questioncount": " 1",
"categorydesc": "The default category for questions shared in context Miscellaneous",
"children": "",
"newchildtooltip": "New child of Default for Miscellaneous"
}
}}
<strong>
<a title="{{#str}}editquestions, question{{/str}}" href="{{{questionbankurl}}}" role="button">
{{categoryname}}
{{#idnumber}}
<span class="badge text-bg-primary">
<span class="accesshide">
{{#str}}idnumber, question{{/str}}
</span>
{{idnumber}}
</span>
{{/idnumber}}
({{questioncount}})
</a>
{{#editablename}}
{{> core/inplace_editable }}
{{/editablename}}
{{^editablename}}
{{#categoryname}}
{{> qbank_managecategories/category_link }}
{{/categoryname}}
{{/editablename}}
{{#idnumber}}
<span class="badge text-bg-primary">
<span class="accesshide">
{{#str}}idnumber, question{{/str}}
</span>
{{idnumber}}
</span>
{{/idnumber}}
</strong>
{{^children}}
{{> qbank_managecategories/newchild }}
@@ -0,0 +1,39 @@
{{!
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 qbank_managecategories/category_name
Partial template for rendering the category name and question count as a link
Comments around whitespace keep the output to one line so it works with inplace_editable::get_linkeverything().
Context variables required for this template:
* questionbankurl - Url for corresponding question bank.
* categoryname - Category name. Raw string. Will be HTML-escaped during rendering.
* questioncount - Number of question in question bank.
Example context (json):
{
"questionbankurl": "question/edit.php?cmid=123",
"categoryname": "Default for Miscellaneous & < > \" '",
"questioncount": "1"
}
}}
<a title="{{#str}}editquestions, question{{/str}}" href="{{{questionbankurl}}}" role="button">{{!
}}{{categoryname}} ({{questioncount}}){{!
}}</a>
@@ -153,3 +153,10 @@ Feature: A teacher can put questions in categories in the question bank
And I apply question bank filter "Category" with value "Used category"
Then the "Category" field validity check should return "true"
And I should see "Test question to be moved"
Scenario: A category name can be edited in place
Given I am on the "Qbank 1" "core_question > question categories" page
When I set the field "Edit category name" in the "Subcategory & < > \" ' &" "list_item" to "Subcategory edited & < > \" ' &"
Then I should not see "Subcategory & < > \" ' &"
And I should see "Subcategory edited & < > \" ' &"
And ".qbank_managecategories-item[data-categoryname^='Subcategory edited']" "css_element" should exist
+8 -2
View File
@@ -263,12 +263,14 @@ class category_manager {
int $updateid,
string $newparent,
string $newname,
string $newinfo,
?string $newinfo = null,
string $newinfoformat = FORMAT_HTML,
?string $idnumber = null,
?int $sortorder = null,
): void {
global $DB;
global $DB, $CFG;
require_once($CFG->libdir . '/questionlib.php');
if (empty($newname)) {
throw new moodle_exception('categorynamecantbeblank', 'question');
}
@@ -284,6 +286,10 @@ class category_manager {
$tocontextid = $oldcat->contextid;
}
if (is_null($newinfo)) {
$newinfo = $oldcat->info;
}
// Check permissions.
$fromcontext = context::instance_by_id($oldcat->contextid);
$this->require_manage_category($fromcontext);
@@ -92,25 +92,9 @@ class question_category_selector implements renderable, templatable {
[$insql, $inparams] = $DB->get_in_or_equal($validcontexts);
$topwhere = $top ? '' : 'AND c.parent <> 0';
$statuscondition = "AND (qv.status = '" . question_version_status::QUESTION_STATUS_READY . "' " .
" OR qv.status = '" . question_version_status::QUESTION_STATUS_DRAFT . "' )";
$substatuscondition = "AND v.status <> '" . question_version_status::QUESTION_STATUS_HIDDEN . "' ";
$countsql = $this->question_count_sql($showallversions);
$sql = "SELECT c.*,
(SELECT COUNT(1)
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
WHERE q.parent = '0'
$statuscondition
AND c.id = qbe.questioncategoryid
AND ({$showallversions} = 1
OR (qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
WHERE be.id = qbe.id $substatuscondition)
)
)
) AS questioncount
({$countsql}) AS questioncount
FROM {question_categories} c
WHERE c.contextid {$insql} {$topwhere}
ORDER BY {$sortorder}";
@@ -118,6 +102,35 @@ class question_category_selector implements renderable, templatable {
return $DB->get_records_sql($sql, $inparams);
}
/**
* Return the SQL query for getting a count of questions in a category.
*
* @param int $showallversions 1 to show all versions not only the latest.
* @param string $categoryparam Category ID parameter or field.
* @return string The SQL.
*/
public static function question_count_sql(int $showallversions = 0, string $categoryparam = 'c.id'): string {
$statuscondition = "AND (qv.status = '" . question_version_status::QUESTION_STATUS_READY . "' " .
" OR qv.status = '" . question_version_status::QUESTION_STATUS_DRAFT . "' )";
$substatuscondition = "AND v.status <> '" . question_version_status::QUESTION_STATUS_HIDDEN . "' ";
return "
SELECT COUNT(1)
FROM {question} q
JOIN {question_versions} qv ON qv.questionid = q.id
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
WHERE q.parent = '0'
$statuscondition
AND ({$showallversions} = 1
OR (qv.version = (SELECT MAX(v.version)
FROM {question_versions} v
JOIN {question_bank_entries} be ON be.id = v.questionbankentryid
WHERE be.id = qbe.id $substatuscondition)
)
)
AND qbe.questioncategoryid = {$categoryparam}
";
}
/**
* Output an array of question categories.
*