Merge branch 'MDL-82393-main-1' of https://github.com/mihailges/moodle

This commit is contained in:
Huong Nguyen 2024-08-08 12:04:38 +07:00
commit 587f65f34f
No known key found for this signature in database
GPG Key ID: 40D88AB693A3E72A
18 changed files with 238 additions and 89 deletions

View File

@ -0,0 +1,12 @@
issueNumber: MDL-82393
notes:
core_course:
- message: >-
The $course parameter in the constructor of the
core_course\output\actionbar\group_selector class has been deprecated
and is no longer used.
type: deprecated
- message: >-
The $course class property in the
core_course\output\actionbar\group_selector class has been removed.
type: removed

View File

@ -6,6 +6,6 @@ define("core_course/actionbar/group",["exports","core_group/comboboxsearch/group
* @copyright 2024 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class Group extends _group.default{constructor(baseUrl){super(),function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"baseUrl",void 0),this.baseUrl=baseUrl}static init(baseUrl){return new Group(baseUrl)}selectOneLink(groupID){const url=new URL(this.baseUrl);return url.searchParams.set("groupsearchvalue",this.getSearchTerm()),url.searchParams.set("group",groupID),url.toString()}}return _exports.default=Group,_exports.default}));
class Group extends _group.default{constructor(baseUrl){super(arguments.length>1&&void 0!==arguments[1]?arguments[1]:null),function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"baseUrl",void 0),this.baseUrl=baseUrl}static init(baseUrl){return new Group(baseUrl,arguments.length>1&&void 0!==arguments[1]?arguments[1]:null)}selectOneLink(groupID){const url=new URL(this.baseUrl);return url.searchParams.set("groupsearchvalue",this.getSearchTerm()),url.searchParams.set("group",groupID),url.toString()}}return _exports.default=Group,_exports.default}));
//# sourceMappingURL=group.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"group.min.js","sources":["../../src/actionbar/group.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\nimport GroupSearch from 'core_group/comboboxsearch/group';\n\n/**\n * Allow the user to search for groups in the action bar.\n *\n * @module core_course/actionbar/group\n * @copyright 2024 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class Group extends GroupSearch {\n\n baseUrl;\n\n /**\n * Construct the class.\n * @param {string} baseUrl The base URL for the page.\n */\n constructor(baseUrl) {\n super();\n this.baseUrl = baseUrl;\n }\n\n /**\n * Allow the class to be invoked via PHP.\n *\n * @param {string} baseUrl The base URL for the page.\n * @returns {Group}\n */\n static init(baseUrl) {\n return new Group(baseUrl);\n }\n\n /**\n * Build up the link that is dedicated to a particular result.\n *\n * @param {Number} groupID The ID of the group selected.\n * @returns {string}\n */\n selectOneLink(groupID) {\n const url = new URL(this.baseUrl);\n url.searchParams.set('groupsearchvalue', this.getSearchTerm());\n url.searchParams.set('group', groupID);\n\n return url.toString();\n }\n}\n"],"names":["Group","GroupSearch","constructor","baseUrl","selectOneLink","groupID","url","URL","this","searchParams","set","getSearchTerm","toString"],"mappings":";;;;;;;;MAwBqBA,cAAcC,eAQ/BC,YAAYC,uLAEHA,QAAUA,oBASPA,gBACD,IAAIH,MAAMG,SASrBC,cAAcC,eACJC,IAAM,IAAIC,IAAIC,KAAKL,gBACzBG,IAAIG,aAAaC,IAAI,mBAAoBF,KAAKG,iBAC9CL,IAAIG,aAAaC,IAAI,QAASL,SAEvBC,IAAIM"}
{"version":3,"file":"group.min.js","sources":["../../src/actionbar/group.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\nimport GroupSearch from 'core_group/comboboxsearch/group';\n\n/**\n * Allow the user to search for groups in the action bar.\n *\n * @module core_course/actionbar/group\n * @copyright 2024 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class Group extends GroupSearch {\n\n baseUrl;\n\n /**\n * Construct the class.\n *\n * @param {string} baseUrl The base URL for the page.\n * @param {int|null} cmid ID of the course module initiating the group search (optional).\n */\n constructor(baseUrl, cmid = null) {\n super(cmid);\n this.baseUrl = baseUrl;\n }\n\n /**\n * Allow the class to be invoked via PHP.\n *\n * @param {string} baseUrl The base URL for the page.\n * @param {int|null} cmid ID of the course module initiating the group search (optional).\n * @returns {Group}\n */\n static init(baseUrl, cmid = null) {\n return new Group(baseUrl, cmid);\n }\n\n /**\n * Build up the link that is dedicated to a particular result.\n *\n * @param {Number} groupID The ID of the group selected.\n * @returns {string}\n */\n selectOneLink(groupID) {\n const url = new URL(this.baseUrl);\n url.searchParams.set('groupsearchvalue', this.getSearchTerm());\n url.searchParams.set('group', groupID);\n\n return url.toString();\n }\n}\n"],"names":["Group","GroupSearch","constructor","baseUrl","selectOneLink","groupID","url","URL","this","searchParams","set","getSearchTerm","toString"],"mappings":";;;;;;;;MAwBqBA,cAAcC,eAU/BC,YAAYC,sEAAgB,4KAEnBA,QAAUA,oBAUPA,gBACD,IAAIH,MAAMG,+DADO,MAU5BC,cAAcC,eACJC,IAAM,IAAIC,IAAIC,KAAKL,gBACzBG,IAAIG,aAAaC,IAAI,mBAAoBF,KAAKG,iBAC9CL,IAAIG,aAAaC,IAAI,QAASL,SAEvBC,IAAIM"}

