Merge branch 'MDL-85649_main' of https://github.com/marxjohnson/moodle
This commit is contained in:
@@ -233,6 +233,7 @@ $string['matchgrades_help'] = 'Imported grades must match one of the fixed list
|
||||
|
||||
* Error if grade not listed - If a question contains any grades not found in the list an error is displayed and that question will not be imported
|
||||
* Nearest grade if not listed - If a grade is found that does not match a value in the list, the grade is changed to the closest matching value in the list ';
|
||||
$string['missingcapability'] = 'You must have a capability allowing usage or management of questions, for example {$a}';
|
||||
$string['missingcourseorcmid'] = 'Need to provide courseid or cmid to print_question.';
|
||||
$string['missingcourseorcmidtolink'] = 'Need to provide courseid or cmid to get_question_edit_link.';
|
||||
$string['missingimportantcode'] = 'This question type is missing important code: {$a}.';
|
||||
@@ -455,6 +456,7 @@ $string['questionbehavioursdisabled'] = 'Question behaviours to disable';
|
||||
$string['questionbehavioursdisabledexplained'] = 'Enter a comma-separated list of behaviours you do not want to appear in the drop-down menu.';
|
||||
$string['questionbehavioursorder'] = 'Question behaviours order';
|
||||
$string['questionbehavioursorderexplained'] = 'Enter a comma-separated list of behaviours in the order you want them to appear in the drop-down menu.';
|
||||
$string['questioncount'] = 'Questions: {$a}';
|
||||
$string['questiondefaultssave'] = 'Save question options as user preference defaults';
|
||||
$string['questiondefaultssave_desc'] = 'Whether question options such as \'Default mark\', \'One or multiple answers?\' etc. should be saved as user preferences when the edit question form is submitted, for use as defaults the next time the user accesses the form.';
|
||||
$string['questionediting'] = 'Question editing';
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
define("core_question/questioncount",["exports","core/str","core/fetch","core/notification","core/loadingicon"],(function(_exports,_str,_fetch,_notification,_loadingicon){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Question count badge with asynchronous loading.
|
||||
*
|
||||
* @module core_question/questioncount
|
||||
* @copyright 2024 Catalyst IT Europe Ltd.
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.fetchCounts=void 0,_fetch=_interopRequireDefault(_fetch),_notification=_interopRequireDefault(_notification),_loadingicon=_interopRequireDefault(_loadingicon);const SELECTORS_COUNT_CONTAINER=".questioncount",SELECTORS_COUNT_BADGE=cmid=>".questioncount[data-cmid='".concat(cmid,"'] .badge");_exports.fetchCounts=async courseId=>{const countContainer=document.querySelector(SELECTORS_COUNT_CONTAINER),endpoint=["bank",courseId,"question_counts"],loadingPromise=_loadingicon.default.addIconToContainerWithPromise(countContainer);try{const response=await _fetch.default.performGet("core_question",endpoint.join("/")),questionCounts=await response.json();for(const[cmid,count]of Object.entries(questionCounts.counts)){const countBadge=document.querySelector(SELECTORS_COUNT_BADGE(cmid));countBadge&&(countBadge.innerText=await(0,_str.getString)("questioncount","question",count),countBadge.classList.remove("d-none"))}}catch(ex){"string"==typeof ex?_notification.default.alert((0,_str.getString)("error","error"),ex):_notification.default.exception(ex)}finally{loadingPromise.resolve()}}}));
|
||||
|
||||
//# sourceMappingURL=questioncount.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"questioncount.min.js","sources":["../src/questioncount.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Question count badge with asynchronous loading.\n *\n * @module core_question/questioncount\n * @copyright 2024 Catalyst IT Europe Ltd.\n * @author Mark Johnson <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getString} from 'core/str';\nimport Fetch from 'core/fetch';\nimport Notification from 'core/notification';\nimport LoadingIcon from 'core/loadingicon';\n\nconst SELECTORS = {\n COUNT_CONTAINER: '.questioncount',\n COUNT_BADGE: (cmid) => `.questioncount[data-cmid='${cmid}'] .badge`,\n};\n\nexport const fetchCounts = async(courseId) => {\n const countContainer = document.querySelector(SELECTORS.COUNT_CONTAINER);\n const endpoint = ['bank', courseId, 'question_counts'];\n const loadingPromise = LoadingIcon.addIconToContainerWithPromise(countContainer);\n try {\n const response = await Fetch.performGet('core_question', endpoint.join('/'));\n const questionCounts = await response.json();\n for (const [cmid, count] of Object.entries(questionCounts.counts)) {\n const countBadge = document.querySelector(SELECTORS.COUNT_BADGE(cmid));\n if (countBadge) {\n countBadge.innerText = await getString('questioncount', 'question', count);\n countBadge.classList.remove('d-none');\n }\n }\n } catch (ex) {\n if (typeof ex === 'string') {\n Notification.alert(getString('error', 'error'), ex);\n } else {\n Notification.exception(ex);\n }\n } finally {\n loadingPromise.resolve();\n }\n};\n"],"names":["SELECTORS","cmid","async","countContainer","document","querySelector","endpoint","courseId","loadingPromise","LoadingIcon","addIconToContainerWithPromise","response","Fetch","performGet","join","questionCounts","json","count","Object","entries","counts","countBadge","innerText","classList","remove","ex","alert","exception","resolve"],"mappings":";;;;;;;;2OA6BMA,0BACe,iBADfA,sBAEYC,0CAAsCA,uCAG7BC,MAAAA,iBACjBC,eAAiBC,SAASC,cAAcL,2BACxCM,SAAW,CAAC,OAAQC,SAAU,mBAC9BC,eAAiBC,qBAAYC,8BAA8BP,0BAEvDQ,eAAiBC,eAAMC,WAAW,gBAAiBP,SAASQ,KAAK,MACjEC,qBAAuBJ,SAASK,WACjC,MAAOf,KAAMgB,SAAUC,OAAOC,QAAQJ,eAAeK,QAAS,OACzDC,WAAajB,SAASC,cAAcL,sBAAsBC,OAC5DoB,aACAA,WAAWC,gBAAkB,kBAAU,gBAAiB,WAAYL,OACpEI,WAAWE,UAAUC,OAAO,YAGtC,MAAOC,IACa,iBAAPA,yBACMC,OAAM,kBAAU,QAAS,SAAUD,0BAEnCE,UAAUF,YAG3BjB,eAAeoB"}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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 count badge with asynchronous loading.
|
||||
*
|
||||
* @module core_question/questioncount
|
||||
* @copyright 2024 Catalyst IT Europe Ltd.
|
||||
* @author Mark Johnson <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {getString} from 'core/str';
|
||||
import Fetch from 'core/fetch';
|
||||
import Notification from 'core/notification';
|
||||
import LoadingIcon from 'core/loadingicon';
|
||||
|
||||
const SELECTORS = {
|
||||
COUNT_CONTAINER: '.questioncount',
|
||||
COUNT_BADGE: (cmid) => `.questioncount[data-cmid='${cmid}'] .badge`,
|
||||
};
|
||||
|
||||
export const fetchCounts = async(courseId) => {
|
||||
const countContainer = document.querySelector(SELECTORS.COUNT_CONTAINER);
|
||||
const endpoint = ['bank', courseId, 'question_counts'];
|
||||
const loadingPromise = LoadingIcon.addIconToContainerWithPromise(countContainer);
|
||||
try {
|
||||
const response = await Fetch.performGet('core_question', endpoint.join('/'));
|
||||
const questionCounts = await response.json();
|
||||
for (const [cmid, count] of Object.entries(questionCounts.counts)) {
|
||||
const countBadge = document.querySelector(SELECTORS.COUNT_BADGE(cmid));
|
||||
if (countBadge) {
|
||||
countBadge.innerText = await getString('questioncount', 'question', count);
|
||||
countBadge.classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
if (typeof ex === 'string') {
|
||||
Notification.alert(getString('error', 'error'), ex);
|
||||
} else {
|
||||
Notification.exception(ex);
|
||||
}
|
||||
} finally {
|
||||
loadingPromise.resolve();
|
||||
}
|
||||
};
|
||||
@@ -17,6 +17,7 @@
|
||||
namespace core_question\output;
|
||||
|
||||
use action_link;
|
||||
use core\output\pix_icon;
|
||||
use renderer_base;
|
||||
use core_courseformat\output\local\content\cm\controlmenu;
|
||||
use core_question\local\bank\question_bank_helper;
|
||||
@@ -30,7 +31,6 @@ use core_question\local\bank\question_bank_helper;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class question_bank_list implements \renderable, \templatable {
|
||||
|
||||
/**
|
||||
* Instantiate the output class.
|
||||
*
|
||||
@@ -49,7 +49,6 @@ class question_bank_list implements \renderable, \templatable {
|
||||
* @return array
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): array {
|
||||
|
||||
$banks = [];
|
||||
foreach ($this->bankinstances as $instance) {
|
||||
if (plugin_supports('mod', $instance->cminfo->modname, FEATURE_PUBLISHES_QUESTIONS)) {
|
||||
@@ -82,6 +81,7 @@ class question_bank_list implements \renderable, \templatable {
|
||||
'description' => $instance->cminfo->get_formatted_content(),
|
||||
'managequestions' => $managequestions->export_for_template($output),
|
||||
'managebank' => $managebankexport,
|
||||
'cmid' => $instance->modid,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ class view_banks implements \templatable, \renderable {
|
||||
'privatebanks' => $privatebankscontext,
|
||||
'addcustombanks' => $addcustombanksrenderable->export_for_template($output),
|
||||
'createdefault' => $cancreatedefault ? $createdefaultrenderable->export_for_template($output) : false,
|
||||
'courseid' => $this->course->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<?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 core_question\route\api;
|
||||
|
||||
use core\context\course;
|
||||
use core\context\module;
|
||||
use core\exception\required_capability_exception;
|
||||
use core\param;
|
||||
use core\router\require_login;
|
||||
use core\router\route;
|
||||
use core\router\schema\parameters\path_parameter;
|
||||
use core\router\schema\response\payload_response;
|
||||
use core_question\local\bank\question_edit_contexts;
|
||||
use core_question\local\bank\question_version_status;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Web service functions related to question banks
|
||||
*
|
||||
* @package core_question
|
||||
* @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 bank {
|
||||
/**
|
||||
* Return the total number of questions in each question bank for the given course.
|
||||
*
|
||||
* This will count all top-level questions (no subquestions) that are not hidden.
|
||||
*
|
||||
* @param int $courseid
|
||||
*/
|
||||
#[route(
|
||||
path: '/bank/{courseid}/question_counts',
|
||||
method: ['GET'],
|
||||
pathtypes: [
|
||||
new path_parameter(
|
||||
name: 'courseid',
|
||||
type: param::INT,
|
||||
description: 'The course ID the fetch question counts for',
|
||||
required: true,
|
||||
),
|
||||
],
|
||||
requirelogin: new require_login(false, true, 'courseid'),
|
||||
)]
|
||||
public function question_counts(
|
||||
ServerRequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
int $courseid,
|
||||
): payload_response {
|
||||
global $DB;
|
||||
$coursecontext = course::instance($courseid);
|
||||
$capabilities = array_merge(question_edit_contexts::$caps['editq'], question_edit_contexts::$caps['categories']);
|
||||
|
||||
if (!has_any_capability($capabilities, $coursecontext)) {
|
||||
throw new required_capability_exception(
|
||||
$coursecontext,
|
||||
reset($capabilities),
|
||||
'missingcapability',
|
||||
'question',
|
||||
);
|
||||
}
|
||||
|
||||
$contextpathlike = $DB->sql_like('c.path', ':contextpath');
|
||||
|
||||
// Get a count of all questions in each module context within this course, keyed by cmid.
|
||||
// Only include modules that have question category records, so we don't get a count for modules that don't use questions.
|
||||
// Return a count of 0 for those modules with no questions.
|
||||
// The double LEFT JOIN of question_versions ensures we only get the latest version for a question bank entry.
|
||||
$sql = "
|
||||
SELECT c.instanceid,
|
||||
COUNT(
|
||||
CASE
|
||||
WHEN q.id IS NOT NULL THEN 1
|
||||
END
|
||||
) AS count
|
||||
FROM {context} c
|
||||
JOIN {question_categories} qc ON qc.contextid = c.id
|
||||
LEFT JOIN {question_bank_entries} qbe ON qbe.questioncategoryid = qc.id
|
||||
LEFT JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
|
||||
LEFT JOIN {question_versions} qv1 ON qv1.questionbankentryid = qbe.id AND qv.version < qv1.version
|
||||
LEFT JOIN {question} q ON q.id = qv.questionid
|
||||
WHERE c.contextlevel = :module
|
||||
AND {$contextpathlike}
|
||||
AND (q.parent = '0' OR q.id IS NULL)
|
||||
AND (qv1.questionbankentryid IS NULL OR q.id IS NULL)
|
||||
GROUP BY c.instanceid
|
||||
";
|
||||
$params = [
|
||||
'hidden' => question_version_status::QUESTION_STATUS_HIDDEN,
|
||||
'module' => module::LEVEL,
|
||||
'contextpath' => "{$coursecontext->path}/%",
|
||||
];
|
||||
$counts = $DB->get_records_sql_menu($sql, $params);
|
||||
return new payload_response(
|
||||
payload: [
|
||||
'counts' => $counts,
|
||||
],
|
||||
request: $request,
|
||||
response: $response,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -213,6 +213,9 @@
|
||||
{{#managequestions}}
|
||||
{{> core/action_link}}
|
||||
{{/managequestions}}
|
||||
<div class="d-inline-block questioncount" data-cmid="{{cmid}}">
|
||||
<span class="badge rounded-pill bg-primary d-none"></span>
|
||||
</div>
|
||||
<div class="manage-bank-actions d-inline-block">
|
||||
{{#managebank}}
|
||||
{{> core/action_menu}}
|
||||
|
||||
@@ -341,3 +341,9 @@
|
||||
{{/privatebanks}}
|
||||
</div>
|
||||
{{/hasprivatebanks}}
|
||||
|
||||
{{#js}}
|
||||
require(['core_question/questioncount'], function(questionCount) {
|
||||
questionCount.fetchCounts('{{courseid}}');
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -56,3 +56,25 @@ Feature: Manage question banks
|
||||
And I click on "Delete" "button"
|
||||
Then I should not see "bank1"
|
||||
But I should see "bank2"
|
||||
|
||||
@javascript
|
||||
Scenario: Question banks display the number of questions they contain
|
||||
Given the following "activities" exist:
|
||||
| activity | name | course | section | intro | showdescription |
|
||||
| qbank | bank3 | C1 | 0 | Bank 3 intro | 0 |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Activity module | bank1 | Test questions 1 |
|
||||
| Activity module | bank2 | Test questions 2 |
|
||||
| Activity module | bank3 | Test questions 3 |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name |
|
||||
| Test questions 1 | truefalse | TF1 |
|
||||
| Test questions 1 | truefalse | TF2 |
|
||||
| Test questions 1 | truefalse | TF3 |
|
||||
| Test questions 2 | truefalse | TF4 |
|
||||
Given I am on the "C1" "Course" page logged in as "teacher1"
|
||||
When I navigate to "Question banks" in current page administration
|
||||
Then I should see "Questions: 3" in the "bank1" "list_item"
|
||||
And I should see "Questions: 1" in the "bank2" "list_item"
|
||||
And I should see "Questions: 0" in the "bank3" "list_item"
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<?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 core_question\route\api;
|
||||
|
||||
use core\tests\router\route_testcase;
|
||||
use core\context\module;
|
||||
|
||||
/**
|
||||
* Unit tests for \core_question\route\api\bank
|
||||
*
|
||||
* @package core_question
|
||||
* @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
|
||||
* @covers \core_question\route\api\bank
|
||||
*/
|
||||
final class bank_test extends route_testcase {
|
||||
/**
|
||||
* An empty bank should return a count of 0.
|
||||
*/
|
||||
public function test_question_count_empty(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->add_class_routes_to_route_loader(
|
||||
bank::class,
|
||||
'/api/rest/v2/question'
|
||||
);
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$qbank = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
|
||||
$response = $this->process_api_request('GET', "/question/bank/{$course->id}/question_counts");
|
||||
$this->assert_valid_response($response);
|
||||
$payload = $this->decode_response($response, true);
|
||||
|
||||
$this->assertEquals(['counts' => [$qbank->cmid => 0]], $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* A bank should return the correct number of questions.
|
||||
*/
|
||||
public function test_question_count_questions(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->add_class_routes_to_route_loader(
|
||||
bank::class,
|
||||
'/api/rest/v2/question'
|
||||
);
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$qbank = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
$bankcontext = module::instance($qbank->cmid);
|
||||
$category = question_get_default_category($bankcontext->id, true);
|
||||
$questiongenerator = self::getDataGenerator()->get_plugin_generator('core_question');
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category->id]);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category->id]);
|
||||
|
||||
$response = $this->process_api_request('GET', "/question/bank/{$course->id}/question_counts");
|
||||
$this->assert_valid_response($response);
|
||||
$payload = $this->decode_response($response, true);
|
||||
|
||||
$this->assertEquals(['counts' => [$qbank->cmid => 2]], $payload);
|
||||
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category->id]);
|
||||
|
||||
$response = $this->process_api_request('GET', "/question/bank/{$course->id}/question_counts");
|
||||
$this->assert_valid_response($response);
|
||||
$payload = $this->decode_response($response, true);
|
||||
|
||||
$this->assertEquals(['counts' => [$qbank->cmid => 3]], $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* A question with multiple versions should only be counted once.
|
||||
*/
|
||||
public function test_question_count_versions(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->add_class_routes_to_route_loader(
|
||||
bank::class,
|
||||
'/api/rest/v2/question'
|
||||
);
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$qbank = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
$bankcontext = module::instance($qbank->cmid);
|
||||
$category = question_get_default_category($bankcontext->id, true);
|
||||
$questiongenerator = self::getDataGenerator()->get_plugin_generator('core_question');
|
||||
$q1 = $questiongenerator->create_question('truefalse', overrides: ['category' => $category->id]);
|
||||
$questiongenerator->update_question($q1, overrides: ['questiontext' => 'edited']);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category->id]);
|
||||
|
||||
$response = $this->process_api_request('GET', "/question/bank/{$course->id}/question_counts");
|
||||
$this->assert_valid_response($response);
|
||||
$payload = $this->decode_response($response, true);
|
||||
|
||||
$this->assertEquals(['counts' => [$qbank->cmid => 2]], $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subquestions should not be included in the question bank's total
|
||||
*/
|
||||
public function test_question_count_subquestions(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->add_class_routes_to_route_loader(
|
||||
bank::class,
|
||||
'/api/rest/v2/question'
|
||||
);
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$qbank = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
$bankcontext = module::instance($qbank->cmid);
|
||||
$category = question_get_default_category($bankcontext->id, true);
|
||||
$questiongenerator = self::getDataGenerator()->get_plugin_generator('core_question');
|
||||
$questiongenerator->create_question('multianswer', 'twosubq', ['category' => $category->id]);
|
||||
|
||||
$response = $this->process_api_request('GET', "/question/bank/{$course->id}/question_counts");
|
||||
$this->assert_valid_response($response);
|
||||
$payload = $this->decode_response($response, true);
|
||||
|
||||
$this->assertEquals(['counts' => [$qbank->cmid => 1]], $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* All course modules using the question bank should have their count returned.
|
||||
*/
|
||||
public function test_question_count_multiple_banks(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->add_class_routes_to_route_loader(
|
||||
bank::class,
|
||||
'/api/rest/v2/question'
|
||||
);
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$qbank1 = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
$qbank2 = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
$qbank3 = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
|
||||
// Quizzes can have questions too.
|
||||
$quiz = self::getDataGenerator()->create_module('quiz', ['course' => $course->id]);
|
||||
// Pages can't have questions. This cmid should not be in the list of counts.
|
||||
self::getDataGenerator()->create_module('page', ['course' => $course->id]);
|
||||
|
||||
$questiongenerator = self::getDataGenerator()->get_plugin_generator('core_question');
|
||||
// Generate questions.
|
||||
// 1 in qbank 1.
|
||||
$bank1context = module::instance($qbank1->cmid);
|
||||
$category1 = question_get_default_category($bank1context->id, true);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category1->id]);
|
||||
// None in qbank 2.
|
||||
// 3 in qbank 3.
|
||||
$bank3context = module::instance($qbank3->cmid);
|
||||
$category3 = question_get_default_category($bank3context->id, true);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category3->id]);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category3->id]);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category3->id]);
|
||||
// 2 in the quiz.
|
||||
$quizcontext = module::instance($quiz->cmid);
|
||||
$category4 = question_get_default_category($quizcontext->id, true);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category4->id]);
|
||||
$questiongenerator->create_question('truefalse', overrides: ['category' => $category4->id]);
|
||||
|
||||
$response = $this->process_api_request('GET', "/question/bank/{$course->id}/question_counts");
|
||||
$this->assert_valid_response($response);
|
||||
$payload = $this->decode_response($response, true);
|
||||
|
||||
$this->assertEquals(
|
||||
['counts' =>
|
||||
[
|
||||
$qbank1->cmid => 1,
|
||||
$qbank2->cmid => 0,
|
||||
$qbank3->cmid => 3,
|
||||
$quiz->cmid => 2,
|
||||
],
|
||||
],
|
||||
$payload,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user