mirror of
https://github.com/moodle/moodle.git
synced 2025-02-11 19:16:23 +01:00
Merge branch 'MDL-82393-main-1' of https://github.com/mihailges/moodle
This commit is contained in:
commit
587f65f34f
12
.upgradenotes/MDL-82393-2024071205253416.yml
Normal file
12
.upgradenotes/MDL-82393-2024071205253416.yml
Normal 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
|
2
course/amd/build/actionbar/group.min.js
vendored
2
course/amd/build/actionbar/group.min.js
vendored
@ -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
|
@ -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"}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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'] = [
|
||||
|
2
group/amd/build/comboboxsearch/group.min.js
vendored
2
group/amd/build/comboboxsearch/group.min.js
vendored
@ -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
@ -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
|
@ -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"}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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];
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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(),
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user