View File

@ -28,10 +28,12 @@ export default class Group extends GroupSearch {
/**
* Construct the class.
*
* @param {string} baseUrl The base URL for the page.
* @param {int|null} cmid ID of the course module initiating the group search (optional).
*/
constructor(baseUrl) {
super();
constructor(baseUrl, cmid = null) {
super(cmid);
this.baseUrl = baseUrl;
}
@ -39,10 +41,11 @@ export default class Group extends GroupSearch {
* Allow the class to be invoked via PHP.
*
* @param {string} baseUrl The base URL for the page.
* @param {int|null} cmid ID of the course module initiating the group search (optional).
* @returns {Group}
*/
static init(baseUrl) {
return new Group(baseUrl);
static init(baseUrl, cmid = null) {
return new Group(baseUrl, cmid);
}
/**

View File

@ -17,10 +17,7 @@
namespace core_course\output\actionbar;
use core\output\comboboxsearch;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderable class for the group selector element in the action bar.
@ -29,85 +26,129 @@ use templatable;
* @copyright 2024 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class group_selector implements renderable, templatable {
class group_selector extends comboboxsearch {
/**
* @var stdClass The course object.
* @var stdClass The context object.
*/
protected $course;
private stdClass $context;
/**
* The class constructor.
*
* @param stdClass $course The course object.
* @param null|stdClass $course This parameter has been deprecated since Moodle 4.5 and should not be used anymore.
* @param stdClass $context The context object.
*/
public function __construct(stdClass $course) {
$this->course = $course;
public function __construct(null|stdClass $course = null, stdClass $context) {
if ($course !== null) {
debugging(
'The course argument has been deprecated. Please remove it from your group_selector class instances.',
DEBUG_DEVELOPER,
);
}
$this->context = $context;
parent::__construct(false, $this->get_button_content(), $this->get_dropdown_content(), 'group-search',
'groupsearchwidget', 'groupsearchdropdown overflow-auto', null, true, $this->get_label(), 'group',
$this->get_active_group());
}
/**
* Export the data for the mustache template.
* Returns the output for the button (trigger) element of the group selector.
*
* @param renderer_base $output The renderer that will be used to render the output.
* @return array
* @return string HTML fragment
*/
public function export_for_template(renderer_base $output) {
global $USER, $OUTPUT;
private function get_button_content(): string {
global $OUTPUT;
$course = $this->course;
$groupmode = $course->groupmode;
$sbody = $OUTPUT->render_from_template('core_group/comboboxsearch/searchbody', [
'courseid' => $course->id,
'currentvalue' => optional_param('groupsearchvalue', '', PARAM_NOTAGS),
'instance' => rand(),
]);
$label = $groupmode == VISIBLEGROUPS ? get_string('selectgroupsvisible') : get_string('selectgroupsseparate');
$buttondata = ['label' => $label];
$context = \context_course::instance($course->id);
if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $context)) {
$allowedgroups = groups_get_all_groups($course->id, 0, $course->defaultgroupingid);
} else {
$allowedgroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid);
}
$activegroup = groups_get_course_group($course, true, $allowedgroups);
$buttondata['group'] = $activegroup;
$activegroup = $this->get_active_group();
$buttondata = [
'label' => $this->get_label(),
'group' => $activegroup,
];
if ($activegroup) {
$group = groups_get_group($activegroup);
$buttondata['selectedgroup'] = format_string($group->name, true, ['context' => $context]);
$buttondata['selectedgroup'] = format_string($group->name, true,
['context' => $this->context->get_course_context()]);
} else if ($activegroup === 0) {
$buttondata['selectedgroup'] = get_string('allparticipants');
}
$groupdropdown = new comboboxsearch(
false,
$OUTPUT->render_from_template('core_group/comboboxsearch/group_selector', $buttondata),
$sbody,
'group-search',
'groupsearchwidget',
'groupsearchdropdown overflow-auto',
null,
true,
$label,
'group',
$activegroup
);
return $groupdropdown->export_for_template($OUTPUT);
return $OUTPUT->render_from_template('core_group/comboboxsearch/group_selector', $buttondata);
}
/**
* Returns the template for the group selector.
* Returns the output of the content rendered within the dropdown (search body area) of the group selector.
*
* @return string HTML fragment
*/
private function get_dropdown_content(): string {
global $OUTPUT;
return $OUTPUT->render_from_template('core_group/comboboxsearch/searchbody', [
'courseid' => $this->context->get_course_context()->instanceid,
'currentvalue' => optional_param('groupsearchvalue', '', PARAM_NOTAGS),
'instance' => rand(),
]);
}
/**
* Returns the label text for the group selector based on specified group mode.
*
* @return string
*/
public function get_template(): string {
return 'core/comboboxsearch';
private function get_label(): string {
return $this->get_group_mode() === VISIBLEGROUPS ? get_string('selectgroupsvisible') :
get_string('selectgroupsseparate');
}
/**
* Returns the active group based on the context level.
*
* @return int|bool The active group (false if groups not used, int if groups used)
*/
private function get_active_group(): int|bool {
global $USER;
$canaccessallgroups = has_capability('moodle/site:accessallgroups', $this->context);
$userid = $this->get_group_mode() == VISIBLEGROUPS || $canaccessallgroups ? 0 : $USER->id;
$course = get_course($this->context->get_course_context()->instanceid);
// Based on the current context level, retrieve the correct grouping ID and specify whether only groups with the
// participation field set to true should be returned.
if ($this->context->contextlevel === CONTEXT_MODULE) {
$cm = get_coursemodule_from_id(false, $this->context->instanceid);
$groupingid = $cm->groupingid;
$participationonly = true;
} else {
$cm = null;
$groupingid = $course->defaultgroupingid;
$participationonly = false;
}
$allowedgroups = groups_get_all_groups(
courseid: $course->id,
userid: $userid,
groupingid: $groupingid,
participationonly: $participationonly
);
if ($cm) {
return groups_get_activity_group($cm, true, $allowedgroups);
}
return groups_get_course_group($course, true, $allowedgroups);
}
/**
* Returns the group mode based on the context level.
*
* @return int The group mode
*/
private function get_group_mode(): int {
if ($this->context->contextlevel == CONTEXT_MODULE) {
$cm = get_coursemodule_from_id(false, $this->context->instanceid);
return groups_get_activity_groupmode($cm);
}
$course = get_course($this->context->instanceid);
return $course->groupmode;
}
}

View File

@ -113,7 +113,8 @@ class action_bar extends \core_grades\output\action_bar {
if ($course->groupmode) {
$actionbarrenderer = $PAGE->get_renderer('core_course', 'actionbar');
$data['groupselector'] = $actionbarrenderer->render(new \core_course\output\actionbar\group_selector($course));
$data['groupselector'] = $actionbarrenderer->render(
new \core_course\output\actionbar\group_selector(null, $this->context));
}
$resetlink = new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]);

View File

@ -147,7 +147,7 @@ class singleview extends grade_report {
global $PAGE;
$renderer = $PAGE->get_renderer('core_course', 'actionbar');
return $renderer->render(new \core_course\output\actionbar\group_selector($course));
return $renderer->render(new \core_course\output\actionbar\group_selector(null, $PAGE->context));
}
/**

View File

@ -94,7 +94,7 @@ class action_bar extends \core_grades\output\action_bar {
$userreportrenderer = $PAGE->get_renderer('gradereport_user');
$course = get_course($courseid);
if ($course->groupmode) {
$groupselector = new \core_course\output\actionbar\group_selector($course);
$groupselector = new \core_course\output\actionbar\group_selector(null, $this->context);
$data['groupselector'] = $PAGE->get_renderer('core_course', 'actionbar')->render($groupselector);
}
$data['userselector'] = [

View File

@ -1,3 +1,3 @@
define("core_group/comboboxsearch/group",["exports","core/comboboxsearch/search_combobox","core_group/comboboxsearch/repository","core/templates","core/utils","core/notification"],(function(_exports,_search_combobox,_repository,_templates,_utils,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_search_combobox=_interopRequireDefault(_search_combobox),_notification=_interopRequireDefault(_notification);class GroupSearch extends _search_combobox.default{constructor(){super(),_defineProperty(this,"courseID",void 0),_defineProperty(this,"bannedFilterFields",["id","link","groupimageurl"]),this.selectors={...this.selectors,courseid:'[data-region="courseid"]',placeholder:'.groupsearchdropdown [data-region="searchplaceholder"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid,this.instance=component.querySelector(this.selectors.instance).dataset.instance;const searchValueElement=this.component.querySelector("#".concat(this.searchInput.dataset.inputElement));searchValueElement.addEventListener("change",(()=>{this.toggleDropdown();const valueElement=this.component.querySelector("#".concat(this.combobox.dataset.inputElement));valueElement.value!==searchValueElement.value&&(valueElement.value=searchValueElement.value,valueElement.dispatchEvent(new Event("change",{bubbles:!0}))),searchValueElement.value=""})),this.$component.on("hide.bs.dropdown",(()=>{this.searchInput.removeAttribute("aria-activedescendant");const listbox=document.querySelector("#".concat(this.searchInput.getAttribute("aria-controls"),'[role="listbox"]'));listbox.querySelectorAll('.active[role="option"]').forEach((option=>{option.classList.remove("active")})),listbox.scrollTop=0,setTimeout((()=>{""!==this.searchInput.value&&(this.searchInput.value="",this.searchInput.dispatchEvent(new Event("input",{bubbles:!0})))}))})),this.renderDefault().catch(_notification.default.exception)}static init(){return new GroupSearch}componentSelector(){return".group-search"}dropdownSelector(){return".groupsearchdropdown"}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core_group/comboboxsearch/resultset",{groups:this.getMatchedResults(),hasresults:this.getMatchedResults().length>0,instance:this.instance,searchterm:this.getSearchTerm()});(0,_templates.replaceNodeContents)(this.selectors.placeholder,html,js),this.searchInput.removeAttribute("aria-activedescendant")}async renderDefault(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown(),this.updateNodes()}async fetchDataset(){return await(0,_repository.groupFetch)(this.courseID).then((r=>r.groups))}async filterDataset(filterableData){return""===this.getPreppedSearchTerm()?filterableData:filterableData.filter((group=>Object.keys(group).some((key=>""!==group[key]&&!this.bannedFilterFields.includes(key)&&group[key].toString().toLowerCase().includes(this.getPreppedSearchTerm())))))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((group=>({id:group.id,name:group.name,groupimageurl:group.groupimageurl}))))}async clickHandler(e){e.target.closest(this.selectors.clearSearch)&&(e.stopPropagation(),this.searchInput.value="",this.setSearchTerms(this.searchInput.value),this.searchInput.focus(),this.clearSearchButton.classList.add("d-none"),await this.filterrenderpipe())}changeHandler(e){window.location=this.selectOneLink(e.target.value)}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{this.setSearchTerms(this.searchInput.value),""===this.getSearchTerm()?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none"),await this.filterrenderpipe()}),300))}selectOneLink(groupID){throw new Error("selectOneLink(".concat(groupID,") must be implemented in ").concat(this.constructor.name))}}return _exports.default=GroupSearch,_exports.default}));
define("core_group/comboboxsearch/group",["exports","core/comboboxsearch/search_combobox","core_group/comboboxsearch/repository","core/templates","core/utils","core/notification"],(function(_exports,_search_combobox,_repository,_templates,_utils,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_search_combobox=_interopRequireDefault(_search_combobox),_notification=_interopRequireDefault(_notification);class GroupSearch extends _search_combobox.default{constructor(){let cmid=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;super(),_defineProperty(this,"courseID",void 0),_defineProperty(this,"cmID",void 0),_defineProperty(this,"bannedFilterFields",["id","link","groupimageurl"]),this.selectors={...this.selectors,courseid:'[data-region="courseid"]',placeholder:'.groupsearchdropdown [data-region="searchplaceholder"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid,this.instance=component.querySelector(this.selectors.instance).dataset.instance,this.cmID=cmid;const searchValueElement=this.component.querySelector("#".concat(this.searchInput.dataset.inputElement));searchValueElement.addEventListener("change",(()=>{this.toggleDropdown();const valueElement=this.component.querySelector("#".concat(this.combobox.dataset.inputElement));valueElement.value!==searchValueElement.value&&(valueElement.value=searchValueElement.value,valueElement.dispatchEvent(new Event("change",{bubbles:!0}))),searchValueElement.value=""})),this.$component.on("hide.bs.dropdown",(()=>{this.searchInput.removeAttribute("aria-activedescendant");const listbox=document.querySelector("#".concat(this.searchInput.getAttribute("aria-controls"),'[role="listbox"]'));listbox.querySelectorAll('.active[role="option"]').forEach((option=>{option.classList.remove("active")})),listbox.scrollTop=0,setTimeout((()=>{""!==this.searchInput.value&&(this.searchInput.value="",this.searchInput.dispatchEvent(new Event("input",{bubbles:!0})))}))})),this.renderDefault().catch(_notification.default.exception)}static init(){return new GroupSearch(arguments.length>0&&void 0!==arguments[0]?arguments[0]:null)}componentSelector(){return".group-search"}dropdownSelector(){return".groupsearchdropdown"}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core_group/comboboxsearch/resultset",{groups:this.getMatchedResults(),hasresults:this.getMatchedResults().length>0,instance:this.instance,searchterm:this.getSearchTerm()});(0,_templates.replaceNodeContents)(this.selectors.placeholder,html,js),this.searchInput.removeAttribute("aria-activedescendant")}async renderDefault(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown(),this.updateNodes()}async fetchDataset(){return await(0,_repository.groupFetch)(this.courseID,this.cmID).then((r=>r.groups))}async filterDataset(filterableData){return""===this.getPreppedSearchTerm()?filterableData:filterableData.filter((group=>Object.keys(group).some((key=>""!==group[key]&&!this.bannedFilterFields.includes(key)&&group[key].toString().toLowerCase().includes(this.getPreppedSearchTerm())))))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((group=>({id:group.id,name:group.name,groupimageurl:group.groupimageurl}))))}async clickHandler(e){e.target.closest(this.selectors.clearSearch)&&(e.stopPropagation(),this.searchInput.value="",this.setSearchTerms(this.searchInput.value),this.searchInput.focus(),this.clearSearchButton.classList.add("d-none"),await this.filterrenderpipe())}changeHandler(e){window.location=this.selectOneLink(e.target.value)}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{this.setSearchTerms(this.searchInput.value),""===this.getSearchTerm()?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none"),await this.filterrenderpipe()}),300))}selectOneLink(groupID){throw new Error("selectOneLink(".concat(groupID,") must be implemented in ").concat(this.constructor.name))}}return _exports.default=GroupSearch,_exports.default}));
//# sourceMappingURL=group.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,6 @@ define("core_group/comboboxsearch/repository",["exports","core/ajax"],(function(
* @module core_group/comboboxsearch/repository
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.groupFetch=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.groupFetch=courseid=>{const request={methodname:"core_group_get_groups_for_selector",args:{courseid:courseid}};return _ajax.default.call([request])[0]}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.groupFetch=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.groupFetch=function(courseid){let cmid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;const request={methodname:"core_group_get_groups_for_selector",args:{courseid:courseid,cmid:cmid}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=repository.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"repository.min.js","sources":["../../src/comboboxsearch/repository.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 * A repo for the comboboxsearch group type.\n *\n * @module core_group/comboboxsearch/repository\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ajax from \"core/ajax\";\n\n/**\n * Given a course ID, we want to fetch the groups, so we may fetch their users.\n *\n * @method groupFetch\n * @param {int} courseid ID of the course to fetch the users of.\n * @return {object} jQuery promise\n */\nexport const groupFetch = (courseid) => {\n const request = {\n methodname: 'core_group_get_groups_for_selector',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"names":["courseid","request","methodname","args","ajax","call"],"mappings":";;;;;;;gKAgC2BA,iBACjBC,QAAU,CACZC,WAAY,qCACZC,KAAM,CACFH,SAAUA,kBAGXI,cAAKC,KAAK,CAACJ,UAAU"}
{"version":3,"file":"repository.min.js","sources":["../../src/comboboxsearch/repository.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 * A repo for the comboboxsearch group type.\n *\n * @module core_group/comboboxsearch/repository\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ajax from \"core/ajax\";\n\n/**\n * Given a course ID and optionally a module ID, we want to fetch the groups, so we may fetch their users.\n *\n * @method groupFetch\n * @param {int} courseid ID of the course to fetch the groups of.\n * @param {int|null} cmid ID of the course module initiating the group search (optional).\n * @return {object} jQuery promise\n */\nexport const groupFetch = (courseid, cmid = null) => {\n const request = {\n methodname: 'core_group_get_groups_for_selector',\n args: {\n courseid: courseid,\n cmid: cmid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"names":["courseid","cmid","request","methodname","args","ajax","call"],"mappings":";;;;;;;gKAiC0B,SAACA,cAAUC,4DAAO,WAClCC,QAAU,CACZC,WAAY,qCACZC,KAAM,CACFJ,SAAUA,SACVC,KAAMA,cAGPI,cAAKC,KAAK,CAACJ,UAAU"}

View File

@ -29,9 +29,15 @@ import Notification from 'core/notification';
export default class GroupSearch extends search_combobox {
courseID;
cmID;
bannedFilterFields = ['id', 'link', 'groupimageurl'];
constructor() {
/**
* Construct the class.
*
* @param {int|null} cmid ID of the course module initiating the group search (optional).
*/
constructor(cmid = null) {
super();
this.selectors = {...this.selectors,
courseid: '[data-region="courseid"]',
@ -41,6 +47,7 @@ export default class GroupSearch extends search_combobox {
this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid;
// Override the instance since the body is built outside the constructor for the combobox.
this.instance = component.querySelector(this.selectors.instance).dataset.instance;
this.cmID = cmid;
const searchValueElement = this.component.querySelector(`#${this.searchInput.dataset.inputElement}`);
searchValueElement.addEventListener('change', () => {
@ -76,8 +83,13 @@ export default class GroupSearch extends search_combobox {
this.renderDefault().catch(Notification.exception);
}
static init() {
return new GroupSearch();
/**
* Initialise an instance of the class.
*
* @param {int|null} cmid ID of the course module initiating the group search (optional).
*/
static init(cmid = null) {
return new GroupSearch(cmid);
}
/**
@ -131,7 +143,7 @@ export default class GroupSearch extends search_combobox {
* @returns {Promise<*>}
*/
async fetchDataset() {
return await groupFetch(this.courseID).then((r) => r.groups);
return await groupFetch(this.courseID, this.cmID).then((r) => r.groups);
}
/**

View File

@ -24,17 +24,19 @@
import ajax from "core/ajax";
/**
* Given a course ID, we want to fetch the groups, so we may fetch their users.
* Given a course ID and optionally a module ID, we want to fetch the groups, so we may fetch their users.
*
* @method groupFetch
* @param {int} courseid ID of the course to fetch the users of.
* @param {int} courseid ID of the course to fetch the groups of.
* @param {int|null} cmid ID of the course module initiating the group search (optional).
* @return {object} jQuery promise
*/
export const groupFetch = (courseid) => {
export const groupFetch = (courseid, cmid = null) => {
const request = {
methodname: 'core_group_get_groups_for_selector',
args: {
courseid: courseid,
cmid: cmid,
},
};
return ajax.call([request])[0];

View File

@ -17,6 +17,7 @@
namespace core_group\external;
use context_course;
use context_module;
use core_external\external_api;
use core_external\external_description;
use core_external\external_function_parameters;
@ -49,6 +50,7 @@ class get_groups_for_selector extends external_api {
return new external_function_parameters (
[
'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED),
'cmid' => new external_value(PARAM_INT, 'Course module Id', VALUE_DEFAULT, 0),
]
);
}
@ -56,38 +58,66 @@ class get_groups_for_selector extends external_api {
/**
* Given a course ID find the existing user groups and map some fields to the returned array of group objects.
*
* If a course module ID is provided, this function will return only the available groups within the given course
* module, adhering to the set group mode for that context. All validation checks will be performed within this
* specific context.
*
* @param int $courseid
* @param int|null $cmid The course module ID (optional).
* @return array Groups and warnings to pass back to the calling widget.
*/
public static function execute(int $courseid): array {
public static function execute(int $courseid, ?int $cmid = null): array {
global $DB, $USER, $OUTPUT;
$params = self::validate_parameters(
self::execute_parameters(),
[
'courseid' => $courseid,
'cmid' => $cmid,
]
);
$warnings = [];
$context = context_course::instance($params['courseid']);
$course = $DB->get_record('course', ['id' => $params['courseid']]);
if ($params['cmid']) {
$context = context_module::instance($params['cmid']);
$cm = get_coursemodule_from_id('', $params['cmid']);
$groupmode = groups_get_activity_groupmode($cm, $course);
$groupingid = $cm->groupingid;
$participationonly = true;
} else {
$context = context_course::instance($params['courseid']);
$groupmode = $course->groupmode;
$groupingid = $course->defaultgroupingid;
$participationonly = false;
}
parent::validate_context($context);
$mappedgroups = [];
$course = $DB->get_record('course', ['id' => $params['courseid']]);
// Initialise the grade tracking object.
if ($groupmode = $course->groupmode) {
if ($groupmode) {
$aag = has_capability('moodle/site:accessallgroups', $context);
$usergroups = [];
$groupuserid = 0;
if ($groupmode == VISIBLEGROUPS || $aag) {
$groupuserid = 0;
// Get user's own groups and put to the top.
$usergroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid);
$usergroups = groups_get_all_groups(
courseid: $course->id,
userid: $USER->id,
groupingid: $groupingid,
participationonly: $participationonly
);
} else {
$groupuserid = $USER->id;
}
$allowedgroups = groups_get_all_groups($course->id, $groupuserid, $course->defaultgroupingid);
$allowedgroups = groups_get_all_groups(
courseid: $course->id,
userid: $groupuserid,
groupingid: $groupingid,
participationonly: $participationonly
);
$allgroups = array_merge($allowedgroups, $usergroups);
// Filter out any duplicate groups.
@ -103,7 +133,8 @@ class get_groups_for_selector extends external_api {
$mappedgroups = array_map(function($group) use ($context, $OUTPUT) {
if ($group->id) { // Particular group. Get the group picture if it exists, otherwise return a generic image.
$picture = get_group_picture_url($group, $group->courseid, true) ??
moodle_url::make_pluginfile_url($context->id, 'group', 'generated', $group->id, '/', 'group.svg');
moodle_url::make_pluginfile_url($context->get_course_context()->id, 'group', 'generated', $group->id,
'/', 'group.svg');
} else { // All participants.
$picture = $OUTPUT->image_url('g/g1');
}

View File

@ -81,7 +81,9 @@ class grading_actionmenu implements templatable, renderable {
global $PAGE, $OUTPUT;
$course = $this->assign->get_course();
$cm = get_coursemodule_from_id('assign', $this->cmid);
$actionbarrenderer = $PAGE->get_renderer('core_course', 'actionbar');
$data = [];
$userid = optional_param('userid', null, PARAM_INT);
@ -101,8 +103,9 @@ class grading_actionmenu implements templatable, renderable {
);
$data['userselector'] = $actionbarrenderer->render($userselector);
if ($course->groupmode) {
$data['groupselector'] = $actionbarrenderer->render(new \core_course\output\actionbar\group_selector($course));
if (groups_get_activity_groupmode($cm, $course)) {
$data['groupselector'] = $actionbarrenderer->render(
new \core_course\output\actionbar\group_selector(null, $PAGE->context));
}
if ($extrafiltersdropdown = $this->get_extra_filters_dropdown()) {
@ -110,7 +113,7 @@ class grading_actionmenu implements templatable, renderable {
$data['extrafiltersdropdown'] = $OUTPUT->render($extrafiltersdropdown);
}
if (groups_get_course_group($course) || $this->get_applied_extra_filters_count() > 0) {
if (groups_get_activity_group($cm) || $this->get_applied_extra_filters_count() > 0) {
$url = new moodle_url('/mod/assign/view.php', [
'id' => $this->cmid,
'action' => 'grading',

View File

@ -4582,8 +4582,8 @@ class assign {
$PAGE->requires->js_call_amd('mod_assign/user', 'init', [$currenturl->out(false)]);
// Conditionally add the group JS if we have groups enabled.
if ($this->get_course()->groupmode) {
$PAGE->requires->js_call_amd('core_course/actionbar/group', 'init', [$currenturl->out(false)]);
if (groups_get_activity_groupmode($this->get_course_module(), $this->get_course())) {
$PAGE->requires->js_call_amd('core_course/actionbar/group', 'init', [$currenturl->out(false), $cmid]);
}
$header = new assign_header($this->get_instance(),

View File

@ -69,6 +69,50 @@ Feature: Group assignment submissions
And I am on the "Homepage" page logged in as student1
And I should not see "Test assignment name" in the "Timeline" "block"
@javascript
Scenario: Confirm that the group switching option is available only when the group settings are correctly configured
Given the following "activity" exists:
| activity | assign |
| course | C1 |
| name | Test assignment |
And I am on the "Test assignment" "assign activity editing" page logged in as teacher1
# The assignment does not have a specified group mode.
When I set the following fields to these values:
| Group mode | No groups |
And I press "Save and display"
And I follow "View all submissions"
Then ".groupsearchwidget" "css_element" should not exist
# The course has a specified group mode, but not enforced on modules.
And I am on the "C1" "course editing" page
And I set the following fields to these values:
| Group mode | Separate groups |
| Force group mode | No |
And I press "Save and display"
And I am on the "Test assignment" Activity page
And I follow "View all submissions"
And ".groupsearchwidget" "css_element" should not exist
# The assignment has a specified group mode.
And I am on the "Test assignment" "assign activity editing" page
And I set the following fields to these values:
| Group mode | Visible groups |
And I press "Save and display"
And I follow "View all submissions"
And ".groupsearchwidget" "css_element" should exist
And I should see "Select visible groups" in the ".groupsearchwidget" "css_element"
And I confirm "All participants" in "group" search within the gradebook widget exists
And I confirm "Group 1" in "group" search within the gradebook widget exists
# The course enforces its group mode on modules.
And I am on the "C1" "course editing" page
And I set the following fields to these values:
| Force group mode | Yes |
And I press "Save and display"
And I am on the "Test assignment" Activity page
And I follow "View all submissions"
And ".groupsearchwidget" "css_element" should exist
And I should see "Select separate groups" in the ".groupsearchwidget" "css_element"
And I confirm "All participants" in "group" search within the gradebook widget exists
And I confirm "Group 1" in "group" search within the gradebook widget exists
@javascript
Scenario: Switch between group modes
Given the following "activity" exists: