first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+3
View File
@@ -0,0 +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}));
//# sourceMappingURL=group.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("core_group/comboboxsearch/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* A repo for the comboboxsearch group type.
*
* @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]}}));
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +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"}
+9
View File
@@ -0,0 +1,9 @@
define("core_group/grouppicker",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default=
/**
* @module core_group/groupPicker
* @copyright 2022 Matthew Hilton <matthewhilton@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class{constructor(){let elementId=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"groups";const pickerDomElement=document.getElementById(elementId);if(!pickerDomElement)throw new Error("Groups picker was not found.");this.element=pickerDomElement}getDomElement(){return this.element}getSelectedValues(){return Array.from(this.element.querySelectorAll("option:checked")).map((el=>parseInt(el.value)))}},_exports.default}));
//# sourceMappingURL=grouppicker.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"grouppicker.min.js","sources":["../src/grouppicker.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 * @module core_group/groupPicker\n * @copyright 2022 Matthew Hilton <matthewhilton@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Class used for interfacing with the group select picker.\n *\n * @class core_group/GroupPicker\n */\nexport default class GroupPicker {\n /**\n * Creates the group picker class and finds the corresponding DOM element.\n *\n * @param {String} elementId The DOM element id of the <select> input\n * @throws Error if the element was not found.\n */\n constructor(elementId = \"groups\") {\n const pickerDomElement = document.getElementById(elementId);\n\n if (!pickerDomElement) {\n throw new Error(\"Groups picker was not found.\");\n }\n\n this.element = pickerDomElement;\n }\n\n /**\n * Returns the DOM element this class is linked to.\n *\n * @returns {HTMLElement} The DOM element\n */\n getDomElement() {\n return this.element;\n }\n\n /**\n * Returns the selected group values.\n *\n * @returns {Number[]} The group IDs that are currently selected.\n */\n getSelectedValues() {\n const selectedOptionElements = Array.from(this.element.querySelectorAll(\"option:checked\"));\n const selectedGroups = selectedOptionElements.map(el => parseInt(el.value));\n\n return selectedGroups;\n }\n}\n"],"names":["constructor","elementId","pickerDomElement","document","getElementById","Error","element","getDomElement","this","getSelectedValues","Array","from","querySelectorAll","map","el","parseInt","value"],"mappings":";;;;;;MAgCIA,kBAAYC,iEAAY,eACdC,iBAAmBC,SAASC,eAAeH,eAE5CC,uBACK,IAAIG,MAAM,qCAGfC,QAAUJ,iBAQnBK,uBACWC,KAAKF,QAQhBG,2BACmCC,MAAMC,KAAKH,KAAKF,QAAQM,iBAAiB,mBAC1BC,KAAIC,IAAMC,SAASD,GAAGE"}
+8
View File
@@ -0,0 +1,8 @@
define("core_group/index",["exports","./grouppicker"],(function(_exports,_grouppicker){var obj;
/**
* @module core_group/index
* @copyright 2022 Matthew Hilton <matthewhilton@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateBulkActionButtons=_exports.setElementEnabled=_exports.init=void 0;const groupPicker=new(_grouppicker=(obj=_grouppicker)&&obj.__esModule?obj:{default:obj}).default;_exports.init=()=>{groupPicker.getDomElement().addEventListener("change",updateBulkActionButtons),updateBulkActionButtons()};const updateBulkActionButtons=()=>{const aGroupIsSelected=0!==groupPicker.getSelectedValues().length,bulkActionsEnabledStatuses={enablemessaging:aGroupIsSelected,disablemessaging:aGroupIsSelected};Object.entries(bulkActionsEnabledStatuses).map((_ref=>{let[buttonId,enabled]=_ref;return setElementEnabled(buttonId,enabled)}))};_exports.updateBulkActionButtons=updateBulkActionButtons;const setElementEnabled=(domElementId,enabled)=>{const element=document.getElementById(domElementId);element&&(enabled?element.removeAttribute("disabled"):element.setAttribute("disabled","disabled"))};_exports.setElementEnabled=setElementEnabled}));
//# sourceMappingURL=index.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.min.js","sources":["../src/index.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 * @module core_group/index\n * @copyright 2022 Matthew Hilton <matthewhilton@catalyst-au.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport GroupPicker from \"./grouppicker\";\n\nconst groupPicker = new GroupPicker();\n\n/**\n * Initialise page.\n */\nexport const init = () => {\n // Init event listeners.\n groupPicker.getDomElement().addEventListener(\"change\", updateBulkActionButtons);\n\n // Call initially to set initial button state.\n updateBulkActionButtons();\n};\n\n/**\n * Updates the bulk action buttons depending on specific conditions.\n */\nexport const updateBulkActionButtons = () => {\n const groupsSelected = groupPicker.getSelectedValues();\n const aGroupIsSelected = groupsSelected.length !== 0;\n\n // Collate the conditions where each button is enabled/disabled.\n const bulkActionsEnabledStatuses = {\n 'enablemessaging': aGroupIsSelected,\n 'disablemessaging': aGroupIsSelected\n };\n\n // Update the status of each button.\n Object.entries(bulkActionsEnabledStatuses).map(([buttonId, enabled]) => setElementEnabled(buttonId, enabled));\n};\n\n/**\n * Adds or removes the given element's disabled attribute.\n * @param {string} domElementId ID of the dom element (without the #)\n * @param {bool} enabled If false, the disable attribute is applied, else it is removed.\n */\nexport const setElementEnabled = (domElementId, enabled) => {\n const element = document.getElementById(domElementId);\n\n if (!element) {\n // If there is no element, we do nothing.\n // The element could be purposefully hidden or removed.\n return;\n }\n\n if (!enabled) {\n element.setAttribute('disabled', 'disabled');\n } else {\n element.removeAttribute('disabled');\n }\n};\n"],"names":["groupPicker","getDomElement","addEventListener","updateBulkActionButtons","aGroupIsSelected","getSelectedValues","length","bulkActionsEnabledStatuses","Object","entries","map","_ref","buttonId","enabled","setElementEnabled","domElementId","element","document","getElementById","removeAttribute","setAttribute"],"mappings":";;;;;oJAsBMA,YAAc,6FAKA,KAEhBA,YAAYC,gBAAgBC,iBAAiB,SAAUC,yBAGvDA,iCAMSA,wBAA0B,WAE7BC,iBAA6C,IAD5BJ,YAAYK,oBACKC,OAGlCC,2BAA6B,iBACZH,kCACCA,kBAIxBI,OAAOC,QAAQF,4BAA4BG,KAAIC,WAAEC,SAAUC,qBAAaC,kBAAkBF,SAAUC,4EAQ3FC,kBAAoB,CAACC,aAAcF,iBACtCG,QAAUC,SAASC,eAAeH,cAEnCC,UAMAH,QAGDG,QAAQG,gBAAgB,YAFxBH,QAAQI,aAAa,WAAY"}
+227
View File
@@ -0,0 +1,227 @@
// 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/>.
/**
* Allow the user to search for groups.
*
* @module core_group/comboboxsearch/group
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import search_combobox from 'core/comboboxsearch/search_combobox';
import {groupFetch} from 'core_group/comboboxsearch/repository';
import {renderForPromise, replaceNodeContents} from 'core/templates';
import {debounce} from 'core/utils';
import Notification from 'core/notification';
export default class GroupSearch extends search_combobox {
courseID;
bannedFilterFields = ['id', 'link', 'groupimageurl'];
constructor() {
super();
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;
// Override the instance since the body is built outside the constructor for the combobox.
this.instance = component.querySelector(this.selectors.instance).dataset.instance;
const searchValueElement = this.component.querySelector(`#${this.searchInput.dataset.inputElement}`);
searchValueElement.addEventListener('change', () => {
this.toggleDropdown(); // Otherwise the dropdown stays open when user choose an option using keyboard.
const valueElement = this.component.querySelector(`#${this.combobox.dataset.inputElement}`);
if (valueElement.value !== searchValueElement.value) {
valueElement.value = searchValueElement.value;
valueElement.dispatchEvent(new Event('change', {bubbles: true}));
}
searchValueElement.value = '';
});
this.$component.on('hide.bs.dropdown', () => {
this.searchInput.removeAttribute('aria-activedescendant');
const listbox = document.querySelector(`#${this.searchInput.getAttribute('aria-controls')}[role="listbox"]`);
listbox.querySelectorAll('.active[role="option"]').forEach(option => {
option.classList.remove('active');
});
listbox.scrollTop = 0;
// Use setTimeout to make sure the following code is executed after the click event is handled.
setTimeout(() => {
if (this.searchInput.value !== '') {
this.searchInput.value = '';
this.searchInput.dispatchEvent(new Event('input', {bubbles: true}));
}
});
});
this.renderDefault().catch(Notification.exception);
}
static init() {
return new GroupSearch();
}
/**
* The overall div that contains the searching widget.
*
* @returns {string}
*/
componentSelector() {
return '.group-search';
}
/**
* The dropdown div that contains the searching widget result space.
*
* @returns {string}
*/
dropdownSelector() {
return '.groupsearchdropdown';
}
/**
* Build the content then replace the node.
*/
async renderDropdown() {
const {html, js} = await renderForPromise('core_group/comboboxsearch/resultset', {
groups: this.getMatchedResults(),
hasresults: this.getMatchedResults().length > 0,
instance: this.instance,
searchterm: this.getSearchTerm(),
});
replaceNodeContents(this.selectors.placeholder, html, js);
// Remove aria-activedescendant when the available options change.
this.searchInput.removeAttribute('aria-activedescendant');
}
/**
* Build the content then replace the node by default we want our form to exist.
*/
async renderDefault() {
this.setMatchedResults(await this.filterDataset(await this.getDataset()));
this.filterMatchDataset();
await this.renderDropdown();
this.updateNodes();
}
/**
* Get the data we will be searching against in this component.
*
* @returns {Promise<*>}
*/
async fetchDataset() {
return await groupFetch(this.courseID).then((r) => r.groups);
}
/**
* Dictate to the search component how and what we want to match upon.
*
* @param {Array} filterableData
* @returns {Array} The users that match the given criteria.
*/
async filterDataset(filterableData) {
// Sometimes we just want to show everything.
if (this.getPreppedSearchTerm() === '') {
return filterableData;
}
return filterableData.filter((group) => Object.keys(group).some((key) => {
if (group[key] === "" || this.bannedFilterFields.includes(key)) {
return false;
}
return group[key].toString().toLowerCase().includes(this.getPreppedSearchTerm());
}));
}
/**
* Given we have a subset of the dataset, set the field that we matched upon to inform the end user.
*/
filterMatchDataset() {
this.setMatchedResults(
this.getMatchedResults().map((group) => {
return {
id: group.id,
name: group.name,
groupimageurl: group.groupimageurl,
};
})
);
}
/**
* The handler for when a user interacts with the component.
*
* @param {MouseEvent} e The triggering event that we are working with.
*/
async clickHandler(e) {
if (e.target.closest(this.selectors.clearSearch)) {
e.stopPropagation();
// Clear the entered search query in the search bar.
this.searchInput.value = '';
this.setSearchTerms(this.searchInput.value);
this.searchInput.focus();
this.clearSearchButton.classList.add('d-none');
// Display results.
await this.filterrenderpipe();
}
}
/**
* The handler for when a user changes the value of the component (selects an option from the dropdown).
*
* @param {Event} e The change event.
*/
changeHandler(e) {
window.location = this.selectOneLink(e.target.value);
}
/**
* Override the input event listener for the text input area.
*/
registerInputHandlers() {
// Register & handle the text input.
this.searchInput.addEventListener('input', debounce(async() => {
this.setSearchTerms(this.searchInput.value);
// We can also require a set amount of input before search.
if (this.getSearchTerm() === '') {
// Hide the "clear" search button in the search bar.
this.clearSearchButton.classList.add('d-none');
} else {
// Display the "clear" search button in the search bar.
this.clearSearchButton.classList.remove('d-none');
}
// User has given something for us to filter against.
await this.filterrenderpipe();
}, 300));
}
/**
* Build up the view all link that is dedicated to a particular result.
* We will call this function when a user interacts with the combobox to redirect them to show their results in the page.
*
* @param {Number} groupID The ID of the group selected.
*/
selectOneLink(groupID) {
throw new Error(`selectOneLink(${groupID}) must be implemented in ${this.constructor.name}`);
}
}
@@ -0,0 +1,41 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A repo for the comboboxsearch group type.
*
* @module core_group/comboboxsearch/repository
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import ajax from "core/ajax";
/**
* Given a course 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.
* @return {object} jQuery promise
*/
export const groupFetch = (courseid) => {
const request = {
methodname: 'core_group_get_groups_for_selector',
args: {
courseid: courseid,
},
};
return ajax.call([request])[0];
};
+63
View File
@@ -0,0 +1,63 @@
// 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/>.
/**
* @module core_group/groupPicker
* @copyright 2022 Matthew Hilton <matthewhilton@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Class used for interfacing with the group select picker.
*
* @class core_group/GroupPicker
*/
export default class GroupPicker {
/**
* Creates the group picker class and finds the corresponding DOM element.
*
* @param {String} elementId The DOM element id of the <select> input
* @throws Error if the element was not found.
*/
constructor(elementId = "groups") {
const pickerDomElement = document.getElementById(elementId);
if (!pickerDomElement) {
throw new Error("Groups picker was not found.");
}
this.element = pickerDomElement;
}
/**
* Returns the DOM element this class is linked to.
*
* @returns {HTMLElement} The DOM element
*/
getDomElement() {
return this.element;
}
/**
* Returns the selected group values.
*
* @returns {Number[]} The group IDs that are currently selected.
*/
getSelectedValues() {
const selectedOptionElements = Array.from(this.element.querySelectorAll("option:checked"));
const selectedGroups = selectedOptionElements.map(el => parseInt(el.value));
return selectedGroups;
}
}
+72
View File
@@ -0,0 +1,72 @@
// 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/>.
/**
* @module core_group/index
* @copyright 2022 Matthew Hilton <matthewhilton@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import GroupPicker from "./grouppicker";
const groupPicker = new GroupPicker();
/**
* Initialise page.
*/
export const init = () => {
// Init event listeners.
groupPicker.getDomElement().addEventListener("change", updateBulkActionButtons);
// Call initially to set initial button state.
updateBulkActionButtons();
};
/**
* Updates the bulk action buttons depending on specific conditions.
*/
export const updateBulkActionButtons = () => {
const groupsSelected = groupPicker.getSelectedValues();
const aGroupIsSelected = groupsSelected.length !== 0;
// Collate the conditions where each button is enabled/disabled.
const bulkActionsEnabledStatuses = {
'enablemessaging': aGroupIsSelected,
'disablemessaging': aGroupIsSelected
};
// Update the status of each button.
Object.entries(bulkActionsEnabledStatuses).map(([buttonId, enabled]) => setElementEnabled(buttonId, enabled));
};
/**
* Adds or removes the given element's disabled attribute.
* @param {string} domElementId ID of the dom element (without the #)
* @param {bool} enabled If false, the disable attribute is applied, else it is removed.
*/
export const setElementEnabled = (domElementId, enabled) => {
const element = document.getElementById(domElementId);
if (!element) {
// If there is no element, we do nothing.
// The element could be purposefully hidden or removed.
return;
}
if (!enabled) {
element.setAttribute('disabled', 'disabled');
} else {
element.removeAttribute('disabled');
}
};
+193
View File
@@ -0,0 +1,193 @@
<?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/>.
/**
* Add/remove group from grouping.
*
* @copyright 1999 Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once('../config.php');
require_once('lib.php');
$groupingid = required_param('id', PARAM_INT);
$PAGE->set_url('/group/assign.php', array('id'=>$groupingid));
if (!$grouping = $DB->get_record('groupings', array('id'=>$groupingid))) {
throw new \moodle_exception('invalidgroupid');
}
if (!$course = $DB->get_record('course', array('id'=>$grouping->courseid))) {
throw new \moodle_exception('invalidcourse');
}
$courseid = $course->id;
require_login($course);
$context = context_course::instance($courseid);
require_capability('moodle/course:managegroups', $context);
$returnurl = $CFG->wwwroot.'/group/groupings.php?id='.$courseid;
if ($frm = data_submitted() and confirm_sesskey()) {
if (isset($frm->cancel)) {
redirect($returnurl);
} else if (isset($frm->add) and !empty($frm->addselect)) {
foreach ($frm->addselect as $groupid) {
// Ask this method not to purge the cache, we'll do it ourselves afterwards.
groups_assign_grouping($grouping->id, (int)$groupid, null, false);
}
// Invalidate the course groups cache seeing as we've changed it.
cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
// Invalidate the user_group_groupings cache, too.
cache_helper::purge_by_definition('core', 'user_group_groupings');
} else if (isset($frm->remove) and !empty($frm->removeselect)) {
foreach ($frm->removeselect as $groupid) {
// Ask this method not to purge the cache, we'll do it ourselves afterwards.
groups_unassign_grouping($grouping->id, (int)$groupid, false);
}
// Invalidate the course groups cache seeing as we've changed it.
cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
// Invalidate the user_group_groupings cache, too.
cache_helper::purge_by_definition('core', 'user_group_groupings');
}
}
$currentmembers = array();
$potentialmembers = array();
if ($groups = $DB->get_records('groups', array('courseid'=>$courseid), 'name')) {
if ($assignment = $DB->get_records('groupings_groups', array('groupingid'=>$grouping->id))) {
foreach ($assignment as $ass) {
$currentmembers[$ass->groupid] = $groups[$ass->groupid];
unset($groups[$ass->groupid]);
}
}
$potentialmembers = $groups;
}
$currentmembersoptions = '';
$currentmemberscount = 0;
if ($currentmembers) {
foreach($currentmembers as $group) {
$currentmembersoptions .= '<option value="' . $group->id . '." title="' . format_string($group->name) . '">' .
format_string($group->name) . '</option>';
$currentmemberscount ++;
}
// Get course managers so they can be highlighted in the list
if ($managerroles = get_config('', 'coursecontact')) {
$coursecontactroles = explode(',', $managerroles);
foreach ($coursecontactroles as $roleid) {
$role = $DB->get_record('role', array('id'=>$roleid));
$managers = get_role_users($roleid, $context, true, 'u.id', 'u.id ASC');
}
}
} else {
$currentmembersoptions .= '<option>&nbsp;</option>';
}
$potentialmembersoptions = '';
$potentialmemberscount = 0;
if ($potentialmembers) {
foreach($potentialmembers as $group) {
$potentialmembersoptions .= '<option value="' . $group->id . '." title="' . format_string($group->name) . '">' .
format_string($group->name) . '</option>';
$potentialmemberscount ++;
}
} else {
$potentialmembersoptions .= '<option>&nbsp;</option>';
}
// Print the page and form
$strgroups = get_string('groups');
$strparticipants = get_string('participants');
$straddgroupstogroupings = get_string('addgroupstogroupings', 'group');
$groupingname = format_string($grouping->name);
navigation_node::override_active_url(new moodle_url('/group/index.php', array('id'=>$course->id)));
$PAGE->set_pagelayout('admin');
$PAGE->navbar->add($strparticipants, new moodle_url('/user/index.php', array('id'=>$courseid)));
$PAGE->navbar->add(get_string('groupings', 'group'),
new moodle_url('/group/groupings.php', ['id' => $courseid]));
$PAGE->navbar->add($straddgroupstogroupings);
/// Print header
$PAGE->set_title("$course->shortname: $strgroups");
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
?>
<div id="addmembersform">
<h3 class="main"><?php print_string('addgroupstogroupings', 'group'); echo ": $groupingname"; ?></h3>
<form id="assignform" method="post" action="">
<div>
<input type="hidden" name="sesskey" value="<?php p(sesskey()); ?>" />
<table summary="" class="generaltable generalbox groupmanagementtable boxaligncenter">
<tr>
<td id="existingcell">
<label for="removeselect"><?php print_string('existingmembers', 'group', $currentmemberscount); ?></label>
<div class="userselector" id="removeselect_wrapper">
<select name="removeselect[]" size="20" id="removeselect" multiple="multiple"
onfocus="document.getElementById('assignform').add.disabled=true;
document.getElementById('assignform').remove.disabled=false;
document.getElementById('assignform').addselect.selectedIndex=-1;">
<?php echo $currentmembersoptions ?>
</select></div></td>
<td id="buttonscell">
<p class="arrow_button">
<input class="btn btn-secondary" name="add" id="add" type="submit"
value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>"
title="<?php print_string('add'); ?>" /><br>
<input class="btn btn-secondary" name="remove" id="remove" type="submit"
value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>"
title="<?php print_string('remove'); ?>" />
</p>
</td>
<td id="potentialcell">
<label for="addselect"><?php print_string('potentialmembers', 'group', $potentialmemberscount); ?></label>
<div class="userselector" id="addselect_wrapper">
<select name="addselect[]" size="20" id="addselect" multiple="multiple"
onfocus="document.getElementById('assignform').add.disabled=false;
document.getElementById('assignform').remove.disabled=true;
document.getElementById('assignform').removeselect.selectedIndex=-1;">
<?php echo $potentialmembersoptions ?>
</select>
</div>
</td>
</tr>
<tr><td colspan="3" id="backcell">
<input class="btn btn-secondary" type="submit" name="cancel"
value="<?php print_string('backtogroupings', 'group'); ?>" />
</td></tr>
</table>
</div>
</form>
</div>
<?php
echo $OUTPUT->footer();
+285
View File
@@ -0,0 +1,285 @@
<?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/>.
/**
* Create and allocate users to groups
*
* @package core_group
* @copyright Matt Clarkson mattc@catalyst.net.nz
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once('lib.php');
require_once('autogroup_form.php');
if (!defined('AUTOGROUP_MIN_RATIO')) {
define('AUTOGROUP_MIN_RATIO', 0.7); // means minimum member count is 70% in the smallest group
}
$courseid = required_param('courseid', PARAM_INT);
$PAGE->set_url('/group/autogroup.php', array('courseid' => $courseid));
if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
throw new \moodle_exception('invalidcourseid');
}
// Make sure that the user has permissions to manage groups.
require_login($course);
$context = context_course::instance($courseid);
require_capability('moodle/course:managegroups', $context);
$returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id;
$strgroups = get_string('groups');
$strparticipants = get_string('participants');
$strautocreategroups = get_string('autocreategroups', 'group');
$PAGE->set_title($strgroups);
$PAGE->set_heading($course->fullname. ': '.$strgroups);
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $courseid)));
// Print the page and form
$preview = '';
$error = '';
/// Get applicable roles - used in menus etc later on
$rolenames = role_fix_names(get_profile_roles($context), $context, ROLENAME_BOTH, true);
/// Create the form
$editform = new autogroup_form(null, array('roles' => $rolenames));
$editform->set_data(array('courseid' => $courseid, 'seed' => time()));
/// Handle form submission
if ($editform->is_cancelled()) {
redirect($returnurl);
} elseif ($data = $editform->get_data()) {
/// Allocate members from the selected role to groups
switch ($data->allocateby) {
case 'no':
case 'random':
case 'lastname':
$orderby = 'lastname, firstname'; break;
case 'firstname':
$orderby = 'firstname, lastname'; break;
case 'idnumber':
$orderby = 'idnumber'; break;
default:
throw new \moodle_exception('unknoworder');
}
$source = array();
if ($data->cohortid) {
$source['cohortid'] = $data->cohortid;
}
if ($data->groupingid) {
$source['groupingid'] = $data->groupingid;
}
if ($data->groupid) {
$source['groupid'] = $data->groupid;
}
// Display only active users if the option was selected or they do not have the capability to view suspended users.
$onlyactive = !empty($data->includeonlyactiveenrol) || !has_capability('moodle/course:viewsuspendedusers', $context);
// TODO Does not support custom user profile fields (MDL-70456).
$extrafields = \core_user\fields::get_identity_fields($context, false);
$users = groups_get_potential_members($data->courseid, $data->roleid, $source, $orderby, !empty($data->notingroup),
$onlyactive, $extrafields);
$usercnt = count($users);
if ($data->allocateby == 'random') {
srand($data->seed);
shuffle($users);
}
$groups = array();
// Plan the allocation
if ($data->groupby == 'groups') {
$numgrps = $data->number;
$userpergrp = floor($usercnt/$numgrps);
} else { // members
$numgrps = ceil($usercnt/$data->number);
$userpergrp = $data->number;
if (!empty($data->nosmallgroups) and $usercnt % $data->number != 0) {
// If there would be one group with a small number of member reduce the number of groups
$missing = $userpergrp * $numgrps - $usercnt;
if ($missing > $userpergrp * (1-AUTOGROUP_MIN_RATIO)) {
// spread the users from the last small group
$numgrps--;
$userpergrp = floor($usercnt/$numgrps);
}
}
}
// allocate the users - all groups equal count first
for ($i=0; $i<$numgrps; $i++) {
$groups[$i] = array();
$groups[$i]['name'] = groups_parse_name(trim($data->namingscheme), $i);
$groups[$i]['members'] = array();
if ($data->allocateby == 'no') {
continue; // do not allocate users
}
for ($j=0; $j<$userpergrp; $j++) {
if (empty($users)) {
break 2;
}
$user = array_shift($users);
$groups[$i]['members'][$user->id] = $user;
}
}
// now distribute the rest
if ($data->allocateby != 'no') {
for ($i=0; $i<$numgrps; $i++) {
if (empty($users)) {
break 1;
}
$user = array_shift($users);
$groups[$i]['members'][$user->id] = $user;
}
}
if (isset($data->preview)) {
$table = new html_table();
if ($data->allocateby == 'no') {
$table->head = array(get_string('groupscount', 'group', $numgrps));
$table->size = array('100%');
$table->align = array('left');
$table->width = '40%';
} else {
$table->head = array(get_string('groupscount', 'group', $numgrps), get_string('groupmembers', 'group'), get_string('usercounttotal', 'group', $usercnt));
$table->size = array('20%', '70%', '10%');
$table->align = array('left', 'left', 'center');
$table->width = '90%';
}
$table->data = array();
$viewfullnames = has_capability('moodle/site:viewfullnames', $context);
foreach ($groups as $group) {
$line = array();
if (groups_get_group_by_name($courseid, $group['name'])) {
$line[] = '<span class="notifyproblem">'.get_string('groupnameexists', 'group', $group['name']).'</span>';
$error = get_string('groupnameexists', 'group', $group['name']);
} else {
$line[] = $group['name'];
}
if ($data->allocateby != 'no') {
$unames = array();
foreach ($group['members'] as $user) {
$fullname = fullname($user, $viewfullnames);
if ($extrafields) {
$extrafieldsdisplay = [];
foreach ($extrafields as $field) {
$extrafieldsdisplay[] = s($user->{$field});
}
$fullname .= ' (' . implode(', ', $extrafieldsdisplay) . ')';
}
$unames[] = $fullname;
}
$line[] = implode(', ', $unames);
$line[] = count($group['members']);
}
$table->data[] = $line;
}
$preview .= html_writer::table($table);
} else {
$grouping = null;
$createdgrouping = null;
$createdgroups = array();
$failed = false;
// prepare grouping
if (!empty($data->grouping)) {
if ($data->grouping < 0) {
$grouping = new stdClass();
$grouping->courseid = $COURSE->id;
$grouping->name = trim($data->groupingname);
$grouping->id = groups_create_grouping($grouping);
$createdgrouping = $grouping->id;
} else {
$grouping = groups_get_grouping($data->grouping);
}
}
// Save the groups data
foreach ($groups as $key=>$group) {
if (groups_get_group_by_name($courseid, $group['name'])) {
$error = get_string('groupnameexists', 'group', $group['name']);
$failed = true;
break;
}
$newgroup = new stdClass();
$newgroup->courseid = $data->courseid;
$newgroup->name = $group['name'];
$newgroup->enablemessaging = $data->enablemessaging ?? 0;
$newgroup->visibility = GROUPS_VISIBILITY_ALL;
$groupid = groups_create_group($newgroup);
$createdgroups[] = $groupid;
foreach($group['members'] as $user) {
groups_add_member($groupid, $user->id);
}
if ($grouping) {
// Ask this function not to invalidate the cache, we'll do that manually once at the end.
groups_assign_grouping($grouping->id, $groupid, null, false);
}
}
// Invalidate the course groups cache seeing as we've changed it.
cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid));
if ($failed) {
foreach ($createdgroups as $groupid) {
groups_delete_group($groupid);
}
if ($createdgrouping) {
groups_delete_grouping($createdgrouping);
}
} else {
redirect($returnurl);
}
}
}
$PAGE->navbar->add($strparticipants, new moodle_url('/user/index.php', array('id'=>$courseid)));
$PAGE->navbar->add($strgroups, new moodle_url('/group/index.php', array('id'=>$courseid)));
$PAGE->navbar->add($strautocreategroups);
echo $OUTPUT->header();
echo $OUTPUT->heading($strautocreategroups);
if ($error != '') {
echo $OUTPUT->notification($error);
}
/// Display the form
$editform->display();
if($preview !== '') {
echo $OUTPUT->heading(get_string('groupspreview', 'group'));
echo $preview;
}
echo $OUTPUT->footer();
+255
View File
@@ -0,0 +1,255 @@
<?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/>.
/**
* Auto group form
*
* @package core_group
* @copyright 2007 mattc-catalyst (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
require_once($CFG->dirroot.'/lib/formslib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
/**
* Auto group form class
*
* @package core_group
* @copyright 2007 mattc-catalyst (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class autogroup_form extends moodleform {
/**
* Form Definition
*/
function definition() {
global $USER, $COURSE;
$coursecontext = context_course::instance($COURSE->id);
$mform =& $this->_form;
$mform->addElement('header', 'autogroup', get_string('general'));
$mform->addElement('text', 'namingscheme', get_string('namingscheme', 'group'));
$mform->addHelpButton('namingscheme', 'namingscheme', 'group');
$mform->addRule('namingscheme', get_string('required'), 'required', null, 'client');
$mform->setType('namingscheme', PARAM_TEXT);
// There must not be duplicate group names in course.
$template = get_string('grouptemplate', 'group');
$gname = groups_parse_name($template, 0);
if (!groups_get_group_by_name($COURSE->id, $gname)) {
$mform->setDefault('namingscheme', $template);
}
$options = array('groups' => get_string('numgroups', 'group'),
'members' => get_string('nummembers', 'group'));
$mform->addElement('select', 'groupby', get_string('groupby', 'group'), $options);
$mform->addElement('text', 'number', get_string('number', 'group'),'maxlength="4" size="4"');
$mform->setType('number', PARAM_INT);
$mform->addRule('number', null, 'numeric', null, 'client');
$mform->addRule('number', get_string('required'), 'required', null, 'client');
// Enable group messaging for the groups to be auto-created.
if (\core_message\api::can_create_group_conversation($USER->id, $coursecontext)) {
$mform->addElement('selectyesno', 'enablemessaging', get_string('enablemessaging', 'group'));
$mform->addHelpButton('enablemessaging', 'enablemessaging', 'group');
}
$mform->addElement('header', 'groupmembershdr', get_string('groupmembers', 'group'));
$mform->setExpanded('groupmembershdr', true);
$options = array(0=>get_string('all'));
$options += $this->_customdata['roles'];
$mform->addElement('select', 'roleid', get_string('selectfromrole', 'group'), $options);
$student = get_archetype_roles('student');
$student = reset($student);
if ($student and array_key_exists($student->id, $options)) {
$mform->setDefault('roleid', $student->id);
}
$coursecontext = context_course::instance($COURSE->id);
if ($cohorts = cohort_get_available_cohorts($coursecontext, COHORT_WITH_ENROLLED_MEMBERS_ONLY, 0, 0)) {
$options = array(0 => get_string('anycohort', 'cohort'));
foreach ($cohorts as $c) {
$options[$c->id] = format_string($c->name, true, [
'context' => context::instance_by_id($c->contextid),
]);
}
$mform->addElement('select', 'cohortid', get_string('selectfromcohort', 'cohort'), $options);
$mform->setDefault('cohortid', '0');
} else {
$mform->addElement('hidden','cohortid');
$mform->setType('cohortid', PARAM_INT);
$mform->setConstant('cohortid', '0');
}
if ($groupings = groups_get_all_groupings($COURSE->id)) {
$options = array();
$options[0] = get_string('none');
foreach ($groupings as $grouping) {
$options[$grouping->id] = format_string($grouping->name);
}
$mform->addElement('select', 'groupingid', get_string('selectfromgrouping', 'group'), $options);
$mform->setDefault('groupingid', 0);
$mform->disabledIf('groupingid', 'notingroup', 'checked');
} else {
$mform->addElement('hidden', 'groupingid');
$mform->setType('groupingid', PARAM_INT);
$mform->setConstant('groupingid', 0);
}
if ($groups = groups_get_all_groups($COURSE->id)) {
$options = array();
$options[0] = get_string('none');
foreach ($groups as $group) {
$options[$group->id] = format_string($group->name);
}
$mform->addElement('select', 'groupid', get_string('selectfromgroup', 'group'), $options);
$mform->setDefault('groupid', 0);
$mform->disabledIf('groupid', 'notingroup', 'checked');
} else {
$mform->addElement('hidden', 'groupid');
$mform->setType('groupid', PARAM_INT);
$mform->setConstant('groupid', 0);
}
$options = array('no' => get_string('noallocation', 'group'),
'random' => get_string('random', 'group'),
'firstname' => get_string('byfirstname', 'group'),
'lastname' => get_string('bylastname', 'group'),
'idnumber' => get_string('byidnumber', 'group'));
$mform->addElement('select', 'allocateby', get_string('allocateby', 'group'), $options);
$mform->setDefault('allocateby', 'random');
$mform->addElement('checkbox', 'nosmallgroups', get_string('nosmallgroups', 'group'));
$mform->disabledIf('nosmallgroups', 'groupby', 'noteq', 'members');
$mform->addElement('checkbox', 'notingroup', get_string('notingroup', 'group'));
$mform->disabledIf('notingroup', 'groupingid', 'neq', 0);
$mform->disabledIf('notingroup', 'groupid', 'neq', 0);
if (has_capability('moodle/course:viewsuspendedusers', $coursecontext)) {
$mform->addElement('checkbox', 'includeonlyactiveenrol', get_string('includeonlyactiveenrol', 'group'), '');
$mform->addHelpButton('includeonlyactiveenrol', 'includeonlyactiveenrol', 'group');
$mform->setDefault('includeonlyactiveenrol', true);
}
$mform->addElement('header', 'groupinghdr', get_string('grouping', 'group'));
$options = array('0' => get_string('nogrouping', 'group'),
'-1'=> get_string('newgrouping', 'group'));
if ($groupings = groups_get_all_groupings($COURSE->id)) {
foreach ($groupings as $grouping) {
$options[$grouping->id] = strip_tags(format_string($grouping->name));
}
}
$mform->addElement('select', 'grouping', get_string('createingrouping', 'group'), $options);
if ($groupings) {
$mform->setDefault('grouping', '-1');
}
$mform->addElement('text', 'groupingname', get_string('groupingname', 'group'), $options);
$mform->setType('groupingname', PARAM_TEXT);
$mform->disabledIf('groupingname', 'grouping', 'noteq', '-1');
$mform->addElement('hidden','courseid');
$mform->setType('courseid', PARAM_INT);
$mform->addElement('hidden','seed');
$mform->setType('seed', PARAM_INT);
$buttonarray = array();
$buttonarray[] = &$mform->createElement('submit', 'preview', get_string('preview'));
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('submit'));
$buttonarray[] = &$mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
}
/**
* Performs validation of the form information
*
* @param array $data
* @param array $files
* @return array $errors An array of $errors
*/
function validation($data, $files) {
global $CFG, $COURSE;
$errors = parent::validation($data, $files);
if ($data['allocateby'] != 'no') {
$source = array();
if ($data['cohortid']) {
$source['cohortid'] = $data['cohortid'];
}
if ($data['groupingid']) {
$source['groupingid'] = $data['groupingid'];
}
if ($data['groupid']) {
$source['groupid'] = $data['groupid'];
}
if (!$users = groups_get_potential_members($data['courseid'], $data['roleid'], $source)) {
$errors['roleid'] = get_string('nousersinrole', 'group');
}
/// Check the number entered is sane
if ($data['groupby'] == 'groups') {
$usercnt = count($users);
if ($data['number'] > $usercnt || $data['number'] < 1) {
$errors['number'] = get_string('toomanygroups', 'group', $usercnt);
}
}
}
//try to detect group name duplicates
$name = groups_parse_name(trim($data['namingscheme']), 0);
if (groups_get_group_by_name($COURSE->id, $name)) {
$errors['namingscheme'] = get_string('groupnameexists', 'group', $name);
}
// check grouping name duplicates
if ( isset($data['grouping']) && $data['grouping'] == '-1') {
$name = trim($data['groupingname']);
if (empty($name)) {
$errors['groupingname'] = get_string('required');
} else if (groups_get_grouping_by_name($COURSE->id, $name)) {
$errors['groupingname'] = get_string('groupingnameexists', 'group', $name);
}
}
/// Check the naming scheme
if ($data['groupby'] == 'groups' and $data['number'] == 1) {
// we can use the name as is because there will be only one group max
} else {
$matchcnt = preg_match_all('/[#@]{1,1}/', $data['namingscheme'], $matches);
if ($matchcnt != 1) {
$errors['namingscheme'] = get_string('badnamingscheme', 'group');
}
}
return $errors;
}
}
+189
View File
@@ -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_group\customfield;
use context;
use context_course;
use context_system;
use core_customfield\api;
use core_customfield\handler;
use core_customfield\field_controller;
use moodle_url;
use restore_task;
/**
* Group handler for custom fields.
*
* @package core_group
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class group_handler extends handler {
/**
* @var group_handler
*/
static protected $singleton;
/**
* Returns a singleton.
*
* @param int $itemid
* @return \core_customfield\handler
*/
public static function create(int $itemid = 0): handler {
if (static::$singleton === null) {
self::$singleton = new static(0);
}
return self::$singleton;
}
/**
* Run reset code after unit tests to reset the singleton usage.
*/
public static function reset_caches(): void {
if (!PHPUNIT_TEST) {
throw new \coding_exception('This feature is only intended for use in unit tests');
}
static::$singleton = null;
}
/**
* The current user can configure custom fields on this component.
*
* @return bool true if the current can configure custom fields, false otherwise
*/
public function can_configure(): bool {
return has_capability('moodle/group:configurecustomfields', $this->get_configuration_context());
}
/**
* The current user can edit custom fields on the given group.
*
* @param field_controller $field
* @param int $instanceid id of the group to test edit permission
* @return bool true if the current can edit custom field, false otherwise
*/
public function can_edit(field_controller $field, int $instanceid = 0): bool {
return has_capability('moodle/course:managegroups', $this->get_instance_context($instanceid));
}
/**
* The current user can view custom fields on the given group.
*
* @param field_controller $field
* @param int $instanceid id of the group to test edit permission
* @return bool true if the current can view custom field, false otherwise
*/
public function can_view(field_controller $field, int $instanceid): bool {
return has_any_capability(['moodle/course:managegroups', 'moodle/course:view'], $this->get_instance_context($instanceid));
}
/**
* Context that should be used for new categories created by this handler.
*
* @return context the context for configuration
*/
public function get_configuration_context(): context {
return context_system::instance();
}
/**
* URL for configuration of the fields on this handler.
*
* @return moodle_url The URL to configure custom fields for this component
*/
public function get_configuration_url(): moodle_url {
return new moodle_url('/group/customfield.php');
}
/**
* Returns the context for the data associated with the given instanceid.
*
* @param int $instanceid id of the record to get the context for
* @return context the context for the given record
*/
public function get_instance_context(int $instanceid = 0): \context {
global $COURSE, $DB;
if ($instanceid > 0) {
$group = $DB->get_record('groups', ['id' => $instanceid], '*', MUST_EXIST);
return context_course::instance($group->courseid);
} else if (!empty($COURSE->id)) {
return context_course::instance($COURSE->id);
} else {
return context_system::instance();
}
}
/**
* Get raw data associated with all fields current user can view or edit
*
* @param int $instanceid
* @return array
*/
public function get_instance_data_for_backup(int $instanceid): array {
$finalfields = [];
$instancedata = $this->get_instance_data($instanceid, true);
foreach ($instancedata as $data) {
if ($data->get('id') && $this->can_backup($data->get_field(), $instanceid)) {
$finalfields[] = [
'id' => $data->get('id'),
'shortname' => $data->get_field()->get('shortname'),
'type' => $data->get_field()->get('type'),
'value' => $data->get_value(),
'valueformat' => $data->get('valueformat'),
'valuetrust' => $data->get('valuetrust'),
'groupid' => $data->get('instanceid'),
];
}
}
return $finalfields;
}
/**
* Creates or updates custom field data.
*
* @param restore_task $task
* @param array $data
*
* @return int|void Conditionally returns the ID of the created or updated record.
*/
public function restore_instance_data_from_backup(restore_task $task, array $data) {
$instanceid = $data['groupid'];
$context = $this->get_instance_context($instanceid);
$editablefields = $this->get_editable_fields($instanceid);
$records = api::get_instance_fields_data($editablefields, $instanceid);
foreach ($records as $d) {
$field = $d->get_field();
if ($field->get('shortname') === $data['shortname'] && $field->get('type') === $data['type']) {
if (!$d->get('id')) {
$d->set($d->datafield(), $data['value']);
$d->set('value', $data['value']);
$d->set('valueformat', $data['valueformat']);
$d->set('valuetrust', !empty($data['valuetrust']));
$d->set('contextid', $context->id);
$d->save();
}
return $d->get('id');
}
}
}
}
@@ -0,0 +1,190 @@
<?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_group\customfield;
use context;
use context_course;
use context_system;
use core_customfield\api;
use core_customfield\handler;
use core_customfield\field_controller;
use moodle_url;
use restore_task;
/**
* Grouping handler for custom fields.
*
* @package core_group
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grouping_handler extends handler {
/**
* @var grouping_handler
*/
static protected $singleton;
/**
* Returns a singleton.
*
* @param int $itemid
* @return \core_customfield\handler
*/
public static function create(int $itemid = 0): handler {
if (static::$singleton === null) {
self::$singleton = new static(0);
}
return self::$singleton;
}
/**
* Run reset code after unit tests to reset the singleton usage.
*/
public static function reset_caches(): void {
if (!PHPUNIT_TEST) {
throw new \coding_exception('This feature is only intended for use in unit tests');
}
static::$singleton = null;
}
/**
* The current user can configure custom fields on this component.
*
* @return bool true if the current can configure custom fields, false otherwise
*/
public function can_configure(): bool {
return has_capability('moodle/group:configurecustomfields', $this->get_configuration_context());
}
/**
* The current user can edit custom fields on the given group.
*
* @param field_controller $field
* @param int $instanceid id of the group to test edit permission
* @return bool true if the current can edit custom field, false otherwise
*/
public function can_edit(field_controller $field, int $instanceid = 0): bool {
return has_capability('moodle/course:managegroups', $this->get_instance_context($instanceid));
}
/**
* The current user can view custom fields on the given group.
*
* @param field_controller $field
* @param int $instanceid id of the group to test edit permission
* @return bool true if the current can view custom field, false otherwise
*/
public function can_view(field_controller $field, int $instanceid): bool {
return has_any_capability(['moodle/course:managegroups', 'moodle/course:view'], $this->get_instance_context($instanceid));
}
/**
* Context that should be used for new categories created by this handler.
*
* @return context the context for configuration
*/
public function get_configuration_context(): context {
return context_system::instance();
}
/**
* URL for configuration of the fields on this handler.
*
* @return moodle_url The URL to configure custom fields for this component
*/
public function get_configuration_url(): moodle_url {
return new moodle_url('/group/grouping_customfield.php');
}
/**
* Returns the context for the data associated with the given instanceid.
*
* @param int $instanceid id of the record to get the context for
* @return context the context for the given record
*/
public function get_instance_context(int $instanceid = 0): context {
global $COURSE, $DB;
if ($instanceid > 0) {
$grouping = $DB->get_record('groupings', ['id' => $instanceid], '*', MUST_EXIST);
return context_course::instance($grouping->courseid);
} else if (!empty($COURSE->id)) {
return context_course::instance($COURSE->id);
} else {
return context_system::instance();
}
}
/**
* Get raw data associated with all fields current user can view or edit
*
* @param int $instanceid
* @return array
*/
public function get_instance_data_for_backup(int $instanceid): array {
$finalfields = [];
$instancedata = $this->get_instance_data($instanceid, true);
foreach ($instancedata as $data) {
if ($data->get('id') && $this->can_backup($data->get_field(), $instanceid)) {
$finalfields[] = [
'id' => $data->get('id'),
'shortname' => $data->get_field()->get('shortname'),
'type' => $data->get_field()->get('type'),
'value' => $data->get_value(),
'valueformat' => $data->get('valueformat'),
'valuetrust' => $data->get('valuetrust'),
'groupingid' => $data->get('instanceid'),
];
}
}
return $finalfields;
}
/**
* Creates or updates custom field data for a instanceid from backup data.
*
* The handlers have to override it if they support backup
*
* @param restore_task $task
* @param array $data
*
* @return int|void Conditionally returns the ID of the created or updated record.
*/
public function restore_instance_data_from_backup(restore_task $task, array $data) {
$instanceid = $data['groupingid'];
$context = $this->get_instance_context($instanceid);
$editablefields = $this->get_editable_fields($instanceid);
$records = api::get_instance_fields_data($editablefields, $instanceid);
foreach ($records as $d) {
$field = $d->get_field();
if ($field->get('shortname') === $data['shortname'] && $field->get('type') === $data['type']) {
if (!$d->get('id')) {
$d->set($d->datafield(), $data['value']);
$d->set('value', $data['value']);
$d->set('valueformat', $data['valueformat']);
$d->set('valuetrust', !empty($data['valuetrust']));
$d->set('contextid', $context->id);
$d->save();
}
return $d->get('id');
}
}
}
}
+150
View File
@@ -0,0 +1,150 @@
<?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_group\external;
use context_course;
use core_external\external_api;
use core_external\external_description;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_grades\external\coding_exception;
use core_grades\external\invalid_parameter_exception;
use core_grades\external\moodle_exception;
use core_grades\external\restricted_context_exception;
use moodle_url;
/**
* External group name and image API implementation
*
* @package core_group
* @copyright 2022 Mathew May <mathew.solutions>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_groups_for_selector extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters (
[
'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED),
]
);
}
/**
* Given a course ID find the existing user groups and map some fields to the returned array of group objects.
*
* @param int $courseid
* @return array Groups and warnings to pass back to the calling widget.
*/
public static function execute(int $courseid): array {
global $DB, $USER, $OUTPUT;
$params = self::validate_parameters(
self::execute_parameters(),
[
'courseid' => $courseid,
]
);
$warnings = [];
$context = context_course::instance($params['courseid']);
parent::validate_context($context);
$mappedgroups = [];
$course = $DB->get_record('course', ['id' => $params['courseid']]);
// Initialise the grade tracking object.
if ($groupmode = $course->groupmode) {
$aag = has_capability('moodle/site:accessallgroups', $context);
$usergroups = [];
$groupuserid = 0;
if ($groupmode == VISIBLEGROUPS || $aag) {
// Get user's own groups and put to the top.
$usergroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid);
} else {
$groupuserid = $USER->id;
}
$allowedgroups = groups_get_all_groups($course->id, $groupuserid, $course->defaultgroupingid);
$allgroups = array_merge($allowedgroups, $usergroups);
// Filter out any duplicate groups.
$groupsmenu = array_intersect_key($allgroups, array_unique(array_column($allgroups, 'name')));
if (!$allowedgroups || $groupmode == VISIBLEGROUPS || $aag) {
array_unshift($groupsmenu, (object) [
'id' => 0,
'name' => get_string('allparticipants'),
]);
}
$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');
} else { // All participants.
$picture = $OUTPUT->image_url('g/g1');
}
return (object) [
'id' => $group->id,
'name' => format_string($group->name, true, ['context' => $context]),
'groupimageurl' => $picture->out(false),
];
}, $groupsmenu);
}
return [
'groups' => $mappedgroups,
'warnings' => $warnings,
];
}
/**
* Returns description of what the group search for the widget should return.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'groups' => new external_multiple_structure(self::group_description()),
'warnings' => new external_warnings(),
]);
}
/**
* Create group return value description.
*
* @return external_description
*/
public static function group_description(): external_description {
$groupfields = [
'id' => new external_value(PARAM_ALPHANUM, 'An ID for the group', VALUE_REQUIRED),
'name' => new external_value(PARAM_TEXT, 'The full name of the group', VALUE_REQUIRED),
'groupimageurl' => new external_value(PARAM_URL, 'Group image URL', VALUE_OPTIONAL),
];
return new external_single_structure($groupfields);
}
}
@@ -0,0 +1,41 @@
<?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_group\hook;
use stdClass;
/**
* Hook after group creation.
*
* @package core_group
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions after a group is created.')]
#[\core\attribute\tags('group')]
class after_group_created {
/**
* Constructor for the hook.
*
* @param stdClass $groupinstance The group instance.
*/
public function __construct(
/** @var stdClass The group instance */
public readonly stdClass $groupinstance,
) {
}
}
@@ -0,0 +1,41 @@
<?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_group\hook;
use stdClass;
/**
* Hook after group deletion.
*
* @package core_group
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions after a group is deleted.')]
#[\core\attribute\tags('group')]
class after_group_deleted {
/**
* Constructor for the hook.
*
* @param stdClass $groupinstance The group instance.
*/
public function __construct(
/** @var stdClass The group instance */
public readonly stdClass $groupinstance,
) {
}
}
@@ -0,0 +1,44 @@
<?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_group\hook;
use stdClass;
/**
* Hook after a member added to the group.
*
* @package core_group
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions after members added to the group.')]
#[\core\attribute\tags('group', 'user')]
class after_group_membership_added {
/**
* Constructor for the hook.
*
* @param stdClass $groupinstance The group instance.
* @param array $userids The user ids.
*/
public function __construct(
/** @var stdClass The group instance */
public readonly stdClass $groupinstance,
/** @var array The user ids */
public readonly array $userids,
) {
}
}
@@ -0,0 +1,44 @@
<?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_group\hook;
use stdClass;
/**
* Hook after a member removed from the group.
*
* @package core_group
* @copyright 2024 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions after members removed from the group.')]
#[\core\attribute\tags('group', 'user')]
class after_group_membership_removed {
/**
* Constructor for the hook.
*
* @param stdClass $groupinstance The group instance.
* @param array $userids The user ids.
*/
public function __construct(
/** @var stdClass The group instance */
public readonly stdClass $groupinstance,
/** @var array The user ids */
public readonly array $userids,
) {
}
}
@@ -0,0 +1,41 @@
<?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_group\hook;
use stdClass;
/**
* Hook after group updates.
*
* @package core_group
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions after a group is updated.')]
#[\core\attribute\tags('group')]
class after_group_updated {
/**
* Constructor for the hook.
*
* @param stdClass $groupinstance The group instance.
*/
public function __construct(
/** @var stdClass The group instance */
public readonly stdClass $groupinstance,
) {
}
}
+93
View File
@@ -0,0 +1,93 @@
<?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/>.
/**
* Group details page.
*
* @package core_group
* @copyright 2017 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_group\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use stdClass;
use templatable;
use context_course;
use moodle_url;
/**
* Group details page class.
*
* @package core_group
* @copyright 2017 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class group_details implements renderable, templatable {
/** @var stdClass $group An object with the group information. */
protected $group;
/**
* group_details constructor.
*
* @param int $groupid Group ID to show details of.
*/
public function __construct($groupid) {
$this->group = groups_get_group($groupid, '*', MUST_EXIST);
}
/**
* Export the data.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
if (!empty($this->group->description) || (!empty($this->group->picture))) {
$context = context_course::instance($this->group->courseid);
$description = file_rewrite_pluginfile_urls($this->group->description,
'pluginfile.php',
$context->id,
'group',
'description',
$this->group->id);
$descriptionformat = $this->group->descriptionformat ?? FORMAT_MOODLE;
$options = [
'overflowdiv' => true,
'context' => $context
];
$data = new stdClass();
$data->name = format_string($this->group->name, true, ['context' => $context]);
$data->pictureurl = get_group_picture_url($this->group, $this->group->courseid, true);
$data->description = format_text($description, $descriptionformat, $options);
if (has_capability('moodle/course:managegroups', $context)) {
$url = new moodle_url('/group/group.php', ['id' => $this->group->id, 'courseid' => $this->group->courseid]);
$data->editurl = $url->out(false);
}
return $data;
} else {
return;
}
}
}
+117
View File
@@ -0,0 +1,117 @@
<?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/>.
/**
* Group index page.
*
* @package core_group
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_group\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Group index page class.
*
* @package core_group
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class index_page implements renderable, templatable {
/** @var int $courseid The course ID. */
public $courseid;
/** @var array The array of groups to be rendered. */
public $groups;
/** @var string The name of the currently selected group. */
public $selectedgroupname;
/** @var array The array of group members to be rendered, if a group is selected. */
public $selectedgroupmembers;
/** @var bool Whether to disable the add members/edit group buttons. */
public $disableaddedit;
/** @var bool Whether to disable the delete group button. */
public $disabledelete;
/** @var array Groups that can't be deleted by the user. */
public $undeletablegroups;
/** @var bool Whether to show/hide the messaging setting buttons. */
public $messagingsettingsvisible;
/**
* index_page constructor.
*
* @param int $courseid The course ID.
* @param array $groups The array of groups to be rendered.
* @param string $selectedgroupname The name of the currently selected group.
* @param array $selectedgroupmembers The array of group members to be rendered, if a group is selected.
* @param bool $disableaddedit Whether to disable the add members/edit group buttons.
* @param bool $disabledelete Whether to disable the delete group button.
* @param array $undeletablegroups Groups that can't be deleted by the user.
* @param bool $messagingsettingsvisible If the messaging settings buttons should be visible.
*/
public function __construct($courseid, $groups, $selectedgroupname, $selectedgroupmembers, $disableaddedit, $disabledelete,
$undeletablegroups, $messagingsettingsvisible) {
$this->courseid = $courseid;
$this->groups = $groups;
$this->selectedgroupname = $selectedgroupname;
$this->selectedgroupmembers = $selectedgroupmembers;
$this->disableaddedit = $disableaddedit;
$this->disabledelete = $disabledelete;
$this->undeletablegroups = $undeletablegroups;
$this->messagingsettingsvisible = $messagingsettingsvisible;
}
/**
* Export the data.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $CFG;
$data = new stdClass();
// Variables that will be passed to the JS helper.
$data->courseid = $this->courseid;
$data->wwwroot = $CFG->wwwroot;
// To be passed to the JS init script in the template. Encode as a JSON string.
$data->undeletablegroups = json_encode($this->undeletablegroups);
// Some buttons are enabled if single group selected.
$data->addmembersdisabled = $this->disableaddedit;
$data->editgroupsettingsdisabled = $this->disableaddedit;
$data->deletegroupdisabled = $this->disabledelete;
$data->groups = $this->groups;
$data->members = $this->selectedgroupmembers;
$data->selectedgroup = $this->selectedgroupname;
$data->messagingsettingsvisible = $this->messagingsettingsvisible;
return $data;
}
}
+61
View File
@@ -0,0 +1,61 @@
<?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/>.
/**
* Renderers.
*
* @package core_group
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_group\output;
defined('MOODLE_INTERNAL') || die();
use plugin_renderer_base;
/**
* Renderer class.
*
* @package core_group
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Defer to template.
*
* @param index_page $page
* @return string
*/
public function render_index_page(index_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('core_group/index', $data);
}
/**
* Defer to template.
*
* @param group_details $page Group details page object.
* @return string HTML to render the group details.
*/
public function group_details(group_details $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('core_group/group_details', $data);
}
}
@@ -0,0 +1,180 @@
<?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/>.
/**
* Contains class core_group\output\user_groups_editable
*
* @package core_group
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_group\output;
use context_course;
use core_user;
use core_external;
use coding_exception;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/group/lib.php');
/**
* Class to display list of user groups.
*
* @package core_group
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_groups_editable extends \core\output\inplace_editable {
/** @var $coursegroups */
private $coursegroups = null;
/** @var $context */
private $context = null;
/**
* Constructor.
*
* @param \stdClass $course The current course
* @param \context $context The course context
* @param \stdClass $user The current user
* @param \stdClass[] $coursegroups The list of course groups from groups_get_all_groups with membership.
* @param array $value Array of groupids.
*/
public function __construct($course, $context, $user, $coursegroups, $value) {
// Check capabilities to get editable value.
$editable = has_capability('moodle/course:managegroups', $context) && !empty($coursegroups);
// Invent an itemid.
$itemid = $course->id . ':' . $user->id;
$value = json_encode($value);
// Remember these for the display value.
$this->coursegroups = $coursegroups;
$this->context = $context;
parent::__construct('core_group', 'user_groups', $itemid, $editable, $value, $value);
// Assignable groups.
$options = [];
foreach ($coursegroups as $group) {
$options[$group->id] = format_string($group->name, true, ['context' => $this->context]);
}
$fullname = fullname($user, has_capability('moodle/site:viewfullnames', $this->context));
$fullname = htmlspecialchars($fullname, ENT_QUOTES, 'utf-8');
$this->edithint = get_string('editusersgroupsa', 'group', $fullname);
$this->editlabel = get_string('editusersgroupsa', 'group', $fullname);
$attributes = ['multiple' => true];
$this->set_type_autocomplete($options, $attributes);
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output
* @return array
*/
public function export_for_template(\renderer_base $output) {
$listofgroups = [];
$groupids = json_decode($this->value);
foreach ($groupids as $id) {
$listofgroups[] = format_string($this->coursegroups[$id]->name, true, ['context' => $this->context]);
}
if (!empty($listofgroups)) {
$this->displayvalue = implode(', ', $listofgroups);
} else {
$this->displayvalue = get_string('groupsnone');
}
return parent::export_for_template($output);
}
/**
* Updates the value in database and returns itself, called from inplace_editable callback
*
* @param int $itemid
* @param mixed $newvalue
* @return \self
*/
public static function update($itemid, $newvalue) {
// Check caps.
// Do the thing.
// Return one of me.
// Validate the inputs.
list($courseid, $userid) = explode(':', $itemid, 2);
$courseid = clean_param($courseid, PARAM_INT);
$userid = clean_param($userid, PARAM_INT);
$groupids = json_decode($newvalue);
foreach ($groupids as $index => $groupid) {
$groupids[$index] = clean_param($groupid, PARAM_INT);
}
// Check user is enrolled in the course.
$context = context_course::instance($courseid);
core_external::validate_context($context);
if (!is_enrolled($context, $userid)) {
throw new coding_exception('User does not belong to the course');
}
// Check that all the groups belong to the course.
$coursegroups = groups_get_all_groups($courseid, 0, 0, 'g.*', true);
$byid = [];
foreach ($groupids as $groupid) {
if (!isset($coursegroups[$groupid])) {
throw new coding_exception('Group does not belong to the course');
}
$byid[$groupid] = $groupid;
}
$groupids = $byid;
// Check permissions.
require_capability('moodle/course:managegroups', $context);
// Process adds.
foreach ($groupids as $groupid) {
if (!isset($coursegroups[$groupid]->members[$userid])) {
// Add them.
groups_add_member($groupid, $userid);
// Keep this variable in sync.
$coursegroups[$groupid]->members[$userid] = $userid;
}
}
// Process removals.
foreach ($coursegroups as $groupid => $group) {
if (isset($group->members[$userid]) && !isset($groupids[$groupid])) {
if (groups_remove_member_allowed($groupid, $userid)) {
groups_remove_member($groupid, $userid);
unset($coursegroups[$groupid]->members[$userid]);
} else {
$groupids[$groupid] = $groupid;
}
}
}
$course = get_course($courseid);
$user = core_user::get_user($userid);
return new self($course, $context, $user, $coursegroups, array_values($groupids));
}
}
+400
View File
@@ -0,0 +1,400 @@
<?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/>.
/**
* Privacy Subsystem implementation for core_group.
*
* @package core_group
* @category privacy
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_group\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
/**
* Privacy Subsystem implementation for core_group.
*
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// Groups store user data.
\core_privacy\local\metadata\provider,
// The group subsystem contains user's group memberships.
\core_privacy\local\request\subsystem\provider,
// The group subsystem can provide information to other plugins.
\core_privacy\local\request\subsystem\plugin_provider,
// This plugin is capable of determining which users have data within it.
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\shared_userlist_provider
{
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('groups_members', [
'groupid' => 'privacy:metadata:groups:groupid',
'userid' => 'privacy:metadata:groups:userid',
'timeadded' => 'privacy:metadata:groups:timeadded',
], 'privacy:metadata:groups');
$collection->link_subsystem('core_message', 'privacy:metadata:core_message');
return $collection;
}
/**
* Writes user data to the writer for the user to download.
*
* @param \context $context The context to export data for.
* @param string $component The component that is calling this function. Empty string means no component.
* @param array $subcontext The sub-context in which to export this data.
* @param int $itemid Optional itemid associated with component.
*/
public static function export_groups(\context $context, string $component, array $subcontext = [], int $itemid = 0) {
global $DB, $USER;
if (!$context instanceof \context_course) {
return;
}
$subcontext[] = get_string('groups', 'core_group');
$sql = "SELECT gm.id, gm.timeadded, gm.userid, g.name, gm.groupid
FROM {groups_members} gm
JOIN {groups} g ON gm.groupid = g.id
WHERE g.courseid = :courseid
AND gm.component = :component
AND gm.userid = :userid";
$params = [
'courseid' => $context->instanceid,
'component' => $component,
'userid' => $USER->id
];
if ($itemid) {
$sql .= ' AND gm.itemid = :itemid';
$params['itemid'] = $itemid;
}
$groups = $DB->get_records_sql($sql, $params);
$groupstoexport = array_map(function($group) {
return (object) [
'name' => format_string($group->name),
'timeadded' => transform::datetime($group->timeadded),
];
}, $groups);
if (!empty($groups)) {
\core_privacy\local\request\writer::with_context($context)
->export_data($subcontext, (object) [
'groups' => $groupstoexport,
]);
foreach ($groups as $group) {
// Export associated conversations to this group.
\core_message\privacy\provider::export_conversations($USER->id, 'core_group', 'groups',
$context, [], $group->groupid);
}
}
}
/**
* Deletes all group memberships for a specified context and component.
*
* @param \context $context Details about which context to delete group memberships for.
* @param string $component Component to delete. Empty string means no component (manual group memberships).
* @param int $itemid Optional itemid associated with component.
*/
public static function delete_groups_for_all_users(\context $context, string $component, int $itemid = 0) {
global $DB;
if (!$context instanceof \context_course) {
return;
}
if (!$DB->record_exists('groups', ['courseid' => $context->instanceid])) {
return;
}
$select = "component = :component AND groupid IN (SELECT g.id FROM {groups} g WHERE courseid = :courseid)";
$params = ['component' => $component, 'courseid' => $context->instanceid];
if ($itemid) {
$select .= ' AND itemid = :itemid';
$params['itemid'] = $itemid;
}
// Delete the group conversations.
$groups = $DB->get_records_select('groups_members', $select, $params);
foreach ($groups as $group) {
\core_message\privacy\provider::delete_conversations_for_all_users($context, 'core_group', 'groups', $group->groupid);
}
// Remove members from the group.
$DB->delete_records_select('groups_members', $select, $params);
// Purge the group and grouping cache for users.
\cache_helper::purge_by_definition('core', 'user_group_groupings');
}
/**
* Deletes all records for a user from a list of approved contexts.
*
* @param approved_contextlist $contextlist Contains the user ID and a list of contexts to be deleted from.
* @param string $component Component to delete from. Empty string means no component (manual memberships).
* @param int $itemid Optional itemid associated with component.
*/
public static function delete_groups_for_user(approved_contextlist $contextlist, string $component, int $itemid = 0) {
global $DB;
$userid = $contextlist->get_user()->id;
$contextids = $contextlist->get_contextids();
if (!$contextids) {
return;
}
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$contextparams += ['contextcourse' => CONTEXT_COURSE];
$groupselect = "SELECT g.id
FROM {groups} g
JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse
WHERE ctx.id $contextsql";
if (!$DB->record_exists_sql($groupselect, $contextparams)) {
return;
}
$select = "userid = :userid AND component = :component AND groupid IN ({$groupselect})";
$params = ['userid' => $userid, 'component' => $component] + $contextparams;
if ($itemid) {
$select .= ' AND itemid = :itemid';
$params['itemid'] = $itemid;
}
// Delete the group conversations.
$groups = $DB->get_records_select('groups_members', $select, $params);
foreach ($groups as $group) {
\core_message\privacy\provider::delete_conversations_for_user($contextlist, 'core_group', 'groups', $group->groupid);
}
// Remove members from the group.
$DB->delete_records_select('groups_members', $select, $params);
// Invalidate the group and grouping cache for the user.
\cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), array($userid));
}
/**
* Add the list of users who are members of some groups in the specified constraints.
*
* @param userlist $userlist The userlist to add the users to.
* @param string $component The component to check.
* @param int $itemid Optional itemid associated with component.
*/
public static function get_group_members_in_context(userlist $userlist, string $component, int $itemid = 0) {
$context = $userlist->get_context();
if (!$context instanceof \context_course) {
return;
}
// Group members in the given context.
$sql = "SELECT gm.userid
FROM {groups_members} gm
JOIN {groups} g ON gm.groupid = g.id
WHERE g.courseid = :courseid AND gm.component = :component";
$params = [
'courseid' => $context->instanceid,
'component' => $component
];
if ($itemid) {
$sql .= ' AND gm.itemid = :itemid';
$params['itemid'] = $itemid;
}
$userlist->add_from_sql('userid', $sql, $params);
// Get the users with some group conversation in this context.
\core_message\privacy\provider::add_conversations_in_context($userlist, 'core_group', 'groups', $itemid);
}
/**
* Deletes all records for multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
* @param string $component Component to delete from. Empty string means no component (manual memberships).
* @param int $itemid Optional itemid associated with component.
*/
public static function delete_groups_for_users(approved_userlist $userlist, string $component, int $itemid = 0) {
global $DB;
$context = $userlist->get_context();
$userids = $userlist->get_userids();
if (!$context instanceof \context_course) {
return;
}
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
$groupselect = "SELECT id FROM {groups} WHERE courseid = :courseid";
$groupparams = ['courseid' => $context->instanceid];
$select = "component = :component AND userid {$usersql} AND groupid IN ({$groupselect})";
$params = ['component' => $component] + $groupparams + $userparams;
if ($itemid) {
$select .= ' AND itemid = :itemid';
$params['itemid'] = $itemid;
}
// Delete the group conversations for these users.
$groups = $DB->get_records_select('groups_members', $select, $params);
foreach ($groups as $group) {
\core_message\privacy\provider::delete_conversations_for_users($userlist, 'core_group', 'groups', $group->groupid);
}
$DB->delete_records_select('groups_members', $select, $params);
// Invalidate the group and grouping cache for the user.
\cache_helper::invalidate_by_definition('core', 'user_group_groupings', array(), $userids);
}
/**
* Get the list of contexts that contain group membership for the specified user.
*
* @param int $userid The user to search.
* @param string $component The component to check.
* @param int $itemid Optional itemid associated with component.
* @return contextlist The contextlist containing the list of contexts.
*/
public static function get_contexts_for_group_member(int $userid, string $component, int $itemid = 0) {
$contextlist = new contextlist();
$sql = "SELECT ctx.id
FROM {groups_members} gm
JOIN {groups} g ON gm.groupid = g.id
JOIN {context} ctx ON g.courseid = ctx.instanceid AND ctx.contextlevel = :contextcourse
WHERE gm.userid = :userid AND gm.component = :component";
$params = [
'contextcourse' => CONTEXT_COURSE,
'userid' => $userid,
'component' => $component
];
if ($itemid) {
$sql .= ' AND gm.itemid = :itemid';
$params['itemid'] = $itemid;
}
$contextlist->add_from_sql($sql, $params);
// Get the contexts where the userid has group conversations.
\core_message\privacy\provider::add_contexts_for_conversations($contextlist, $userid, 'core_group', 'groups', $itemid);
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param int $userid The user to search.
* @return contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
return static::get_contexts_for_group_member($userid, '');
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_course) {
return;
}
static::get_group_members_in_context($userlist, '');
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
$contexts = $contextlist->get_contexts();
foreach ($contexts as $context) {
static::export_groups($context, '');
}
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
static::delete_groups_for_all_users($context, '');
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
static::delete_groups_for_user($contextlist, '');
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
static::delete_groups_for_users($userlist, '');
}
}
@@ -0,0 +1,151 @@
<?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/>.
declare(strict_types=1);
namespace core_group\reportbuilder\datasource;
use core_group\reportbuilder\local\entities\{grouping, group, group_member};
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\{course, user};
use core_reportbuilder\local\helpers\database;
/**
* Groups datasource
*
* @package core_group
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groups extends datasource {
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('groups', 'core_group');
}
/**
* Initialise report
*/
protected function initialise(): void {
$courseentity = new course();
$coursealias = $courseentity->get_table_alias('course');
$this->set_main_table('course', $coursealias);
$this->add_entity($courseentity);
$paramsiteid = database::generate_param_name();
$this->add_base_condition_sql("{$coursealias}.id != :{$paramsiteid}", [$paramsiteid => SITEID]);
// Re-use the context table alias/join from the course entity in subsequent entities.
$contextalias = $courseentity->get_table_alias('context');
$this->add_join($courseentity->get_context_join());
// Group entity.
$groupentity = (new group())
->set_table_alias('context', $contextalias);
$groupsalias = $groupentity->get_table_alias('groups');
$this->add_entity($groupentity
->add_join("LEFT JOIN {groups} {$groupsalias} ON {$groupsalias}.courseid = {$coursealias}.id"));
// Grouping entity.
$groupingentity = (new grouping())
->set_table_alias('context', $contextalias);
$groupingsalias = $groupingentity->get_table_alias('groupings');
// Sub-select for all groupings groups.
$groupinginnerselect = "
SELECT gr.*, grg.groupid
FROM {groupings} gr
JOIN {groupings_groups} grg ON grg.groupingid = gr.id";
$this->add_entity($groupingentity
->add_joins($groupentity->get_joins())
->add_join("LEFT JOIN ({$groupinginnerselect}) {$groupingsalias}
ON {$groupingsalias}.courseid = {$coursealias}.id AND {$groupingsalias}.groupid = {$groupsalias}.id"));
// Group member entity.
$groupmemberentity = new group_member();
$groupsmembersalias = $groupmemberentity->get_table_alias('groups_members');
$this->add_entity($groupmemberentity
->add_joins($groupentity->get_joins())
->add_join("LEFT JOIN {groups_members} {$groupsmembersalias} ON {$groupsmembersalias}.groupid = {$groupsalias}.id"));
// User entity.
$userentity = new user();
$useralias = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_joins($groupmemberentity->get_joins())
->add_join("LEFT JOIN {user} {$useralias} ON {$useralias}.id = {$groupsmembersalias}.userid"));
// Add all elements from entities to be available in custom reports.
$this->add_all_from_entities();
}
/**
* Return the columns that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'course:coursefullnamewithlink',
'group:name',
'user:fullname',
];
}
/**
* Return the column sorting that will be added to the report upon creation
*
* @return int[]
*/
public function get_default_column_sorting(): array {
return [
'course:coursefullnamewithlink' => SORT_ASC,
'group:name' => SORT_ASC,
'user:fullname' => SORT_ASC,
];
}
/**
* Return the filters that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_filters(): array {
return [
'course:fullname',
'group:name',
];
}
/**
* Return the conditions that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'course:fullname',
'group:name',
];
}
}
@@ -0,0 +1,343 @@
<?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/>.
declare(strict_types=1);
namespace core_group\reportbuilder\local\entities;
use context_course;
use context_helper;
use html_writer;
use lang_string;
use moodle_url;
use stdClass;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{boolean_select, date, select, text};
use core_reportbuilder\local\helpers\{custom_fields, format};
use core_reportbuilder\local\report\{column, filter};
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->libdir}/grouplib.php");
/**
* Group entity
*
* @package core_group
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class group extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'context',
'groups',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('group', 'core_group');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$groupsalias = $this->get_table_alias('groups');
$customfields = (new custom_fields(
"{$groupsalias}.id",
$this->get_entity_name(),
'core_group',
'group',
))
->add_joins($this->get_joins());
$columns = array_merge($this->get_all_columns(), $customfields->get_columns());
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = array_merge($this->get_all_filters(), $customfields->get_filters());
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
global $DB;
$contextalias = $this->get_table_alias('context');
$groupsalias = $this->get_table_alias('groups');
// Name column.
$columns[] = (new column(
'name',
new lang_string('name'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$groupsalias}.name, {$groupsalias}.courseid")
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->set_is_sortable(true)
->set_callback(static function($name, stdClass $group): string {
if ($name === null) {
return '';
}
context_helper::preload_from_record($group);
$context = context_course::instance($group->courseid);
return format_string($group->name, true, ['context' => $context]);
});
// ID number column.
$columns[] = (new column(
'idnumber',
new lang_string('idnumber'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$groupsalias}.idnumber")
->set_is_sortable(true);
// Description column.
$descriptionfieldsql = "{$groupsalias}.description";
if ($DB->get_dbfamily() === 'oracle') {
$descriptionfieldsql = $DB->sql_order_by_text($descriptionfieldsql, 1024);
}
$columns[] = (new column(
'description',
new lang_string('description'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_LONGTEXT)
->add_field($descriptionfieldsql, 'description')
->add_fields("{$groupsalias}.descriptionformat, {$groupsalias}.id, {$groupsalias}.courseid")
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->set_is_sortable(false)
->set_callback(static function(?string $description, stdClass $group): string {
global $CFG;
if ($description === null) {
return '';
}
require_once("{$CFG->libdir}/filelib.php");
context_helper::preload_from_record($group);
$context = context_course::instance($group->courseid);
$description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $context->id, 'group',
'description', $group->id);
return format_text($description, $group->descriptionformat, ['context' => $context]);
});
// Enrolment key column.
$columns[] = (new column(
'enrolmentkey',
new lang_string('enrolmentkey', 'core_group'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$groupsalias}.enrolmentkey")
->set_is_sortable(true);
// Visibility column.
$columns[] = (new column(
'visibility',
new lang_string('visibilityshort', 'core_group'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_fields("{$groupsalias}.visibility")
->set_is_sortable(true)
// It doesn't make sense to offer integer aggregation methods for this column.
->set_disabled_aggregation(['avg', 'max', 'min', 'sum'])
->set_callback(static function(?int $visibility): string {
if ($visibility === null) {
return '';
}
$options = [
GROUPS_VISIBILITY_ALL => new lang_string('visibilityall', 'core_group'),
GROUPS_VISIBILITY_MEMBERS => new lang_string('visibilitymembers', 'core_group'),
GROUPS_VISIBILITY_OWN => new lang_string('visibilityown', 'core_group'),
GROUPS_VISIBILITY_NONE => new lang_string('visibilitynone', 'core_group'),
];
return (string) ($options[$visibility] ?? $visibility);
});
// Participation column.
$columns[] = (new column(
'participation',
new lang_string('participationshort', 'core_group'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_BOOLEAN)
->add_fields("{$groupsalias}.participation")
->set_is_sortable(true)
->set_callback([format::class, 'boolean_as_text']);
// Picture column.
$columns[] = (new column(
'picture',
new lang_string('picture'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_fields("{$groupsalias}.picture, {$groupsalias}.id, {$contextalias}.id AS contextid")
->set_is_sortable(false)
// It doesn't make sense to offer integer aggregation methods for this column.
->set_disabled_aggregation(['avg', 'max', 'min', 'sum'])
->set_callback(static function ($picture, stdClass $group): string {
if (empty($group->picture)) {
return '';
}
$pictureurl = moodle_url::make_pluginfile_url($group->contextid, 'group', 'icon', $group->id, '/', 'f2');
$pictureurl->param('rev', $group->picture);
return html_writer::img($pictureurl, '');
});
// Time created column.
$columns[] = (new column(
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$groupsalias}.timecreated")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
// Time modified column.
$columns[] = (new column(
'timemodified',
new lang_string('timemodified', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$groupsalias}.timemodified")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$groupsalias = $this->get_table_alias('groups');
// Name filter.
$filters[] = (new filter(
text::class,
'name',
new lang_string('name'),
$this->get_entity_name(),
"{$groupsalias}.name"
))
->add_joins($this->get_joins());
// ID number filter.
$filters[] = (new filter(
text::class,
'idnumber',
new lang_string('idnumber'),
$this->get_entity_name(),
"{$groupsalias}.idnumber"
))
->add_joins($this->get_joins());
// Visibility filter.
$filters[] = (new filter(
select::class,
'visibility',
new lang_string('visibilityshort', 'core_group'),
$this->get_entity_name(),
"{$groupsalias}.visibility"
))
->add_joins($this->get_joins())
->set_options([
GROUPS_VISIBILITY_ALL => new lang_string('visibilityall', 'core_group'),
GROUPS_VISIBILITY_MEMBERS => new lang_string('visibilitymembers', 'core_group'),
GROUPS_VISIBILITY_OWN => new lang_string('visibilityown', 'core_group'),
GROUPS_VISIBILITY_NONE => new lang_string('visibilitynone', 'core_group'),
]);
// Participation filter.
$filters[] = (new filter(
boolean_select::class,
'participation',
new lang_string('participationshort', 'core_group'),
$this->get_entity_name(),
"{$groupsalias}.participation"
))
->add_joins($this->get_joins());
// Time created filter.
$filters[] = (new filter(
date::class,
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name(),
"{$groupsalias}.timecreated"
))
->add_joins($this->get_joins());
return $filters;
}
}
@@ -0,0 +1,132 @@
<?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/>.
declare(strict_types=1);
namespace core_group\reportbuilder\local\entities;
use core_reportbuilder\local\filters\date;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\{column, filter};
/**
* Group member entity
*
* @package core_group
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class group_member extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'groups_members',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('groupmember', 'core_group');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$groupsmembersalias = $this->get_table_alias('groups_members');
// Time added column.
$columns[] = (new column(
'timeadded',
new lang_string('timeadded', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$groupsmembersalias}.timeadded")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
// Component column.
$columns[] = (new column(
'component',
new lang_string('plugin', 'core'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$groupsmembersalias}.component")
->set_is_sortable(true);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$groupsmembersalias = $this->get_table_alias('groups_members');
// Time added filter.
$filters[] = (new filter(
date::class,
'timeadded',
new lang_string('timeadded', 'core_reportbuilder'),
$this->get_entity_name(),
"{$groupsmembersalias}.timeadded"
))
->add_joins($this->get_joins());
return $filters;
}
}
@@ -0,0 +1,237 @@
<?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/>.
declare(strict_types=1);
namespace core_group\reportbuilder\local\entities;
use context_course;
use context_helper;
use lang_string;
use stdClass;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{date, text};
use core_reportbuilder\local\helpers\{custom_fields, format};
use core_reportbuilder\local\report\{column, filter};
/**
* Grouping entity
*
* @package core_group
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grouping extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'context',
'groupings',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('grouping', 'core_group');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$groupingsalias = $this->get_table_alias('groupings');
$customfields = (new custom_fields(
"{$groupingsalias}.id",
$this->get_entity_name(),
'core_group',
'grouping',
))
->add_joins($this->get_joins());
$columns = array_merge($this->get_all_columns(), $customfields->get_columns());
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = array_merge($this->get_all_filters(), $customfields->get_filters());
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
global $DB;
$contextalias = $this->get_table_alias('context');
$groupingsalias = $this->get_table_alias('groupings');
// Name column.
$columns[] = (new column(
'name',
new lang_string('name'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$groupingsalias}.name, {$groupingsalias}.courseid")
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->set_is_sortable(true)
->set_callback(static function($name, stdClass $grouping): string {
if ($name === null) {
return '';
}
context_helper::preload_from_record($grouping);
$context = context_course::instance($grouping->courseid);
return format_string($grouping->name, true, ['context' => $context]);
});
// ID number column.
$columns[] = (new column(
'idnumber',
new lang_string('idnumber'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$groupingsalias}.idnumber")
->set_is_sortable(true);
// Description column.
$descriptionfieldsql = "{$groupingsalias}.description";
if ($DB->get_dbfamily() === 'oracle') {
$descriptionfieldsql = $DB->sql_order_by_text($descriptionfieldsql, 1024);
}
$columns[] = (new column(
'description',
new lang_string('description'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_LONGTEXT)
->add_field($descriptionfieldsql, 'description')
->add_fields("{$groupingsalias}.descriptionformat, {$groupingsalias}.id, {$groupingsalias}.courseid")
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->set_is_sortable(false)
->set_callback(static function(?string $description, stdClass $grouping): string {
global $CFG;
if ($description === null) {
return '';
}
require_once("{$CFG->libdir}/filelib.php");
context_helper::preload_from_record($grouping);
$context = context_course::instance($grouping->courseid);
$description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $context->id, 'grouping',
'description', $grouping->id);
return format_text($description, $grouping->descriptionformat, ['context' => $context]);
});
// Time created column.
$columns[] = (new column(
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$groupingsalias}.timecreated")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
// Time modified column.
$columns[] = (new column(
'timemodified',
new lang_string('timemodified', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$groupingsalias}.timemodified")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$groupingsalias = $this->get_table_alias('groupings');
// Name filter.
$filters[] = (new filter(
text::class,
'name',
new lang_string('name'),
$this->get_entity_name(),
"{$groupingsalias}.name"
))
->add_joins($this->get_joins());
// ID number filter.
$filters[] = (new filter(
text::class,
'idnumber',
new lang_string('idnumber'),
$this->get_entity_name(),
"{$groupingsalias}.idnumber"
))
->add_joins($this->get_joins());
// Time created filter.
$filters[] = (new filter(
date::class,
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name(),
"{$groupingsalias}.timecreated"
))
->add_joins($this->get_joins());
return $filters;
}
}
+181
View File
@@ -0,0 +1,181 @@
<?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/>.
/**
* Group visibility methods
*
* @package core_group
* @copyright 2022 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_group;
/**
* Group visibility methods.
*/
class visibility {
/**
* Store the number groups with visibility other than ALL on the course.
*
* @param int $courseid Course ID to update the cache for.
* @param \cache|null $cache Existing cache instance. If null, once will be created.
* @return void
* @throws \dml_exception
*/
public static function update_hiddengroups_cache(int $courseid, ?\cache $cache = null): void {
global $DB;
if (!$cache) {
$cache = \cache::make('core', 'coursehiddengroups');
}
$hiddengroups = $DB->count_records_select('groups', 'courseid = ? AND visibility != ?',
[$courseid, GROUPS_VISIBILITY_ALL]);
$cache->set($courseid, $hiddengroups);
}
/**
* Return whether a course currently had hidden groups.
*
* This can be used as a shortcut to decide whether visibility restrictions need to be applied. If this returns false,
* we may be able to use cached data, or do a much simpler query.
*
* @param int $courseid
* @return bool
* @throws \coding_exception
* @throws \dml_exception
*/
public static function course_has_hidden_groups(int $courseid): bool {
$cache = \cache::make('core', 'coursehiddengroups');
$hiddengroups = $cache->get($courseid);
if ($hiddengroups === false) {
self::update_hiddengroups_cache($courseid, $cache);
$cache->get($courseid);
}
return $hiddengroups > 0;
}
/**
* Can the current user view all the groups on the course?
*
* Returns true if there are no groups on the course with visibility != ALL,
* or if the user has viewhiddengroups.
*
* This is useful for deciding whether we need to perform additional visibility checkes
* such as the sql_* methods of this class.
*
* @param int $courseid
* @return bool
*/
public static function can_view_all_groups(int $courseid): bool {
$viewhidden = has_capability('moodle/course:viewhiddengroups', \context_course::instance($courseid));
$hashidden = self::course_has_hidden_groups($courseid);
return $viewhidden || !$hashidden;
}
/**
* Return SQL conditions for determining whether a user can see a group and its memberships.
*
* @param int $userid
* @param string $groupsalias The SQL alias being used for the groups table.
* @param string $groupsmembersalias The SQL alias being used for the groups_members table.
* @return array [$where, $params]
*/
public static function sql_group_visibility_where(int $userid,
string $groupsalias = 'g', string $groupsmembersalias = 'gm'): array {
global $USER;
// Apply visibility restrictions.
// Everyone can see who is in groups with ALL visibility.
$where = "({$groupsalias}.visibility = :all";
$params['all'] = GROUPS_VISIBILITY_ALL;
if ($userid == $USER->id) {
// If the user is looking at their own groups, they can see those with MEMBERS or OWN visibility.
$where .= " OR {$groupsalias}.visibility IN (:members, :own)";
$params['members'] = GROUPS_VISIBILITY_MEMBERS;
$params['own'] = GROUPS_VISIBILITY_OWN;
} else {
list($memberssql, $membersparams) = self::sql_members_visibility_condition($groupsalias, $groupsmembersalias);
// If someone else's groups, they can see those with MEMBERS visibilty, only if they are a member too.
$where .= " OR ($memberssql)";
$params = array_merge($params, $membersparams);
}
$where .= ")";
return [$where, $params];
}
/**
* Return SQL conditions for determining whether a user can see a group's members.
*
* @param string $groupsalias The SQL alias being used for the groups table.
* @param string $groupsmembersalias The SQL alias being used for the groups_members table.
* @param string $useralias The SQL alias being used for the user table.
* @param string $paramprefix Prefix for the parameter names.
* @return array [$where, $params]
*/
public static function sql_member_visibility_where(
string $groupsalias = 'g',
string $groupsmembersalias = 'gm',
string $useralias = 'u',
string $paramprefix = '',
): array {
global $USER;
list($memberssql, $membersparams) = self::sql_members_visibility_condition($groupsalias, $groupsmembersalias, $paramprefix);
$where = "(
{$groupsalias}.visibility = :{$paramprefix}all
OR ($memberssql)
OR ({$groupsalias}.visibility = :{$paramprefix}own AND {$useralias}.id = :{$paramprefix}currentuser2)
)";
$params = [
"{$paramprefix}all" => GROUPS_VISIBILITY_ALL,
"{$paramprefix}own" => GROUPS_VISIBILITY_OWN,
"{$paramprefix}currentuser2" => $USER->id,
];
$params = array_merge($params, $membersparams);
return [$where, $params];
}
/**
* Return a condition to check if a user can view a group because it has MEMBERS visibility and they are a member.
*
* @param string $groupsalias The SQL alias being used for the groups table.
* @param string $groupsmembersalias The SQL alias being used for the groups_members table.
* @param string $paramprefix Prefix for the parameter names.
* @return array [$sql, $params]
*/
protected static function sql_members_visibility_condition(
string $groupsalias = 'g',
string $groupsmembersalias = 'gm',
string $paramprefix = '',
): array {
global $USER;
$sql = "{$groupsalias}.visibility = :{$paramprefix}members
AND (
SELECT gm2.id
FROM {groups_members} gm2
WHERE gm2.groupid = {$groupsmembersalias}.groupid
AND gm2.userid = :{$paramprefix}currentuser
) IS NOT NULL";
$params = [
"{$paramprefix}members" => GROUPS_VISIBILITY_MEMBERS,
"{$paramprefix}currentuser" => $USER->id
];
return [$sql, $params];
}
}
+265
View File
@@ -0,0 +1,265 @@
// 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/>.
/**
* Client-side JavaScript for group management interface.
* @copyright vy-shane AT moodle.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
/**
* Class UpdatableGroupsCombo
*/
function UpdatableGroupsCombo(wwwRoot, courseId) {
this.wwwRoot = wwwRoot;
this.courseId = courseId;
this.connectCallback = {
success: function(o) {
if (o.responseText !== undefined) {
var groupsComboEl = document.getElementById("groups");
var membersComboEl = document.getElementById("members");
if (membersComboEl) {
// Clear the members list box.
while (membersComboEl.firstChild) {
membersComboEl.removeChild(membersComboEl.firstChild);
}
}
if (groupsComboEl && o.responseText) {
var groups = eval("("+o.responseText+")");
// Populate the groups list box.
for (var i=0; i<groups.length; i++) {
var optionEl = document.createElement("option");
optionEl.setAttribute("value", groups[i].id);
optionEl.title = groups[i].name;
optionEl.innerHTML = groups[i].name;
groupsComboEl.appendChild(optionEl);
}
}
}
// Remove the loader gif image.
removeLoaderImgs("groupsloader", "groupslabel");
},
failure: function(o) {
removeLoaderImgs("membersloader", "memberslabel");
this.currentTransId = null;
}
};
}
/**
* Class UpdatableMembersCombo
*/
function UpdatableMembersCombo(wwwRoot, courseId) {
this.wwwRoot = wwwRoot;
this.courseId = courseId;
this.connectCallback = {
success: function(t, o) {
if (o.responseText !== undefined) {
var selectEl = document.getElementById("members");
if (selectEl && o.responseText) {
var roles = eval("("+o.responseText+")");
// Clear the members list box.
if (selectEl) {
while (selectEl.firstChild) {
selectEl.removeChild(selectEl.firstChild);
}
}
// Populate the members list box.
for (var i=0; i<roles.length; i++) {
var optgroupEl = document.createElement("optgroup");
optgroupEl.setAttribute("label",roles[i].name);
for(var j=0; j<roles[i].users.length; j++) {
var optionEl = document.createElement("option");
optionEl.setAttribute("value", roles[i].users[j].id);
optionEl.title = roles[i].users[j].name;
optionEl.innerHTML = Y.Escape.html(roles[i].users[j].name);
optgroupEl.appendChild(optionEl);
}
selectEl.appendChild(optgroupEl);
}
}
}
// Remove the loader gif image.
removeLoaderImgs("membersloader", "memberslabel");
},
failure: function() {
removeLoaderImgs("membersloader", "memberslabel");
}
};
// Hide the updatemembers input since AJAX will take care of this.
var updatemembers = Y.one('#updatemembers');
if (updatemembers) {
updatemembers.hide();
}
}
/**
* When a group is selected, we need to update the members.
* The Add/Remove Users button also needs to be disabled/enabled
* depending on whether or not a group is selected
*/
UpdatableMembersCombo.prototype.refreshMembers = function () {
// Get group selector and check selection type
var selectEl = document.getElementById("groups");
var selectionCount=0,groupId=0;
if( selectEl ) {
for (var i = 0; i < selectEl.options.length; i++) {
if(selectEl.options[i].selected) {
selectionCount++;
if(!groupId) {
groupId=selectEl.options[i].value;
}
}
}
}
var singleSelection=selectionCount == 1;
// Add the loader gif image (we only load for single selections)
if(singleSelection) {
createLoaderImg("membersloader", "memberslabel", this.wwwRoot);
}
// Update the label.
var spanEl = document.getElementById("thegroup");
if (singleSelection) {
spanEl.innerHTML = selectEl.options[selectEl.selectedIndex].title;
} else {
spanEl.innerHTML = '&nbsp;';
}
// Clear the members list box.
selectEl = document.getElementById("members");
if (selectEl) {
while (selectEl.firstChild) {
selectEl.removeChild(selectEl.firstChild);
}
}
document.getElementById("showaddmembersform").disabled = !singleSelection;
document.getElementById("showeditgroupsettingsform").disabled = !singleSelection;
document.getElementById("deletegroup").disabled = selectionCount == 0;
if(singleSelection) {
var sUrl = this.wwwRoot + "/group/index.php?id=" + this.courseId + "&group=" + groupId + "&action=ajax_getmembersingroup";
var self = this;
YUI().use('io', function (Y) {
Y.io(sUrl, {
method: 'GET',
context: this,
on: self.connectCallback
});
});
}
};
var createLoaderImg = function (elClass, parentId, wwwRoot) {
var parentEl = document.getElementById(parentId);
if (!parentEl) {
return false;
}
if (document.getElementById("loaderImg")) {
// A loader image already exists.
return false;
}
var loadingImg = document.createElement("img");
loadingImg.setAttribute("src", M.util.image_url('/i/ajaxloader', 'moodle'));
loadingImg.setAttribute("class", elClass);
loadingImg.setAttribute("alt", "Loading");
loadingImg.setAttribute("id", "loaderImg");
parentEl.appendChild(loadingImg);
return true;
};
var removeLoaderImgs = function (elClass, parentId) {
var parentEl = document.getElementById(parentId);
if (parentEl) {
var loader = document.getElementById("loaderImg");
if (loader) {
parentEl.removeChild(loader);
}
}
};
/**
* Updates the current groups information shown about a user when a user is selected.
*
* @global {Array} userSummaries
* userSummaries is added to the page via /user/selector/lib.php - group_non_members_selector::print_user_summaries()
* as a global that can be used by this function.
*/
function updateUserSummary() {
var selectEl = document.getElementById('addselect'),
summaryDiv = document.getElementById('group-usersummary'),
length = selectEl.length,
selectCnt = 0,
selectIdx = -1,
i;
for (i = 0; i < length; i++) {
if (selectEl.options[i].selected) {
selectCnt++;
selectIdx = i;
}
}
if (selectCnt == 1 && userSummaries[selectIdx]) {
summaryDiv.innerHTML = userSummaries[selectIdx];
} else {
summaryDiv.innerHTML = '';
}
return true;
}
function init_add_remove_members_page(Y) {
var add = Y.one('#add');
var addselect = M.core_user.get_user_selector('addselect');
add.set('disabled', addselect.is_selection_empty());
addselect.on('user_selector:selectionchanged', function(isempty) {
add.set('disabled', isempty);
});
var remove = Y.one('#remove');
var removeselect = M.core_user.get_user_selector('removeselect');
remove.set('disabled', removeselect.is_selection_empty());
removeselect.on('user_selector:selectionchanged', function(isempty) {
remove.set('disabled', isempty);
});
addselect = document.getElementById('addselect');
addselect.onchange = updateUserSummary;
}
+41
View File
@@ -0,0 +1,41 @@
<?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/>.
/**
* Manage group custom fields
*
* @package core_group
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_group\customfield\group_handler;
use core_customfield\output\management;
require_once('../config.php');
require_once($CFG->libdir . '/adminlib.php');
admin_externalpage_setup('group_customfield');
$output = $PAGE->get_renderer('core_customfield');
$handler = group_handler::create();
$outputpage = new management($handler);
echo $output->header(),
$output->heading(new lang_string('group_customfield', 'admin')),
$output->render($outputpage),
$output->footer();
+97
View File
@@ -0,0 +1,97 @@
<?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/>.
/**
* Delete group
*
* @package core_group
* @copyright 2008 The Open University, s.marshall AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once('lib.php');
// Get and check parameters
$courseid = required_param('courseid', PARAM_INT);
$groupids = required_param('groups', PARAM_SEQUENCE);
$confirm = optional_param('confirm', 0, PARAM_BOOL);
$PAGE->set_url('/group/delete.php', array('courseid'=>$courseid,'groups'=>$groupids));
$PAGE->set_pagelayout('standard');
// Make sure course is OK and user has access to manage groups
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
throw new \moodle_exception('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/course:managegroups', $context);
$changeidnumber = has_capability('moodle/course:changeidnumber', $context);
// Make sure all groups are OK and belong to course
$groupidarray = explode(',',$groupids);
$groupnames = array();
foreach($groupidarray as $groupid) {
if (!$group = $DB->get_record('groups', array('id' => $groupid))) {
throw new \moodle_exception('invalidgroupid');
}
if (!empty($group->idnumber) && !$changeidnumber) {
throw new \moodle_exception('grouphasidnumber', '', '', $group->name);
}
if ($courseid != $group->courseid) {
throw new \moodle_exception('groupunknown', '', '', $group->courseid);
}
$groupnames[] = format_string($group->name);
}
$returnurl='index.php?id='.$course->id;
if(count($groupidarray)==0) {
throw new \moodle_exception('errorselectsome', 'group', $returnurl);
}
if ($confirm && data_submitted()) {
if (!confirm_sesskey() ) {
throw new \moodle_exception('confirmsesskeybad', 'error', $returnurl);
}
foreach($groupidarray as $groupid) {
groups_delete_group($groupid);
}
redirect($returnurl);
} else {
$PAGE->set_title(get_string('deleteselectedgroup', 'group'));
$PAGE->set_heading($course->fullname . ': '. get_string('deleteselectedgroup', 'group'));
echo $OUTPUT->header();
$optionsyes = array('courseid'=>$courseid, 'groups'=>$groupids, 'sesskey'=>sesskey(), 'confirm'=>1);
$optionsno = array('id'=>$courseid);
if(count($groupnames)==1) {
$message=get_string('deletegroupconfirm', 'group', $groupnames[0]);
} else {
$message=get_string('deletegroupsconfirm', 'group').'<ul>';
foreach($groupnames as $groupname) {
$message.='<li>'.$groupname.'</li>';
}
$message.='</ul>';
}
$formcontinue = new single_button(new moodle_url('delete.php', $optionsyes), get_string('yes'), 'post');
$formcancel = new single_button(new moodle_url('index.php', $optionsno), get_string('no'), 'get');
echo $OUTPUT->confirm($message, $formcontinue, $formcancel);
echo $OUTPUT->footer();
}
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
<?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/>.
/**
* Create group OR edit group settings.
*
* @copyright 2006 The Open University, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once('../config.php');
require_once('lib.php');
require_once('group_form.php');
/// get url variables
$courseid = optional_param('courseid', 0, PARAM_INT);
$id = optional_param('id', 0, PARAM_INT);
$delete = optional_param('delete', 0, PARAM_BOOL);
$confirm = optional_param('confirm', 0, PARAM_BOOL);
// This script used to support group delete, but that has been moved. In case
// anyone still links to it, let's redirect to the new script.
if ($delete) {
debugging('Deleting a group through group/group.php is deprecated and will be removed soon. Please use group/delete.php instead');
redirect(new moodle_url('delete.php', array('courseid' => $courseid, 'groups' => $id)));
}
if ($id) {
if (!$group = $DB->get_record('groups', array('id'=>$id))) {
throw new \moodle_exception('invalidgroupid');
}
if (empty($courseid)) {
$courseid = $group->courseid;
} else if ($courseid != $group->courseid) {
throw new \moodle_exception('invalidcourseid');
}
if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
throw new \moodle_exception('invalidcourseid');
}
} else {
if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
throw new \moodle_exception('invalidcourseid');
}
$group = new stdClass();
$group->courseid = $course->id;
}
if ($id !== 0) {
$PAGE->set_url('/group/group.php', array('id'=>$id));
} else {
$PAGE->set_url('/group/group.php', array('courseid'=>$courseid));
}
require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/course:managegroups', $context);
$strgroups = get_string('groups');
$PAGE->set_title($strgroups);
$PAGE->set_heading($course->fullname . ': '.$strgroups);
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $course->id)));
$returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id.'&group='.$id;
// Prepare the description editor: We do support files for group descriptions
$editoroptions = array('maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$course->maxbytes, 'trust'=>false, 'context'=>$context, 'noclean'=>true);
if (!empty($group->id)) {
$editoroptions['subdirs'] = file_area_contains_subdirs($context, 'group', 'description', $group->id);
$group = file_prepare_standard_editor($group, 'description', $editoroptions, $context, 'group', 'description', $group->id);
} else {
$editoroptions['subdirs'] = false;
$group = file_prepare_standard_editor($group, 'description', $editoroptions, $context, 'group', 'description', null);
}
/// First create the form
$editform = new group_form(null, array('editoroptions' => $editoroptions, 'group' => $group));
$editform->set_data($group);
if ($editform->is_cancelled()) {
redirect($returnurl);
} elseif ($data = $editform->get_data()) {
if (!has_capability('moodle/course:changeidnumber', $context)) {
// Remove the idnumber if the user doesn't have permission to modify it
unset($data->idnumber);
}
if ($data->id) {
groups_update_group($data, $editform, $editoroptions);
} else {
$id = groups_create_group($data, $editform, $editoroptions);
$returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id.'&group='.$id;
}
redirect($returnurl);
}
$strgroups = get_string('groups');
$strparticipants = get_string('participants');
if ($id) {
$strheading = get_string('editgroupsettings', 'group');
} else {
$strheading = get_string('creategroup', 'group');
}
$PAGE->navbar->add($strparticipants, new moodle_url('/user/index.php', array('id'=>$courseid)));
$PAGE->navbar->add($strgroups, new moodle_url('/group/index.php', array('id'=>$courseid)));
$PAGE->navbar->add($strheading);
/// Print header
echo $OUTPUT->header();
echo '<div id="grouppicture">';
if ($id) {
print_group_picture($group, $course->id);
}
echo '</div>';
$editform->display();
echo $OUTPUT->footer();
+240
View File
@@ -0,0 +1,240 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A form for the creation and editing of groups.
*
* @copyright 2006 The Open University, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
defined('MOODLE_INTERNAL') || die;
use core_group\visibility;
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Group form class
*
* @copyright 2006 The Open University, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
class group_form extends moodleform {
/**
* Definition of the form
*/
function definition() {
global $USER, $CFG, $COURSE;
$coursecontext = context_course::instance($COURSE->id);
$mform =& $this->_form;
$editoroptions = $this->_customdata['editoroptions'];
$group = $this->_customdata['group'];
$mform->addElement('header', 'general', get_string('general', 'form'));
$mform->addElement('text','name', get_string('groupname', 'group'),'maxlength="254" size="50"');
$mform->addRule('name', get_string('required'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
$mform->addElement('text','idnumber', get_string('idnumbergroup'), 'maxlength="100" size="10"');
$mform->addHelpButton('idnumber', 'idnumbergroup');
$mform->setType('idnumber', PARAM_RAW);
if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
$mform->hardFreeze('idnumber');
}
$mform->addElement('editor', 'description_editor', get_string('groupdescription', 'group'), null, $editoroptions);
$mform->setType('description_editor', PARAM_RAW);
$mform->addElement('passwordunmask', 'enrolmentkey', get_string('enrolmentkey', 'group'), 'maxlength="254" size="24"', get_string('enrolmentkey', 'group'));
$mform->addHelpButton('enrolmentkey', 'enrolmentkey', 'group');
$mform->setType('enrolmentkey', PARAM_RAW);
$visibilityoptions = [
GROUPS_VISIBILITY_ALL => get_string('visibilityall', 'group'),
GROUPS_VISIBILITY_MEMBERS => get_string('visibilitymembers', 'group'),
GROUPS_VISIBILITY_OWN => get_string('visibilityown', 'group'),
GROUPS_VISIBILITY_NONE => get_string('visibilitynone', 'group')
];
$mform->addElement('select', 'visibility', get_string('visibility', 'group'), $visibilityoptions);
$mform->addHelpButton('visibility', 'visibility', 'group');
$mform->setType('visibility', PARAM_INT);
$mform->addElement('advcheckbox', 'participation', '', get_string('participation', 'group'));
$mform->addHelpButton('participation', 'participation', 'group');
$mform->setType('participation', PARAM_BOOL);
$mform->setDefault('participation', 1);
$mform->hideIf('participation', 'visibility', 'in', [GROUPS_VISIBILITY_OWN, GROUPS_VISIBILITY_NONE]);
// Group conversation messaging.
if (\core_message\api::can_create_group_conversation($USER->id, $coursecontext)) {
$mform->addElement('selectyesno', 'enablemessaging', get_string('enablemessaging', 'group'));
$mform->addHelpButton('enablemessaging', 'enablemessaging', 'group');
$mform->hideIf('enablemessaging', 'visibility', 'in', [GROUPS_VISIBILITY_OWN, GROUPS_VISIBILITY_NONE]);
}
$mform->addElement('static', 'currentpicture', get_string('currentpicture'));
$mform->addElement('checkbox', 'deletepicture', get_string('delete'));
$mform->setDefault('deletepicture', 0);
$mform->addElement('filepicker', 'imagefile', get_string('newpicture', 'group'));
$mform->addHelpButton('imagefile', 'newpicture', 'group');
$handler = \core_group\customfield\group_handler::create();
$handler->instance_form_definition($mform, empty($group->id) ? 0 : $group->id);
$handler->instance_form_before_set_data($group);
$mform->addElement('hidden','id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden','courseid');
$mform->setType('courseid', PARAM_INT);
$this->add_action_buttons();
}
/**
* Extend the form definition after the data has been parsed.
*/
public function definition_after_data() {
global $COURSE, $DB, $USER;
$mform = $this->_form;
$groupid = $mform->getElementValue('id');
$coursecontext = context_course::instance($COURSE->id);
if ($group = $DB->get_record('groups', array('id' => $groupid))) {
// If can create group conversation then get if a conversation area exists and it is enabled.
if (\core_message\api::can_create_group_conversation($USER->id, $coursecontext)) {
if (\core_message\api::is_conversation_area_enabled('core_group', 'groups', $groupid, $coursecontext->id)) {
$mform->getElement('enablemessaging')->setSelected(1);
}
}
// Print picture.
if (!($pic = print_group_picture($group, $COURSE->id, true, true, false))) {
$pic = get_string('none');
if ($mform->elementExists('deletepicture')) {
$mform->removeElement('deletepicture');
}
}
$imageelement = $mform->getElement('currentpicture');
$imageelement->setValue($pic);
} else {
if ($mform->elementExists('currentpicture')) {
$mform->removeElement('currentpicture');
}
if ($mform->elementExists('deletepicture')) {
$mform->removeElement('deletepicture');
}
}
if ($DB->record_exists('groups_members', ['groupid' => $groupid])) {
// If the group has members, lock visibility and participation fields.
/** @var MoodleQuickForm_select $visibility */
$visibility = $mform->getElement('visibility');
$visibility->freeze();
/** @var MoodleQuickForm_advcheckbox $participation */
$participation = $mform->getElement('participation');
$participation->freeze();
}
$handler = core_group\customfield\group_handler::create();
$handler->instance_form_definition_after_data($this->_form, empty($groupid) ? 0 : $groupid);
}
/**
* Form validation
*
* @param array $data
* @param array $files
* @return array $errors An array of errors
*/
function validation($data, $files) {
global $COURSE, $DB, $CFG;
$errors = parent::validation($data, $files);
$name = trim($data['name']);
if (isset($data['idnumber'])) {
$idnumber = trim($data['idnumber']);
} else {
$idnumber = '';
}
if ($data['id'] && $group = $DB->get_record('groups', ['id' => $data['id']])) {
if ($group->name != $name) {
if (groups_get_group_by_name($COURSE->id, $name)) {
$errors['name'] = get_string('groupnameexists', 'group', $name);
}
}
if (!empty($idnumber) && $group->idnumber != $idnumber) {
if (groups_get_group_by_idnumber($COURSE->id, $idnumber)) {
$errors['idnumber']= get_string('idnumbertaken');
}
}
if ($data['enrolmentkey'] != '') {
$errmsg = '';
if (!empty($CFG->groupenrolmentkeypolicy) && $group->enrolmentkey !== $data['enrolmentkey']
&& !check_password_policy($data['enrolmentkey'], $errmsg)) {
// Enforce password policy when the password is changed.
$errors['enrolmentkey'] = $errmsg;
} else {
// Prevent twice the same enrolment key in course groups.
$sql = "SELECT id FROM {groups} WHERE id <> :groupid AND courseid = :courseid AND enrolmentkey = :key";
$params = array('groupid' => $data['id'], 'courseid' => $COURSE->id, 'key' => $data['enrolmentkey']);
if ($DB->record_exists_sql($sql, $params)) {
$errors['enrolmentkey'] = get_string('enrolmentkeyalreadyinuse', 'group');
}
}
}
} else if (groups_get_group_by_name($COURSE->id, $name)) {
$errors['name'] = get_string('groupnameexists', 'group', $name);
} else if (!empty($idnumber) && groups_get_group_by_idnumber($COURSE->id, $idnumber)) {
$errors['idnumber']= get_string('idnumbertaken');
} else if ($data['enrolmentkey'] != '') {
$errmsg = '';
if (!empty($CFG->groupenrolmentkeypolicy) && !check_password_policy($data['enrolmentkey'], $errmsg)) {
// Enforce password policy.
$errors['enrolmentkey'] = $errmsg;
} else if ($DB->record_exists('groups', array('courseid' => $COURSE->id, 'enrolmentkey' => $data['enrolmentkey']))) {
// Prevent the same enrolment key from being used multiple times in course groups.
$errors['enrolmentkey'] = get_string('enrolmentkeyalreadyinuse', 'group');
}
}
$handler = \core_group\customfield\group_handler::create();
$errors = array_merge($errors, $handler->instance_form_validation($data, $files));
return $errors;
}
/**
* Get editor options for this form
*
* @return array An array of options
*/
function get_editor_options() {
return $this->_customdata['editoroptions'];
}
}
+149
View File
@@ -0,0 +1,149 @@
<?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/>.
/**
* Create grouping OR edit grouping settings.
*
* @copyright 2006 The Open University, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once('../config.php');
require_once('lib.php');
require_once('grouping_form.php');
/// get url variables
$courseid = optional_param('courseid', 0, PARAM_INT);
$id = optional_param('id', 0, PARAM_INT);
$delete = optional_param('delete', 0, PARAM_BOOL);
$confirm = optional_param('confirm', 0, PARAM_BOOL);
$url = new moodle_url('/group/grouping.php');
if ($id) {
$url->param('id', $id);
if (!$grouping = $DB->get_record('groupings', array('id'=>$id))) {
throw new \moodle_exception('invalidgroupid');
}
$grouping->description = clean_text($grouping->description);
if (empty($courseid)) {
$courseid = $grouping->courseid;
} else if ($courseid != $grouping->courseid) {
throw new \moodle_exception('invalidcourseid');
} else {
$url->param('courseid', $courseid);
}
if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
throw new \moodle_exception('invalidcourseid');
}
} else {
$url->param('courseid', $courseid);
if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
throw new \moodle_exception('invalidcourseid');
}
$grouping = new stdClass();
$grouping->courseid = $course->id;
}
$PAGE->set_url($url);
require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/course:managegroups', $context);
$strgroupings = get_string('groupings', 'group');
$PAGE->set_title($strgroupings);
$PAGE->set_heading($course->fullname. ': '.$strgroupings);
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $course->id)));
$returnurl = $CFG->wwwroot.'/group/groupings.php?id='.$course->id;
if ($id and $delete) {
if (!empty($grouping->idnumber) && !has_capability('moodle/course:changeidnumber', $context)) {
throw new \moodle_exception('groupinghasidnumber', '', '', $grouping->name);
}
if (!$confirm) {
$PAGE->set_title(get_string('deletegrouping', 'group'));
$PAGE->set_heading($course->fullname. ': '. get_string('deletegrouping', 'group'));
echo $OUTPUT->header();
$optionsyes = array('id'=>$id, 'delete'=>1, 'courseid'=>$courseid, 'sesskey'=>sesskey(), 'confirm'=>1);
$optionsno = array('id'=>$courseid);
$formcontinue = new single_button(new moodle_url('grouping.php', $optionsyes), get_string('yes'), 'get');
$formcancel = new single_button(new moodle_url('groupings.php', $optionsno), get_string('no'), 'get');
echo $OUTPUT->confirm(get_string('deletegroupingconfirm', 'group', $grouping->name), $formcontinue, $formcancel);
echo $OUTPUT->footer();
die;
} else if (confirm_sesskey()){
if (groups_delete_grouping($id)) {
redirect($returnurl);
} else {
throw new \moodle_exception('erroreditgrouping', 'group', $returnurl);
}
}
}
// Prepare the description editor: We do support files for grouping descriptions
$editoroptions = array('maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$course->maxbytes, 'trust'=>true, 'context'=>$context, 'noclean'=>true);
if (!empty($grouping->id)) {
$grouping = file_prepare_standard_editor($grouping, 'description', $editoroptions, $context, 'grouping', 'description', $grouping->id);
} else {
$grouping = file_prepare_standard_editor($grouping, 'description', $editoroptions, $context, 'grouping', 'description', null);
}
/// First create the form
$editform = new grouping_form(null, compact('editoroptions', 'grouping'));
$editform->set_data($grouping);
if ($editform->is_cancelled()) {
redirect($returnurl);
} elseif ($data = $editform->get_data()) {
$success = true;
if (!has_capability('moodle/course:changeidnumber', $context)) {
// Remove the idnumber if the user doesn't have permission to modify it
unset($data->idnumber);
}
if ($data->id) {
groups_update_grouping($data, $editoroptions);
} else {
groups_create_grouping($data, $editoroptions);
}
redirect($returnurl);
}
$strparticipants = get_string('participants');
if ($id) {
$strheading = get_string('editgroupingsettings', 'group');
} else {
$strheading = get_string('creategrouping', 'group');
}
$PAGE->navbar->add($strparticipants, new moodle_url('/user/index.php', array('id'=>$courseid)));
$PAGE->navbar->add($strgroupings, new moodle_url('/group/groupings.php', array('id'=>$courseid)));
$PAGE->navbar->add($strheading);
/// Print header
echo $OUTPUT->header();
echo $OUTPUT->heading($strheading);
$editform->display();
echo $OUTPUT->footer();
+41
View File
@@ -0,0 +1,41 @@
<?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/>.
/**
* Manage grouping custom fields
*
* @package core_group
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_group\customfield\grouping_handler;
use core_customfield\output\management;
require_once('../config.php');
require_once($CFG->libdir . '/adminlib.php');
admin_externalpage_setup('grouping_customfield');
$output = $PAGE->get_renderer('core_customfield');
$handler = grouping_handler::create();
$outputpage = new management($handler);
echo $output->header(),
$output->heading(new lang_string('grouping_customfield', 'admin')),
$output->render($outputpage),
$output->footer();
+131
View File
@@ -0,0 +1,131 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A form for creating and editing groupings.
*
* @copyright 2006 The Open University, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Grouping form class
*
* @copyright 2006 The Open University, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
class grouping_form extends moodleform {
/**
* Form definition
*/
function definition() {
global $USER, $CFG, $COURSE;
$coursecontext = context_course::instance($COURSE->id);
$mform =& $this->_form;
$editoroptions = $this->_customdata['editoroptions'];
$grouping = $this->_customdata['grouping'];
$mform->addElement('header', 'general', get_string('general', 'form'));
$mform->addElement('text','name', get_string('groupingname', 'group'),'maxlength="254" size="50"');
$mform->addRule('name', get_string('required'), 'required', null, 'server');
$mform->setType('name', PARAM_TEXT);
$mform->addElement('text','idnumber', get_string('idnumbergrouping'), 'maxlength="100" size="10"');
$mform->addHelpButton('idnumber', 'idnumbergrouping');
$mform->setType('idnumber', PARAM_RAW);
if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
$mform->hardFreeze('idnumber');
}
$mform->addElement('editor', 'description_editor', get_string('groupingdescription', 'group'), null, $editoroptions);
$mform->setType('description_editor', PARAM_RAW);
$handler = \core_group\customfield\grouping_handler::create();
$handler->instance_form_definition($mform, empty($grouping->id) ? 0 : $grouping->id);
$handler->instance_form_before_set_data($grouping);
$mform->addElement('hidden','id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'courseid');
$mform->setType('courseid', PARAM_INT);
$this->add_action_buttons();
}
/**
* Form validation
*
* @param array $data
* @param array $files
* @return array $errors An array of validataion errors for the form.
*/
function validation($data, $files) {
global $COURSE, $DB;
$errors = parent::validation($data, $files);
$name = trim($data['name']);
if (isset($data['idnumber'])) {
$idnumber = trim($data['idnumber']);
} else {
$idnumber = '';
}
if ($data['id'] and $grouping = $DB->get_record('groupings', array('id'=>$data['id']))) {
if (core_text::strtolower($grouping->name) != core_text::strtolower($name)) {
if (groups_get_grouping_by_name($COURSE->id, $name)) {
$errors['name'] = get_string('groupingnameexists', 'group', $name);
}
}
if (!empty($idnumber) && $grouping->idnumber != $idnumber) {
if (groups_get_grouping_by_idnumber($COURSE->id, $idnumber)) {
$errors['idnumber']= get_string('idnumbertaken');
}
}
} else if (groups_get_grouping_by_name($COURSE->id, $name)) {
$errors['name'] = get_string('groupingnameexists', 'group', $name);
} else if (!empty($idnumber) && groups_get_grouping_by_idnumber($COURSE->id, $idnumber)) {
$errors['idnumber']= get_string('idnumbertaken');
}
$handler = \core_group\customfield\grouping_handler::create();
$errors = array_merge($errors, $handler->instance_form_validation($data, $files));
return $errors;
}
/**
* Apply a logic after data is set.
*/
public function definition_after_data() {
$groupid = $this->_form->getElementValue('id');
$handler = \core_group\customfield\grouping_handler::create();
$handler->instance_form_definition_after_data($this->_form, empty($groupid) ? 0 : $groupid);
}
}
+117
View File
@@ -0,0 +1,117 @@
<?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/>.
/**
* Allows a creator to edit groupings
*
* @copyright 1999 Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once '../config.php';
require_once $CFG->dirroot.'/group/lib.php';
$courseid = required_param('id', PARAM_INT);
$PAGE->set_url('/group/groupings.php', array('id'=>$courseid));
if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
throw new \moodle_exception('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/course:managegroups', $context);
$strgrouping = get_string('grouping', 'group');
$strgroups = get_string('groups');
$strname = get_string('name');
$strdelete = get_string('delete');
$stredit = get_string('edit');
$srtnewgrouping = get_string('creategrouping', 'group');
$strgroups = get_string('groups');
$strgroupings = get_string('groupings', 'group');
$struses = get_string('activities');
$strparticipants = get_string('participants');
$strmanagegrping = get_string('showgroupsingrouping', 'group');
navigation_node::override_active_url(new moodle_url('/group/index.php', array('id'=>$courseid)));
$PAGE->navbar->add($strgroupings);
/// Print header
$PAGE->set_title($strgroupings);
$PAGE->set_heading($course->fullname);
$PAGE->set_pagelayout('standard');
echo $OUTPUT->header();
echo $OUTPUT->render_participants_tertiary_nav($course);
$data = array();
if ($groupings = $DB->get_records('groupings', array('courseid'=>$course->id), 'name')) {
$canchangeidnumber = has_capability('moodle/course:changeidnumber', $context);
foreach ($groupings as $gid => $grouping) {
$groupings[$gid]->formattedname = format_string($grouping->name, true, array('context' => $context));
}
core_collator::asort_objects_by_property($groupings, 'formattedname');
foreach($groupings as $grouping) {
$line = array();
$line[0] = $grouping->formattedname;
if ($groups = groups_get_all_groups($courseid, 0, $grouping->id)) {
$groupnames = array();
foreach ($groups as $group) {
$groupnames[] = format_string($group->name);
}
$line[1] = implode(', ', $groupnames);
} else {
$line[1] = get_string('none');
}
$line[2] = $DB->count_records('course_modules', array('course'=>$course->id, 'groupingid'=>$grouping->id));
$url = new moodle_url('/group/grouping.php', array('id' => $grouping->id));
$buttons = html_writer::link($url, $OUTPUT->pix_icon('t/edit', $stredit, 'core',
array('class' => 'iconsmall')), array('title' => $stredit));
if (empty($grouping->idnumber) || $canchangeidnumber) {
// It's only possible to delete groups without an idnumber unless the user has the changeidnumber capability.
$url = new moodle_url('/group/grouping.php', array('id' => $grouping->id, 'delete' => 1));
$buttons .= html_writer::link($url, $OUTPUT->pix_icon('t/delete', $strdelete, 'core',
array('class' => 'iconsmall')), array('title' => $strdelete));
} else {
$buttons .= $OUTPUT->spacer();
}
$url = new moodle_url('/group/assign.php', array('id' => $grouping->id));
$buttons .= html_writer::link($url, $OUTPUT->pix_icon('t/groups', $strmanagegrping, 'core',
array('class' => 'iconsmall')), array('title' => $strmanagegrping));
$line[3] = $buttons;
$data[] = $line;
}
}
$table = new html_table();
$table->head = array($strgrouping, $strgroups, $struses, $stredit);
$table->size = array('30%', '50%', '10%', '10%');
$table->align = array('left', 'left', 'center', 'center');
$table->width = '90%';
$table->data = $data;
echo html_writer::table($table);
echo $OUTPUT->container_start('buttons');
echo $OUTPUT->single_button(new moodle_url('grouping.php', array('courseid'=>$courseid)), $srtnewgrouping);
echo $OUTPUT->container_end();
echo $OUTPUT->footer();
+271
View File
@@ -0,0 +1,271 @@
<?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/>.
/**
* Bulk group creation registration script from a comma separated file
*
* @copyright 1999 Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once('../config.php');
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->dirroot.'/group/lib.php');
include_once('import_form.php');
$id = required_param('id', PARAM_INT); // Course id
$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
$PAGE->set_url('/group/import.php', array('id'=>$id));
require_login($course);
$context = context_course::instance($id);
require_capability('moodle/course:managegroups', $context);
$strimportgroups = get_string('importgroups', 'core_group');
$PAGE->navbar->add($strimportgroups);
navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $course->id)));
$PAGE->set_title("$course->shortname: $strimportgroups");
$PAGE->set_heading($course->fullname);
$PAGE->set_pagelayout('admin');
$returnurl = new moodle_url('/group/index.php', array('id'=>$id));
$importform = new groups_import_form(null, ['id' => $id]);
// If a file has been uploaded, then process it
if ($importform->is_cancelled()) {
redirect($returnurl);
} else if ($formdata = $importform->get_data()) {
echo $OUTPUT->header();
$text = $importform->get_file_content('userfile');
$text = preg_replace('!\r\n?!', "\n", $text);
require_once($CFG->libdir . '/csvlib.class.php');
$importid = csv_import_reader::get_new_iid('groupimport');
$csvimport = new csv_import_reader($importid, 'groupimport');
$delimiter = $formdata->delimiter_name;
$encoding = $formdata->encoding;
$readcount = $csvimport->load_csv_content($text, $encoding, $delimiter);
if ($readcount === false) {
throw new \moodle_exception('csvfileerror', 'error', $PAGE->url, $csvimport->get_error());
} else if ($readcount == 0) {
throw new \moodle_exception('csvemptyfile', 'error', $PAGE->url, $csvimport->get_error());
} else if ($readcount == 1) {
throw new \moodle_exception('csvnodata', 'error', $PAGE->url);
}
$csvimport->init();
unset($text);
// make arrays of valid fields for error checking
$required = array("groupname" => 1);
$optionaldefaults = array("lang" => 1);
$optional = array("coursename" => 1,
"idnumber" => 1,
"groupidnumber" => 1,
"description" => 1,
"enrolmentkey" => 1,
"groupingname" => 1,
"enablemessaging" => 1,
);
// Check custom fields from group and grouping.
$customfields = \core_group\customfield\group_handler::create()->get_fields();
$customfieldnames = [];
foreach ($customfields as $customfield) {
$controller = \core_customfield\data_controller::create(0, null, $customfield);
$customfieldnames['customfield_' . $customfield->get('shortname')] = 1;
}
$customfields = \core_group\customfield\grouping_handler::create()->get_fields();
$groupingcustomfields = [];
foreach ($customfields as $customfield) {
$controller = \core_customfield\data_controller::create(0, null, $customfield);
$groupingcustomfieldname = 'grouping_customfield_' . $customfield->get('shortname');
$customfieldnames[$groupingcustomfieldname] = 1;
$groupingcustomfields[$groupingcustomfieldname] = 'customfield_' . $customfield->get('shortname');
}
// --- get header (field names) ---
// Using get_columns() ensures the Byte Order Mark is removed.
$header = $csvimport->get_columns();
// Check for valid field names.
foreach ($header as $i => $h) {
// Remove whitespace.
$h = trim($h);
$header[$i] = $h;
if (!isset($required[$h]) && !isset($optionaldefaults[$h]) && !isset($optional[$h]) && !isset($customfieldnames[$h])) {
throw new \moodle_exception('invalidfieldname', 'error', $PAGE->url, $h);
}
if (isset($required[$h])) {
$required[$h] = 2;
}
}
// check for required fields
foreach ($required as $key => $value) {
if ($value < 2) {
throw new \moodle_exception('fieldrequired', 'error', $PAGE->url, $key);
}
}
$linenum = 2; // since header is line 1
while ($line = $csvimport->next()) {
$newgroup = new stdClass();//to make Martin happy
foreach ($optionaldefaults as $key => $value) {
$newgroup->$key = current_language(); //defaults to current language
}
foreach ($line as $key => $value) {
$record[$header[$key]] = trim($value);
}
if (trim(implode($line)) !== '') {
// add a new group to the database
// add fields to object $user
foreach ($record as $name => $value) {
// check for required values
if (isset($required[$name]) and !$value) {
throw new \moodle_exception('missingfield', 'error', $PAGE->url, $name);
} else if ($name == "groupname") {
$newgroup->name = $value;
} else {
// normal entry
$newgroup->{$name} = $value;
}
}
if (isset($newgroup->idnumber) && strlen($newgroup->idnumber)) {
//if idnumber is set, we use that.
//unset invalid courseid
if (!$mycourse = $DB->get_record('course', array('idnumber'=>$newgroup->idnumber))) {
echo $OUTPUT->notification(get_string('unknowncourseidnumber', 'error', $newgroup->idnumber));
unset($newgroup->courseid);//unset so 0 doesn't get written to database
} else {
$newgroup->courseid = $mycourse->id;
}
} else if (isset($newgroup->coursename) && strlen($newgroup->coursename)) {
//else use course short name to look up
//unset invalid coursename (if no id)
if (!$mycourse = $DB->get_record('course', array('shortname' => $newgroup->coursename))) {
echo $OUTPUT->notification(get_string('unknowncourse', 'error', $newgroup->coursename));
unset($newgroup->courseid);//unset so 0 doesn't get written to database
} else {
$newgroup->courseid = $mycourse->id;
}
} else {
//else use use current id
$newgroup->courseid = $id;
}
unset($newgroup->idnumber);
unset($newgroup->coursename);
//if courseid is set
if (isset($newgroup->courseid)) {
$linenum++;
$groupname = $newgroup->name;
$newgrpcoursecontext = context_course::instance($newgroup->courseid);
///Users cannot upload groups in courses they cannot update.
if (!has_capability('moodle/course:managegroups', $newgrpcoursecontext) or (!is_enrolled($newgrpcoursecontext) and !has_capability('moodle/course:view', $newgrpcoursecontext))) {
echo $OUTPUT->notification(get_string('nopermissionforcreation', 'group', $groupname));
} else {
if (isset($newgroup->groupidnumber)) {
// The CSV field for the group idnumber is groupidnumber rather than
// idnumber as that field is already in use for the course idnumber.
$newgroup->groupidnumber = trim($newgroup->groupidnumber);
if (has_capability('moodle/course:changeidnumber', $newgrpcoursecontext)) {
$newgroup->idnumber = $newgroup->groupidnumber;
if ($existing = groups_get_group_by_idnumber($newgroup->courseid, $newgroup->idnumber)) {
// idnumbers must be unique to a course but we shouldn't ignore group creation for duplicates
$existing->name = s($existing->name);
$existing->idnumber = s($existing->idnumber);
$existing->problemgroup = $groupname;
echo $OUTPUT->notification(get_string('groupexistforcoursewithidnumber', 'error', $existing));
unset($newgroup->idnumber);
}
}
// Always drop the groupidnumber key. It's not a valid database field
unset($newgroup->groupidnumber);
}
if ($groupid = groups_get_group_by_name($newgroup->courseid, $groupname)) {
echo $OUTPUT->notification("$groupname :".get_string('groupexistforcourse', 'error', $groupname));
} else if ($groupid = groups_create_group($newgroup)) {
echo $OUTPUT->notification(get_string('groupaddedsuccesfully', 'group', $groupname), 'notifysuccess');
} else {
echo $OUTPUT->notification(get_string('groupnotaddederror', 'error', $groupname));
continue;
}
// Add group to grouping
if (isset($newgroup->groupingname) && strlen($newgroup->groupingname)) {
$groupingname = $newgroup->groupingname;
if (! $groupingid = groups_get_grouping_by_name($newgroup->courseid, $groupingname)) {
$data = new stdClass();
$data->courseid = $newgroup->courseid;
$data->name = $groupingname;
// Add customfield if exists.
foreach ($header as $fieldname) {
if (isset($customfieldnames[$fieldname]) && isset($newgroup->$fieldname)) {
$data->{$groupingcustomfields[$groupingcustomfieldname]} = $newgroup->$fieldname;
}
}
if ($groupingid = groups_create_grouping($data)) {
echo $OUTPUT->notification(get_string('groupingaddedsuccesfully', 'group', $groupingname), 'notifysuccess');
} else {
echo $OUTPUT->notification(get_string('groupingnotaddederror', 'error', $groupingname));
continue;
}
}
// if we have reached here we definitely have a groupingid
$a = array('groupname' => $groupname, 'groupingname' => $groupingname);
try {
groups_assign_grouping($groupingid, $groupid);
echo $OUTPUT->notification(get_string('groupaddedtogroupingsuccesfully', 'group', $a), 'notifysuccess');
} catch (Exception $e) {
echo $OUTPUT->notification(get_string('groupnotaddedtogroupingerror', 'error', $a));
}
}
}
}
unset ($newgroup);
}
}
$csvimport->close();
echo $OUTPUT->single_button($returnurl, get_string('continue'), 'get');
echo $OUTPUT->footer();
die;
}
/// Print the form
echo $OUTPUT->header();
echo $OUTPUT->heading_with_help($strimportgroups, 'importgroups', 'core_group');
$importform->display();
echo $OUTPUT->footer();
+79
View File
@@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A form for group import.
*
* @package core_group
* @copyright 2010 Toyomoyo (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir . '/csvlib.class.php');
/**
* Groups import form class
*
* @package core_group
* @copyright 2010 Toyomoyo (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groups_import_form extends moodleform {
/**
* Form definition
*/
function definition() {
$mform =& $this->_form;
$data = $this->_customdata;
//fill in the data depending on page params
//later using set_data
$mform->addElement('header', 'general', get_string('general'));
$filepickeroptions = array();
$filepickeroptions['filetypes'] = '*';
$filepickeroptions['maxbytes'] = get_max_upload_file_size();
$mform->addElement('filepicker', 'userfile', get_string('import'), null, $filepickeroptions);
$mform->addRule('userfile', null, 'required');
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$choices = csv_import_reader::get_delimiter_list();
$mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'group'), $choices);
if (array_key_exists('cfg', $choices)) {
$mform->setDefault('delimiter_name', 'cfg');
} else if (get_string('listsep', 'langconfig') == ';') {
$mform->setDefault('delimiter_name', 'semicolon');
} else {
$mform->setDefault('delimiter_name', 'comma');
}
$choices = core_text::get_encodings();
$mform->addElement('select', 'encoding', get_string('encoding', 'group'), $choices);
$mform->setDefault('encoding', 'UTF-8');
$this->add_action_buttons(true, get_string('importgroups', 'core_group'));
$this->set_data($data);
}
}
+266
View File
@@ -0,0 +1,266 @@
<?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/>.
/**
* The main group management user interface.
*
* @copyright 2006 The Open University, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once('../config.php');
require_once('lib.php');
$courseid = required_param('id', PARAM_INT);
$groupid = optional_param('group', false, PARAM_INT);
$userid = optional_param('user', false, PARAM_INT);
$action = optional_param('action', false, PARAM_TEXT);
// Support either single group= parameter, or array groups[].
if ($groupid) {
$groupids = array($groupid);
} else {
$groupids = optional_param_array('groups', array(), PARAM_INT);
}
$singlegroup = (count($groupids) == 1);
$returnurl = $CFG->wwwroot.'/group/index.php?id='.$courseid;
// Get the course information so we can print the header and
// check the course id is valid.
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$url = new moodle_url('/group/index.php', array('id' => $courseid));
navigation_node::override_active_url($url);
if ($userid) {
$url->param('user', $userid);
}
if ($groupid) {
$url->param('group', $groupid);
}
$PAGE->set_url($url);
// Make sure that the user has permissions to manage groups.
require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/course:managegroups', $context);
$PAGE->requires->js('/group/clientlib.js', true);
$PAGE->requires->js('/group/module.js', true);
// Check for multiple/no group errors.
if (!$singlegroup) {
switch($action) {
case 'ajax_getmembersingroup':
case 'showgroupsettingsform':
case 'showaddmembersform':
case 'updatemembers':
throw new \moodle_exception('errorselectone', 'group', $returnurl);
}
}
switch ($action) {
case false: // OK, display form.
break;
case 'ajax_getmembersingroup':
$roles = array();
$userfieldsapi = \core_user\fields::for_identity($context)->with_userpic();
[
'selects' => $userfieldsselects,
'joins' => $userfieldsjoin,
'params' => $userfieldsparams
] = (array)$userfieldsapi->get_sql('u', true, '', '', false);
$extrafields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
if ($groupmemberroles = groups_get_members_by_role($groupids[0], $courseid,
'u.id, ' . $userfieldsselects, null, '', $userfieldsparams, $userfieldsjoin)) {
$viewfullnames = has_capability('moodle/site:viewfullnames', $context);
foreach ($groupmemberroles as $roleid => $roledata) {
$shortroledata = new stdClass();
$shortroledata->name = html_entity_decode($roledata->name, ENT_QUOTES, 'UTF-8');
$shortroledata->users = array();
foreach ($roledata->users as $member) {
$shortmember = new stdClass();
$shortmember->id = $member->id;
$shortmember->name = fullname($member, $viewfullnames);
if ($extrafields) {
$extrafieldsdisplay = [];
foreach ($extrafields as $field) {
// No escaping here, handled client side in response to AJAX request.
$extrafieldsdisplay[] = $member->{$field};
}
$shortmember->name .= ' (' . implode(', ', $extrafieldsdisplay) . ')';
}
$shortroledata->users[] = $shortmember;
}
$roles[] = $shortroledata;
}
}
echo json_encode($roles);
die; // Client side JavaScript takes it from here.
case 'deletegroup':
if (count($groupids) == 0) {
throw new \moodle_exception('errorselectsome', 'group', $returnurl);
}
$groupidlist = implode(',', $groupids);
redirect(new moodle_url('/group/delete.php', array('courseid' => $courseid, 'groups' => $groupidlist)));
break;
case 'showcreateorphangroupform':
redirect(new moodle_url('/group/group.php', array('courseid' => $courseid)));
break;
case 'showautocreategroupsform':
redirect(new moodle_url('/group/autogroup.php', array('courseid' => $courseid)));
break;
case 'showimportgroups':
redirect(new moodle_url('/group/import.php', array('id' => $courseid)));
break;
case 'showgroupsettingsform':
redirect(new moodle_url('/group/group.php', array('courseid' => $courseid, 'id' => $groupids[0])));
break;
case 'updategroups': // Currently reloading.
break;
case 'removemembers':
break;
case 'showaddmembersform':
redirect(new moodle_url('/group/members.php', array('group' => $groupids[0])));
break;
case 'updatemembers': // Currently reloading.
break;
case 'enablemessaging':
set_groups_messaging($groupids, true);
redirect($returnurl, get_string('messagingenabled', 'group', count($groupids)), null,
\core\output\notification::NOTIFY_SUCCESS);
break;
case 'disablemessaging':
set_groups_messaging($groupids, false);
redirect($returnurl, get_string('messagingdisabled', 'group', count($groupids)), null,
\core\output\notification::NOTIFY_SUCCESS);
break;
default: // ERROR.
throw new \moodle_exception('unknowaction', '', $returnurl);
break;
}
// Print the page and form.
$strgroups = get_string('groups');
$strparticipants = get_string('participants');
// Print header.
$PAGE->set_title($strgroups);
$PAGE->set_heading($course->fullname);
$PAGE->set_pagelayout('standard');
echo $OUTPUT->header();
echo $OUTPUT->render_participants_tertiary_nav($course);
$groups = groups_get_all_groups($courseid);
$selectedname = null;
$preventgroupremoval = array();
// Get list of groups to render.
$groupoptions = array();
if ($groups) {
foreach ($groups as $group) {
$selected = false;
$usercount = $DB->count_records('groups_members', array('groupid' => $group->id));
$groupname = format_string($group->name, true, ['context' => $context, 'escape' => false]) . ' (' . $usercount . ')';
if (in_array($group->id, $groupids)) {
$selected = true;
if ($singlegroup) {
// Only keep selected name if there is one group selected.
$selectedname = $groupname;
}
}
if (!empty($group->idnumber) && !has_capability('moodle/course:changeidnumber', $context)) {
$preventgroupremoval[$group->id] = true;
}
$groupoptions[] = (object) [
'value' => $group->id,
'selected' => $selected,
'text' => s($groupname)
];
}
}
// Get list of group members to render if there is a single selected group.
$members = array();
if ($singlegroup) {
$userfieldsapi = \core_user\fields::for_identity($context)->with_userpic();
[
'selects' => $userfieldsselects,
'joins' => $userfieldsjoin,
'params' => $userfieldsparams
] = (array)$userfieldsapi->get_sql('u', true, '', '', false);
$extrafields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
if ($groupmemberroles = groups_get_members_by_role(reset($groupids), $courseid,
'u.id, ' . $userfieldsselects, null, '', $userfieldsparams, $userfieldsjoin)) {
$viewfullnames = has_capability('moodle/site:viewfullnames', $context);
foreach ($groupmemberroles as $roleid => $roledata) {
$users = array();
foreach ($roledata->users as $member) {
$shortmember = new stdClass();
$shortmember->value = $member->id;
$shortmember->text = fullname($member, $viewfullnames);
if ($extrafields) {
$extrafieldsdisplay = [];
foreach ($extrafields as $field) {
$extrafieldsdisplay[] = s($member->{$field});
}
$shortmember->text .= ' (' . implode(', ', $extrafieldsdisplay) . ')';
}
$users[] = $shortmember;
}
$members[] = (object)[
'role' => html_entity_decode($roledata->name, ENT_QUOTES, 'UTF-8'),
'rolemembers' => $users
];
}
}
}
$disableaddedit = !$singlegroup;
$disabledelete = !empty($groupids);
$caneditmessaging = \core_message\api::can_create_group_conversation($USER->id, $context);
$renderable = new \core_group\output\index_page($courseid, $groupoptions, $selectedname, $members, $disableaddedit, $disabledelete,
$preventgroupremoval, $caneditmessaging);
$output = $PAGE->get_renderer('core_group');
echo $output->render($renderable);
echo $OUTPUT->footer();
+1326
View File
File diff suppressed because it is too large Load Diff
+165
View File
@@ -0,0 +1,165 @@
<?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/>.
/**
* Add/remove members from group.
*
* @copyright 2006 The Open University and others, N.D.Freear AT open.ac.uk, J.White AT open.ac.uk and others
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once(__DIR__ . '/../config.php');
require_once(__DIR__ . '/lib.php');
require_once($CFG->dirroot . '/user/selector/lib.php');
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->libdir . '/filelib.php');
$groupid = required_param('group', PARAM_INT);
$cancel = optional_param('cancel', false, PARAM_BOOL);
$group = $DB->get_record('groups', array('id'=>$groupid), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$group->courseid), '*', MUST_EXIST);
$PAGE->set_url('/group/members.php', array('group'=>$groupid));
$PAGE->set_pagelayout('admin');
require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/course:managegroups', $context);
$returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id.'&group='.$group->id;
if ($cancel) {
redirect($returnurl);
}
$groupmembersselector = new group_members_selector('removeselect', array('groupid' => $groupid, 'courseid' => $course->id));
$potentialmembersselector = new group_non_members_selector('addselect', array('groupid' => $groupid, 'courseid' => $course->id));
if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
$userstoadd = $potentialmembersselector->get_selected_users();
if (!empty($userstoadd)) {
foreach ($userstoadd as $user) {
if (!groups_add_member($groupid, $user->id)) {
throw new \moodle_exception('erroraddremoveuser', 'group', $returnurl);
}
$groupmembersselector->invalidate_selected_users();
$potentialmembersselector->invalidate_selected_users();
}
}
}
if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
$userstoremove = $groupmembersselector->get_selected_users();
if (!empty($userstoremove)) {
foreach ($userstoremove as $user) {
if (!groups_remove_member_allowed($groupid, $user->id)) {
throw new \moodle_exception('errorremovenotpermitted', 'group', $returnurl,
$user->fullname);
}
if (!groups_remove_member($groupid, $user->id)) {
throw new \moodle_exception('erroraddremoveuser', 'group', $returnurl);
}
$groupmembersselector->invalidate_selected_users();
$potentialmembersselector->invalidate_selected_users();
}
}
}
// Print the page and form
$strgroups = get_string('groups');
$strparticipants = get_string('participants');
$stradduserstogroup = get_string('adduserstogroup', 'group');
$strusergroupmembership = get_string('usergroupmembership', 'group');
$groupname = format_string($group->name);
$PAGE->requires->js('/group/clientlib.js');
$PAGE->navbar->add($strparticipants, new moodle_url('/user/index.php', array('id'=>$course->id)));
$PAGE->navbar->add($strgroups, new moodle_url('/group/index.php', array('id'=>$course->id)));
$PAGE->navbar->add($stradduserstogroup);
/// Print header
$PAGE->set_title("$course->shortname: $strgroups");
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('adduserstogroup', 'group').": $groupname", 3);
// Store the rows we want to display in the group info.
$groupinforow = array();
// Check if there is a description to display.
if (!empty($group->description)) {
$grouprenderer = $PAGE->get_renderer('core_group');
$groupdetailpage = new \core_group\output\group_details($groupid);
echo $grouprenderer->group_details($groupdetailpage);
}
/// Print the editing form
?>
<div id="addmembersform">
<form id="assignform" method="post" action="<?php echo $CFG->wwwroot; ?>/group/members.php?group=<?php echo $groupid; ?>">
<div>
<input type="hidden" name="sesskey" value="<?php p(sesskey()); ?>" />
<table class="generaltable generalbox groupmanagementtable boxaligncenter" summary="">
<tr>
<td id='existingcell'>
<p>
<label for="removeselect"><?php print_string('groupmembers', 'group'); ?></label>
</p>
<?php $groupmembersselector->display(); ?>
</td>
<td id='buttonscell'>
<p class="arrow_button">
<input class="btn btn-secondary" name="add" id="add"
type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>"
title="<?php print_string('add'); ?>" /><br />
<input class="btn btn-secondary" name="remove" id="remove"
type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>"
title="<?php print_string('remove'); ?>" />
</p>
</td>
<td id='potentialcell'>
<p>
<label for="addselect"><?php print_string('potentialmembs', 'group'); ?></label>
</p>
<?php $potentialmembersselector->display(); ?>
</td>
<td>
<p><?php echo($strusergroupmembership) ?></p>
<div id="group-usersummary"></div>
</td>
</tr>
<tr><td colspan="3" id='backcell'>
<input class="btn btn-secondary" type="submit" name="cancel"
value="<?php print_string('backtogroups', 'group'); ?>" />
</td></tr>
</table>
</div>
</form>
</div>
<?php
//outputs the JS array used to display the other groups users are in
$potentialmembersselector->print_user_summaries($course->id);
//this must be after calling display() on the selectors so their setup JS executes first
$PAGE->requires->js_init_call('init_add_remove_members_page', null, false, $potentialmembersselector->get_js_module());
echo $OUTPUT->footer();
+42
View File
@@ -0,0 +1,42 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
M.core_group = {
hoveroverlay : null
};
M.core_group.init_index = function(Y, wwwroot, courseid) {
M.core_group.groupsCombo = new UpdatableGroupsCombo(wwwroot, courseid);
M.core_group.membersCombo = new UpdatableMembersCombo(wwwroot, courseid);
};
M.core_group.groupslist = function(Y, preventgroupremoval) {
var actions = {
init : function() {
// We need to add check_deletable both on change for the groups, and then call it the first time the page loads
Y.one('#groups').on('change', this.check_deletable, this);
this.check_deletable();
},
check_deletable : function() {
// Ensure that if the 'preventremoval' attribute is set, the delete button is greyed out
var candelete = true;
var optionselected = false;
Y.one('#groups').get('options').each(function(option) {
if (option.get('selected')) {
optionselected = true;
if (option.getAttribute('value') in preventgroupremoval) {
candelete = false;
}
}
}, this);
var deletebutton = Y.one('#deletegroup');
if (candelete && optionselected) {
deletebutton.removeAttribute('disabled');
} else {
deletebutton.setAttribute('disabled', 'disabled');
}
}
}
actions.init();
};
+381
View File
@@ -0,0 +1,381 @@
<?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/>.
/**
* Print an overview of groupings & group membership
*
* @copyright Matt Clarkson mattc@catalyst.net.nz
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core_group
*/
require_once('../config.php');
require_once($CFG->libdir . '/filelib.php');
define('OVERVIEW_NO_GROUP', -1); // The fake group for users not in a group.
define('OVERVIEW_GROUPING_GROUP_NO_GROUPING', -1); // The fake grouping for groups that have no grouping.
define('OVERVIEW_GROUPING_NO_GROUP', -2); // The fake grouping for users with no group.
$courseid = required_param('id', PARAM_INT);
$groupid = optional_param('group', 0, PARAM_INT);
$groupingid = optional_param('grouping', 0, PARAM_INT);
$dataformat = optional_param('dataformat', '', PARAM_ALPHA);
$returnurl = $CFG->wwwroot.'/group/index.php?id='.$courseid;
$rooturl = $CFG->wwwroot.'/group/overview.php?id='.$courseid;
if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
throw new \moodle_exception('invalidcourse');
}
$url = new moodle_url('/group/overview.php', array('id'=>$courseid));
if ($groupid !== 0) {
$url->param('group', $groupid);
}
if ($groupingid !== 0) {
$url->param('grouping', $groupingid);
}
$PAGE->set_url($url);
// Make sure that the user has permissions to manage groups.
require_login($course);
$context = context_course::instance($courseid);
require_capability('moodle/course:managegroups', $context);
$strgroups = get_string('groups');
$strparticipants = get_string('participants');
$stroverview = get_string('overview', 'group');
$strgrouping = get_string('grouping', 'group');
$strgroup = get_string('group', 'group');
$strnotingrouping = get_string('notingrouping', 'group');
$strfiltergroups = get_string('filtergroups', 'group');
$strnogroups = get_string('nogroups', 'group');
$strdescription = get_string('description');
$strnotingroup = get_string('notingrouplist', 'group');
$strnogroup = get_string('nogroup', 'group');
$strnogrouping = get_string('nogrouping', 'group');
// This can show all users and all groups in a course.
// This is lots of data so allow this script more resources.
raise_memory_limit(MEMORY_EXTRA);
// Get all groupings and sort them by formatted name.
$groupings = $DB->get_records('groupings', array('courseid'=>$courseid), 'name');
foreach ($groupings as $gid => $grouping) {
$groupings[$gid]->formattedname = format_string($grouping->name, true, array('context' => $context));
}
core_collator::asort_objects_by_property($groupings, 'formattedname');
$members = array();
foreach ($groupings as $grouping) {
$members[$grouping->id] = array();
}
// Groups not in a grouping.
$members[OVERVIEW_GROUPING_GROUP_NO_GROUPING] = array();
// Get all groups and sort them by formatted name.
$groups = $DB->get_records('groups', array('courseid'=>$courseid), 'name');
foreach ($groups as $id => $group) {
$groups[$id]->formattedname = format_string($group->name, true, ['context' => $context]);
}
core_collator::asort_objects_by_property($groups, 'formattedname');
$params = array('courseid'=>$courseid);
if ($groupid) {
$groupwhere = "AND g.id = :groupid";
$params['groupid'] = $groupid;
} else {
$groupwhere = "";
}
if ($groupingid) {
if ($groupingid < 0) { // No grouping filter.
$groupingwhere = "AND gg.groupingid IS NULL";
} else {
$groupingwhere = "AND gg.groupingid = :groupingid";
$params['groupingid'] = $groupingid;
}
} else {
$groupingwhere = "";
}
list($sort, $sortparams) = users_order_by_sql('u');
$userfieldsapi = \core_user\fields::for_identity($context)->with_userpic();
[
'selects' => $userfieldsselects,
'joins' => $userfieldsjoin,
'params' => $userfieldsparams
] = (array)$userfieldsapi->get_sql('u', true);
$extrafields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
$allnames = 'u.id ' . $userfieldsselects;
$sql = "SELECT g.id AS groupid, gg.groupingid, u.id AS userid, $allnames, u.idnumber, u.username
FROM {groups} g
LEFT JOIN {groupings_groups} gg ON g.id = gg.groupid
LEFT JOIN {groups_members} gm ON g.id = gm.groupid
LEFT JOIN {user} u ON gm.userid = u.id
$userfieldsjoin
WHERE g.courseid = :courseid $groupwhere $groupingwhere
ORDER BY g.name, $sort";
$rs = $DB->get_recordset_sql($sql, array_merge($params, $sortparams, $userfieldsparams));
foreach ($rs as $row) {
$user = username_load_fields_from_object((object) [], $row, null,
array_merge(['id' => 'userid', 'username', 'idnumber'], $extrafields));
if (!$row->groupingid) {
$row->groupingid = OVERVIEW_GROUPING_GROUP_NO_GROUPING;
}
if (!array_key_exists($row->groupid, $members[$row->groupingid])) {
$members[$row->groupingid][$row->groupid] = array();
}
if (!empty($user->id)) {
$members[$row->groupingid][$row->groupid][] = $user;
}
}
$rs->close();
// Add 'no groupings' / 'no groups' selectors.
$groupings[OVERVIEW_GROUPING_GROUP_NO_GROUPING] = (object)array(
'id' => OVERVIEW_GROUPING_GROUP_NO_GROUPING,
'formattedname' => $strnogrouping,
);
$groups[OVERVIEW_NO_GROUP] = (object)array(
'id' => OVERVIEW_NO_GROUP,
'courseid' => $courseid,
'idnumber' => '',
'name' => $strnogroup,
'formattedname' => $strnogroup,
'description' => '',
'descriptionformat' => FORMAT_HTML,
'enrolmentkey' => '',
'picture' => 0,
'timecreated' => 0,
'timemodified' => 0,
);
// Add users who are not in a group.
if ($groupid <= 0 && $groupingid <= 0) {
list($esql, $params) = get_enrolled_sql($context, null, 0, true);
$sql = "SELECT u.id, $allnames, u.idnumber, u.username
FROM {user} u
JOIN ($esql) e ON e.id = u.id
LEFT JOIN (
SELECT gm.userid
FROM {groups_members} gm
JOIN {groups} g ON g.id = gm.groupid
WHERE g.courseid = :courseid
) grouped ON grouped.userid = u.id
$userfieldsjoin
WHERE grouped.userid IS NULL
ORDER BY $sort";
$params['courseid'] = $courseid;
$nogroupusers = $DB->get_records_sql($sql, array_merge($params, $userfieldsparams));
if ($nogroupusers) {
$members[OVERVIEW_GROUPING_NO_GROUP][OVERVIEW_NO_GROUP] = $nogroupusers;
}
}
// Export groups if requested.
if ($dataformat !== '') {
$columnnames = array(
'grouping' => $strgrouping,
'group' => $strgroup,
'firstname' => get_string('firstname'),
'lastname' => get_string('lastname'),
);
$extrafields = \core_user\fields::get_identity_fields($context, false);
foreach ($extrafields as $field) {
$columnnames[$field] = \core_user\fields::get_display_name($field);
}
$alldata = array();
// Generate file name.
$shortname = format_string($course->shortname, true, array('context' => $context))."_groups";
$i = 0;
foreach ($members as $gpgid => $groupdata) {
if ($groupingid and $groupingid != $gpgid) {
if ($groupingid > 0 || $gpgid > 0) {
// Still show 'not in group' when 'no grouping' selected.
continue; // Do not export.
}
}
if ($gpgid < 0) {
// Display 'not in group' for grouping id == OVERVIEW_GROUPING_NO_GROUP.
if ($gpgid == OVERVIEW_GROUPING_NO_GROUP) {
$groupingname = $strnotingroup;
} else {
$groupingname = $strnotingrouping;
}
} else {
$groupingname = $groupings[$gpgid]->formattedname;
}
if (empty($groupdata)) {
$alldata[$i] = array_fill_keys(array_keys($columnnames), '');
$alldata[$i]['grouping'] = $groupingname;
$i++;
}
foreach ($groupdata as $gpid => $users) {
if ($groupid and $groupid != $gpid) {
continue;
}
if (empty($users)) {
$alldata[$i] = array_fill_keys(array_keys($columnnames), '');
$alldata[$i]['grouping'] = $groupingname;
$alldata[$i]['group'] = $groups[$gpid]->formattedname;
$i++;
}
foreach ($users as $option => $user) {
$alldata[$i]['grouping'] = $groupingname;
$alldata[$i]['group'] = $groups[$gpid]->formattedname;
$alldata[$i]['firstname'] = $user->firstname;
$alldata[$i]['lastname'] = $user->lastname;
foreach ($extrafields as $field) {
$alldata[$i][$field] = $user->$field;
}
$i++;
}
}
}
\core\dataformat::download_data(
$shortname,
$dataformat,
$columnnames,
$alldata,
function($record, $supportshtml) use ($extrafields) {
if ($supportshtml) {
foreach ($extrafields as $extrafield) {
$record[$extrafield] = s($record[$extrafield]);
}
}
return $record;
});
die;
}
// Main page content.
navigation_node::override_active_url(new moodle_url('/group/index.php', array('id'=>$courseid)));
$PAGE->navbar->add(get_string('overview', 'group'));
/// Print header
$PAGE->set_title($strgroups);
$PAGE->set_heading($course->fullname);
$PAGE->set_pagelayout('standard');
echo $OUTPUT->header();
echo $OUTPUT->render_participants_tertiary_nav($course);
echo $strfiltergroups;
$options = array();
$options[0] = get_string('all');
foreach ($groupings as $grouping) {
$options[$grouping->id] = strip_tags($grouping->formattedname);
}
$popupurl = new moodle_url($rooturl.'&group='.$groupid);
$select = new single_select($popupurl, 'grouping', $options, $groupingid, array());
$select->label = $strgrouping;
$select->formid = 'selectgrouping';
echo $OUTPUT->render($select);
$options = array();
$options[0] = get_string('all');
foreach ($groups as $group) {
$options[$group->id] = $group->formattedname;
}
$popupurl = new moodle_url($rooturl.'&grouping='.$groupingid);
$select = new single_select($popupurl, 'group', $options, $groupid, array());
$select->label = $strgroup;
$select->formid = 'selectgroup';
echo $OUTPUT->render($select);
/// Print table
$printed = false;
foreach ($members as $gpgid=>$groupdata) {
if ($groupingid and $groupingid != $gpgid) {
if ($groupingid > 0 || $gpgid > 0) { // Still show 'not in group' when 'no grouping' selected.
continue; // Do not show.
}
}
$table = new html_table();
$table->head = array(get_string('groupscount', 'group', count($groupdata)), get_string('groupmembers', 'group'), get_string('usercount', 'group'));
$table->size = array('20%', '70%', '10%');
$table->align = array('left', 'left', 'center');
$table->width = '90%';
$table->data = array();
foreach ($groupdata as $gpid=>$users) {
if ($groupid and $groupid != $gpid) {
continue;
}
$line = array();
$name = print_group_picture($groups[$gpid], $course->id, false, true, false) . $groups[$gpid]->formattedname;
$description = file_rewrite_pluginfile_urls($groups[$gpid]->description, 'pluginfile.php', $context->id, 'group', 'description', $gpid);
$options = new stdClass;
$options->noclean = true;
$options->overflowdiv = true;
$line[] = $name;
$viewfullnames = has_capability('moodle/site:viewfullnames', $context);
$fullnames = array();
foreach ($users as $user) {
$displayname = fullname($user, $viewfullnames);
if ($extrafields) {
$extrafieldsdisplay = [];
foreach ($extrafields as $field) {
$extrafieldsdisplay[] = s($user->{$field});
}
$displayname .= ' (' . implode(', ', $extrafieldsdisplay) . ')';
}
$fullnames[] = html_writer::link(new moodle_url('/user/view.php', ['id' => $user->id, 'course' => $course->id]),
$displayname);
}
$line[] = implode(', ', $fullnames);
$line[] = count($users);
$table->data[] = $line;
}
if ($groupid and empty($table->data)) {
continue;
}
if ($gpgid < 0) {
// Display 'not in group' for grouping id == OVERVIEW_GROUPING_NO_GROUP.
if ($gpgid == OVERVIEW_GROUPING_NO_GROUP) {
echo $OUTPUT->heading($strnotingroup, 3);
} else {
echo $OUTPUT->heading($strnotingrouping, 3);
}
} else {
echo $OUTPUT->heading($groupings[$gpgid]->formattedname, 3);
$description = file_rewrite_pluginfile_urls($groupings[$gpgid]->description, 'pluginfile.php', $context->id, 'grouping', 'description', $gpgid);
$options = new stdClass;
$options->overflowdiv = true;
echo $OUTPUT->box(format_text($description, $groupings[$gpgid]->descriptionformat, $options), 'generalbox boxwidthnarrow boxaligncenter');
}
echo html_writer::table($table);
$printed = true;
}
// Add buttons for exporting groups/groupings.
echo $OUTPUT->download_dataformat_selector(get_string('exportgroupsgroupings', 'group'), 'overview.php', 'dataformat', [
'id' => $courseid,
'group' => $groupid,
'grouping' => $groupingid,
]);
echo $OUTPUT->footer();
+38
View File
@@ -0,0 +1,38 @@
<?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/>.
/**
* Prints navigation tabs
*
* @package core_group
* @copyright 2010 Petr Skoda (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$row = array();
$row[] = new tabobject('groups',
new moodle_url('/group/index.php', array('id' => $courseid)),
get_string('groups'));
$row[] = new tabobject('groupings',
new moodle_url('/group/groupings.php', array('id' => $courseid)),
get_string('groupings', 'group'));
$row[] = new tabobject('overview',
new moodle_url('/group/overview.php', array('id' => $courseid)),
get_string('overview', 'group'));
echo '<div class="groupdisplay">';
echo $OUTPUT->tabtree($row, $currenttab);
echo '</div>';
@@ -0,0 +1,44 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_group/comboboxsearch/group_selector
The group selector trigger element.
Context variables required for this template:
* label - The label text fot the group selector element.
* group - The value of the group selector element (id of the preselected group)
* selectedgroup - The text of the selected group option.
Example context (json):
{
"label": "Select separate groups",
"group": "21",
"selectedgroup": "Group 1"
}
}}
<span class="d-none" data-region="groupid" data-groupid="{{group}}"></span>
<div class="align-items-center d-flex">
<div class="d-block pr-3 text-truncate">
<span class="d-block small" aria-hidden="true">
{{label}}
</span>
<span class="p-0 font-weight-bold">
{{selectedgroup}}
</span>
</div>
</div>
@@ -0,0 +1,42 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_group/comboboxsearch/resultitem
Template for the individual result item.
Context variables required for this template:
* id - Group system ID.
* name - Groups' name.
* groupimageurl - The link of the groups picture.
Example context (json):
{
"id": 2,
"name": "Foo bar",
"groupimageurl": "http://foo.bar/grade/report/grader/index.php?id=42&userid=2"
}
}}
{{<core/local/comboboxsearch/resultitem }}
{{$content}}
<div class="pr-2 pl-1 w-25">
<img class="rounded-circle mx-auto img-fluid" src="{{groupimageurl}}" alt=""/>
</div>
<div class="pr-3 w-75">
<span class="d-block w-100 p-0 text-truncate">
{{name}}
</span>
</div>
{{/content}}
{{/core/local/comboboxsearch/resultitem}}
@@ -0,0 +1,51 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_group/comboboxsearch/resultset
Wrapping template for returned result items.
Context variables required for this template:
* groups - Our returned groups to render.
* instance - The instance ID of the combo box.
* searchterm - The entered text to find these results.
* hasgroups - Allow the handling where no users exist for the returned search term.
Example context (json):
{
"groups": [
{
"id": 2,
"name": "Foo bar",
"link": "http://foo.bar/grade/report/grader/index.php?id=42&userid=2"
},
{
"id": 3,
"name": "Bar Foo",
"link": "http://foo.bar/grade/report/grader/index.php?id=42&userid=3"
}
],
"instance": 25,
"searchterm": "Foo",
"hasresults": true
}
}}
{{<core/local/comboboxsearch/resultset}}
{{$listid}}groups{{/listid}}
{{$results}}
{{#groups}}
{{>core_group/comboboxsearch/resultitem}}
{{/groups}}
{{/results}}
{{/core/local/comboboxsearch/resultset}}
@@ -0,0 +1,55 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_group/comboboxsearch/searchbody
Wrapping template for search input.
Context variables required for this template:
* courseid - The id of the course to search within.
* groupid - The id of the group to search within.
* currentvalue - The prefill value for the search input if provided
Example context (json):
{
"courseid": 2,
"groupid": 25,
"currentvalue": "bar"
}
}}
<div class="flex-column h-100 w-100">
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}"></span>
<span class="d-none" data-region="groupid" data-groupid="{{groupid}}"></span>
<span class="d-none" data-region="instance" data-instance="{{instance}}"></span>
{{< core/search_input_auto }}
{{$label}}{{#str}}
searchgroups, core
{{/str}}{{/label}}
{{$placeholder}}{{#str}}
searchgroups, core
{{/str}}{{/placeholder}}
{{$value}}{{currentvalue}}{{/value}}
{{$additionalattributes}}
role="combobox"
aria-expanded="true"
aria-controls="groups-{{instance}}-result-listbox"
aria-autocomplete="list"
data-input-element="input-{{uniqid}}-{{instance}}"
{{/additionalattributes}}
{{/ core/search_input_auto }}
<input type="hidden" name="search" id="input-{{uniqid}}-{{instance}}"/>
<div data-region="searchplaceholder"></div>
</div>
+53
View File
@@ -0,0 +1,53 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_group/group_details
Template for the Groups page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* name string Group Name
* pictureurl string Group image url
* description string Group description
* edit string edit link to edit the group
Example context (json):
{
"name": "Group Name",
"pictureurl": "https://raw.githubusercontent.com/moodle/moodle/master/pix/g/f1.png",
"description": "This is the description for Group Name",
"editurl": "http://www.moodle.org"
}
}}
{{#name}}
<div class="groupinfobox container-fluid card p-3">
{{#pictureurl}}
<div class="group-image"><img class="grouppicture" src="{{{pictureurl}}}" alt="{{{name}}}" title="{{{name}}}"/></div>
{{/pictureurl}}
{{#editurl}}
<div class="group-edit"><a href="{{editurl}}">{{#pix}}t/edit, core, {{#str}}editgroupprofile{{/str}}{{/pix}}</a></div>
{{/editurl}}
<h3 class="">{{{name}}}</h3>
<div class="group-description">{{{description}}}</div>
</div>
{{/name}}
+160
View File
@@ -0,0 +1,160 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_group/index
Template for the Groups page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* courseid int The course ID.
* selectedgroup string The initially selected group.
* editgroupsettingsdisabled bool Whether to disable the "Edit group settings" button on load.
* deletegroupdisabled bool Whether to disable the "Delete selected group" button on load.
* addmembersdisabled bool Whether to disable the "Add/remove users" button on load.
* groups array The list of groups.
* members array The list of members, grouped based on roles.
* undeletablegroups string A JSON string containing an array of group IDs that a user cannot delete.
* messagingsettingsvisible bool Wether the messaging settings buttons should be visible.
Example context (json):
{
"courseid": "1",
"selectedgroup": "Group 1 (3)",
"editgroupsettingsdisabled": false,
"deletegroupdisabled": false,
"addmembersdisabled": false,
"messagingenabled": true,
"groups": [
{
"value": "1",
"text": "Group 1 (3)",
"selected": true
},
{
"value": "2",
"text": "Group 2 (2)"
}
],
"members": [
{
"role": "Student",
"rolemembers": [
{
"value": "1",
"text": "John Doe"
},
{
"value": "2",
"text": "Jane Doe"
},
{
"value": "3",
"text": "John Smith"
}
]
}
],
"undeletablegroups": "[1: true, 3: true]"
}
}}
<form id="groupeditform" action="index.php" method="post">
<div class="container-fluid groupmanagementtable">
<div class="row rtl-compatible">
<div class="col-md-6 mb-1">
<input type="hidden" name="id" value="{{courseid}}">
<div class="mb-3">
<label for="groups">
<span id="groupslabel">{{#str}}groups{{/str}}</span>
<span id="thegrouping">&nbsp;</span>
</label>
<select name="groups[]" multiple="multiple" id="groups" size="15" class="form-control input-block-level">
{{#groups}}
<option value="{{value}}" {{#selected}}selected="selected"{{/selected}} title="{{{text}}}">{{{text}}}</option>
{{/groups}}
</select>
</div>
<h3> {{#str}} withselected, group {{/str}} </h3>
<div class="mb-3">
<button type="submit" name="action" id="updatemembers" value="updatemembers" class="btn btn-secondary">{{#str}}showmembersforgroup, group{{/str}}</button>
</div>
<div class="mb-3">
<button type="submit" name="action" id="showeditgroupsettingsform" value="showgroupsettingsform" {{#editgroupsettingsdisabled}}disabled="disabled"{{/editgroupsettingsdisabled}} class="btn btn-secondary">{{#str}}editgroupsettings, group{{/str}}</button>
</div>
<div class="mb-3">
<button type="submit" name="action" id="deletegroup" value="deletegroup" {{#deletegroupdisabled}}disabled="disabled"{{/deletegroupdisabled}} class="btn btn-secondary">{{#str}}deleteselectedgroup, group{{/str}}</button>
</div>
{{#messagingsettingsvisible}}
<div class="mb-3">
<button type="submit" name="action" id="disablemessaging" value="disablemessaging" class="btn btn-secondary" disabled="disabled">{{#str}}disablemessagingaction, group{{/str}}</button>
</div>
<div class="mb-3">
<button type="submit" name="action" id="enablemessaging" value="enablemessaging" class="btn btn-secondary" disabled="disabled">{{#str}}enablemessagingaction, group{{/str}}</button>
</div>
{{/messagingsettingsvisible}}
<h3> {{#str}} manageactions, group {{/str}} </h3>
<div class="mb-3">
<button type="submit" name="action" id="showcreateorphangroupform" value="showcreateorphangroupform" class="btn btn-secondary">{{#str}}creategroup, group{{/str}}</button>
</div>
<div class="mb-3">
<button type="submit" name="action" id="showautocreategroupsform" value="showautocreategroupsform" class="btn btn-secondary">{{#str}}autocreategroups, group{{/str}}</button>
</div>
<div class="mb-3">
<button type="submit" name="action" id="showimportgroups" value="showimportgroups" class="btn btn-secondary">{{#str}}importgroups, group{{/str}}</button>
</div>
</div>
<div class="col-md-6 mb-1">
<div class="mb-3">
<label for="members">
<span id="memberslabel">{{#str}}membersofselectedgroup, group{{/str}}</span>
<span id="thegroup">{{{selectedgroup}}}</span>
</label>
<select size="15" multiple="multiple" class="form-control input-block-level" id="members" name="user">
{{#members}}
<optgroup label="{{role}}">
{{#rolemembers}}
<option value="{{value}}" title="{{text}}">{{{text}}}</option>
{{/rolemembers}}
</optgroup>
{{/members}}
</select>
</div>
<div class="mb-3">
<button type="submit" value="showaddmembersform" class="btn btn-secondary" {{#addmembersdisabled}}disabled="disabled"{{/addmembersdisabled}} name="action" id="showaddmembersform">{{#str}}adduserstogroup, group{{/str}}</button>
</div>
</div>
</div>
</div>
</form>
{{#js}}
require(['jquery', 'core/yui'], function($) {
$("#groups").change(function() {
M.core_group.membersCombo.refreshMembers();
});
M.core_group.init_index(Y, "{{wwwroot}}", {{courseid}});
var undeletableGroups = JSON.parse('{{{undeletablegroups}}}');
M.core_group.groupslist(Y, undeletableGroups);
});
{{/js}}
{{#js}}
require(['core_group/index'], (module) => module.init());
{{/js}}
+193
View File
@@ -0,0 +1,193 @@
@core @core_group
Feature: Automatic creation of groups
In order to quickly create groups
As a teacher
I need to create groups automatically and allocate them in groupings if necessary
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
| student4 | Student | 4 | student4@example.com |
| student5 | Student | 5 | student5@example.com |
| student6 | Student | 6 | student6@example.com |
| student7 | Student | 7 | student7@example.com |
| student8 | Student | 8 | student8@example.com |
| student9 | Student | 9 | student9@example.com |
| student10 | Student | 10 | student10@example.com |
| suspendedstudent11 | Suspended student | 11 | suspendedstudent11@example.com |
And the following "course enrolments" exist:
| user | course | role | status |
| teacher1 | C1 | editingteacher | 0 |
| student1 | C1 | student | 0 |
| student2 | C1 | student | 0 |
| student3 | C1 | student | 0 |
| student4 | C1 | student | 0 |
| student5 | C1 | student | 0 |
| student6 | C1 | student | 0 |
| student7 | C1 | student | 0 |
| student8 | C1 | student | 0 |
| student9 | C1 | student | 0 |
| student10 | C1 | student | 0 |
| suspendedstudent11 | C1 | student | 1 |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
When I press "Auto-create groups"
And I expand all fieldsets
@javascript
Scenario: Split automatically the course users in groups and add the groups to a new grouping
Given I set the following fields to these values:
| Auto create based on | Number of groups |
| Group/member count | 2 |
| Grouping of auto-created groups | New grouping |
| Grouping name | Grouping name |
And I press "Preview"
Then I should see "Group members"
And I should see "User count"
And I should see "Group A"
And I should see "Group B"
And I press "Submit"
And the "groups" select box should contain "Group A (5)"
And the "groups" select box should contain "Group B (5)"
# Check that group messaging is not enabled for the auto-created groups.
And I set the field "groups" to "Group A"
And I press "Edit group settings"
And I should see "No" in the "Group messaging" "select"
And I press "Cancel"
And I set the field "groups" to "Group B"
And I press "Edit group settings"
And I should see "No" in the "Group messaging" "select"
And I press "Cancel"
# Check groupings.
And I set the field "Participants tertiary navigation" to "Groupings"
And I should see "Grouping name"
And I click on "Show groups in grouping" "link" in the "Grouping name" "table_row"
And the "removeselect" select box should contain "Group A"
And the "removeselect" select box should contain "Group B"
@javascript
Scenario: Split automatically the course users in groups based on group member count
Given I set the following fields to these values:
| Auto create based on | Members per group |
| Group/member count | 4 |
| Grouping of auto-created groups | New grouping |
| Grouping name | Grouping name |
| Allocate members | Alphabetically by last name, first name |
And I press "Preview"
Then the following should exist in the "generaltable" table:
| Groups (3) | Group members | User count (10) |
| Group A | Student 1 (student1@example.com) | 4 |
| Group B | Student 5 (student5@example.com) | 4 |
| Group C | Student 9 (student9@example.com) | 2 |
And I set the field "Prevent last small group" to "1"
And I press "Preview"
And I should see "Group A" in the ".generaltable" "css_element"
And I should see "Group B" in the ".generaltable" "css_element"
And I should see "5" in the "Group A" "table_row"
And I should see "5" in the "Group B" "table_row"
@javascript
Scenario: Split automatically the course users in groups that are not in groups
Given I press "Cancel"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group 1 |
And I press "Save changes"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group 2 |
And I press "Save changes"
When I add "Student 1" user to "Group 1" group members
And I add "Student 2" user to "Group 1" group members
And I add "Student 3" user to "Group 2" group members
And I add "Student 4" user to "Group 2" group members
And I press "Auto-create groups"
And I expand all fieldsets
And I set the field "Auto create based on" to "Number of groups"
And I set the field "Group/member count" to "2"
And I set the field "Grouping of auto-created groups" to "No grouping"
And I set the field "Ignore users in groups" to "1"
And I press "Submit"
And the "groups" select box should contain "Group A (3)"
And the "groups" select box should contain "Group B (3)"
@javascript
Scenario: Split users into groups based on existing groups or groupings
Given I set the following fields to these values:
| Naming scheme | Group @ |
| Auto create based on | Number of groups |
| Group/member count | 2 |
| Grouping of auto-created groups | No grouping |
And I press "Submit"
And I press "Auto-create groups"
And I set the following fields to these values:
| Naming scheme | Test @ |
| Auto create based on | Number of groups |
| Group/member count | 2 |
| groupid | Group A |
| Grouping of auto-created groups | New grouping |
| Grouping name | Sub Grouping |
And I press "Submit"
And the "groups" select box should contain "Test A (3)"
And the "groups" select box should contain "Test B (2)"
And I press "Auto-create groups"
And I set the following fields to these values:
| Naming scheme | Test # |
| Auto create based on | Number of groups |
| Group/member count | 2 |
| Select members from grouping | Sub Grouping |
| Grouping of auto-created groups | No grouping |
And I press "Submit"
And the "groups" select box should contain "Test 1 (3)"
And the "groups" select box should contain "Test 2 (2)"
Scenario: Exclude suspended users when auto-creating groups
Given I set the field "Include only active enrolments" to "1"
And I set the field "Auto create based on" to "Members per group"
When I set the field "Group/member count" to "11"
And I press "Preview"
Then I should not see "Suspended Student 11"
Scenario: Include suspended users when auto-creating groups
Given I set the field "Include only active enrolments" to "0"
And I set the field "Auto create based on" to "Members per group"
When I set the field "Group/member count" to "11"
And I press "Preview"
Then I should see "Suspended student 11 (suspendedstudent11@example.com)"
Scenario: Do not display 'Include only active enrolments' if user does not have the 'moodle/course:viewsuspendedusers' capability
Given I log out
And the following "role capability" exists:
| role | editingteacher |
| moodle/course:viewsuspendedusers | prevent |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
When I press "Auto-create groups"
Then I should not see "Include only active enrolments"
And I set the field "Group/member count" to "11"
And I press "Preview"
And I should not see "Suspended Student 11"
@javascript
Scenario: Auto-create groups with group messaging
Given I set the following fields to these values:
| Naming scheme | Group @ |
| Auto create based on | Number of groups |
| Group/member count | 2 |
| Grouping of auto-created groups | No grouping |
| Group messaging | Yes |
And I press "Submit"
And I set the field "groups" to "Group A"
When I press "Edit group settings"
Then I should see "Yes" in the "Group messaging" "select"
And I press "Cancel"
And I set the field "groups" to "Group B"
And I press "Edit group settings"
And I should see "Yes" in the "Group messaging" "select"
@@ -0,0 +1,50 @@
@core @core_group
Feature: Backup and restore a course containing groups
In order to transfer groups to another course
As a teacher
I want to backup and restore a course retaining the groups
Background:
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion | numsections |
| Course 1 | C1 | topics | 1 | 3 |
And the following "users" exist:
| username | firstname | lastname |
| teacher1 | Teacher | Teacher |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "groups" exist:
| name | course | idnumber | visibility | participation |
| Visible/Participation | C1 | VP | 0 | 1 |
| Only visible to members/Participation | C1 | MP | 1 | 1 |
| Only see own membership | C1 | O | 2 | 0 |
| Not visible | C1 | N | 3 | 0 |
| Visible/Non-Participation | C1 | VN | 0 | 0 |
| Only visible to members/Non-Participation | C1 | MN | 1 | 0 |
And the following config values are set as admin:
| enableasyncbackup | 0 |
And I log in as "admin"
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Schema | Course name | Restored course |
@javascript
Scenario Outline: Check restored groups
Given I am on the "Restored course copy 1" "groups" page logged in as teacher1
When I set the field "Groups" to "<group>"
And I press "Edit group settings"
Then the following fields match these values:
| Group ID number | <idnumber> |
| Group membership visibility | <visibility> |
| Show group in dropdown menu for activities in group mode | <participation> |
Examples:
| group | idnumber | visibility | participation |
| Visible/Participation | VP | 0 | 1 |
| Only visible to members/Participation | MP | 1 | 1 |
| Only see own membership | O | 2 | 0 |
| Not visible | N | 3 | 0 |
| Visible/Non-Participation | VN | 0 | 0 |
| Only visible to members/Non-Participation | MN | 1 | 0 |
+86
View File
@@ -0,0 +1,86 @@
<?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/>.
/**
* Behat groups-related steps definitions.
*
* @package core_group
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
* Groups-related steps definitions.
*
* @package core_group
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_groups extends behat_base {
/**
* Add the specified user to the group. You should be in the groups page when running this step. The user should be specified like "Firstname Lastname (user@example.com)".
*
* @Given /^I add "(?P<user_fullname_string>(?:[^"]|\\")*)" user to "(?P<group_name_string>(?:[^"]|\\")*)" group members$/
* @throws ElementNotFoundException Thrown by behat_base::find
* @param string $username
* @param string $groupname
*/
public function i_add_user_to_group_members($userfullname, $groupname) {
// Select the group in the select.
$this->execute('behat_forms::i_set_the_field_to', [get_string('groups', 'core'), $this->escape($groupname)]);
// Press "Add/remove users".
$this->execute('behat_general::i_click_on', [get_string('adduserstogroup', 'group'), "button"]);
// Select the user.
$this->execute('behat_forms::i_set_the_field_to', ["addselect", $this->escape($userfullname)]);
// Click add button.
$this->execute('behat_general::i_click_on', [get_string('add', 'core'), "button"]);
// Returning to the main groups page.
$this->execute('behat_general::i_click_on', [get_string('backtogroups', 'group'), "button"]);
}
/**
* A single or comma-separated list of groups expected within a grouping, on group overview page.
*
* @Given /^the group overview should include groups "(?P<groups_string>(?:[^"]|\\")*)" in grouping "(?P<grouping_string>(?:[^"]|\\")*)"$/
* @param string $groups one or comma seperated list of groups.
* @param string $grouping grouping in which all group should be present.
*/
public function the_groups_overview_should_include_groups_in_grouping($groups, $grouping) {
$groups = array_map('trim', explode(',', $groups));
foreach ($groups as $groupname) {
// Find the table after the H3 containing the grouping name, then look for the group name in the first column.
$xpath = "//h3[normalize-space(.) = '{$grouping}']/following-sibling::div[contains(@class, 'table-responsive')]" .
"/table//tr//td[contains(concat(' ', normalize-space(@class), ' '), ' c0 ')][normalize-space(.) = '{$groupname}' ]";
$this->execute('behat_general::should_exist', array($xpath, 'xpath_element'));
}
}
}
+58
View File
@@ -0,0 +1,58 @@
@core @core_group
Feature: Bulk update group messaging status
In order to update group messaging settings in bulk
As a teacher
I need to be able to select the groups and update their messaging settings using the buttons provided.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "groups" exist:
| course | name | idnumber |
| C1 | Group-A-Test | GA |
| C1 | Group-B-Test | GB |
And I am on the "Course 1" "groups" page logged in as "teacher1"
@javascript
Scenario: Bulk enable messaging in groups
Given I set the field "groups" to "Group-A-Test (0)"
And I press "Edit group settings"
And I set the field "id_enablemessaging" to "0"
And I press "Save changes"
And I wait until the page is ready
And the field "groups" matches value "Group-A-Test (0)"
And I press "Enable messaging"
And I wait until the page is ready
And I should see "Successfully enabled messaging in 1 group(s)"
And I set the field "groups" to "Group-A-Test (0)"
And I press "Edit group settings"
Then the field "id_enablemessaging" matches value "1"
@javascript
Scenario: Bulk disable messaging in groups
Given I set the field "groups" to "Group-A-Test (0)"
And I press "Edit group settings"
And I set the field "id_enablemessaging" to "1"
And I press "Save changes"
And I wait until the page is ready
And the field "groups" matches value "Group-A-Test (0)"
And I press "Disable messaging"
And I wait until the page is ready
And I should see "Successfully disabled messaging in 1 group(s)"
And I set the field "groups" to "Group-A-Test (0)"
And I press "Edit group settings"
Then the field "id_enablemessaging" matches value "0"
@javascript
Scenario: Messaging buttons are enabled when a group is selected
Given I set the field "groups" to "Group-A-Test (0)"
Then the field "groups" matches value "Group-A-Test (0)"
And the "Enable messaging" "button" should be enabled
And the "Disable messaging" "button" should be enabled
+153
View File
@@ -0,0 +1,153 @@
@core @core_group
Feature: Organize students into groups
In order to organize course activities in groups
As a teacher
I need to group students
@javascript
Scenario: Assign students to groups
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student0 | Student | 0 | student0@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student0 | C1 | student |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group 1 |
And I press "Save changes"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group 2 |
And I press "Save changes"
When I add "Student 0 (student0@example.com)" user to "Group 1" group members
And I add "Student 1 (student1@example.com)" user to "Group 1" group members
And I add "Student 2 (student2@example.com)" user to "Group 2" group members
And I add "Student 3 (student3@example.com)" user to "Group 2" group members
Then I set the field "groups" to "Group 1 (2)"
And the "members" select box should contain "Student 0 (student0@example.com)"
And the "members" select box should contain "Student 1 (student1@example.com)"
And the "members" select box should not contain "Student 2 (student2@example.com)"
And the "members" select box should not contain "Student 3 (student3@example.com)"
And I set the field "groups" to "Group 2 (2)"
And the "members" select box should contain "Student 2 (student2@example.com)"
And the "members" select box should contain "Student 3 (student3@example.com)"
And the "members" select box should not contain "Student 0 (student0@example.com)"
And the "members" select box should not contain "Student 1 (student1@example.com)"
And I navigate to course participants
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group 1"
And I click on "Apply filters" "button"
And I should see "Student 0"
And I should see "Student 1"
And I should not see "Student 2"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group 2"
And I click on "Apply filters" "button"
And I should see "Student 2"
And I should see "Student 3"
And I should not see "Student 0"
@javascript
Scenario: Assign students to groups with site user identity configured
Given the following "courses" exist:
| fullname | shortname | groupmode |
| Course 1 | C1 | 1 |
And the following "users" exist:
| username | firstname | lastname | email | country |
| teacher | Teacher | 1 | teacher@example.com | GB |
| student | Student | 1 | student@example.com | DE |
And the following "course enrolments" exist:
| user | course | role |
| teacher | C1 | editingteacher |
| student | C1 | student |
And the following config values are set as admin:
| showuseridentity | email,country |
And I log in as "teacher"
And I am on the "Course 1" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group 1 |
And I press "Save changes"
When I add "Student 1 (student@example.com, DE)" user to "Group 1" group members
And I set the field "groups" to "Group 1 (1)"
Then the "members" select box should contain "Student 1 (student@example.com\, DE)"
# Non-AJAX version of the groups page.
And I press "Add/remove users"
And I press "Back to groups"
And the "members" select box should contain "Student 1 (student@example.com\, DE)"
Scenario: Create groups and groupings without the 'moodle/course:changeidnumber' capability
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "role capability" exists:
| role | editingteacher |
| moodle/course:changeidnumber | prevent |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
When I press "Create group"
Then the "idnumber" "field" should be readonly
And I set the following fields to these values:
| Group name | The greatest group that never existed |
And I press "Save changes"
And I should see "The greatest group that never existed"
And I am on the "Course 1" "groupings" page
And I press "Create grouping"
And the "idnumber" "field" should be readonly
And I set the following fields to these values:
| Grouping name | Not the greatest grouping, but it's ok! |
And I press "Save changes"
And I should see "Not the greatest grouping, but it's ok!"
Scenario: Create groups with enrolment key
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
| Course 2 | C2 | 0 | 1 |
And I log in as "admin"
And I am on the "Course 1" "groups" page
When I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
| Enrolment key | badpasswd |
And I press "Save changes"
And I should see "Passwords must have at least 1 digit(s)"
And I set the following fields to these values:
| Group name | Group A |
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group B |
| Enrolment key | Abcdef-1 |
And I press "Save changes"
Then I should see "This enrolment key is already used for another group."
And I set the following fields to these values:
| Enrolment key | Abcdef-2 |
And I press "Save changes"
And the "groups" select box should contain "Group B (0)"
And I am on the "Course 2" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
| Enrolment key | Abcdef-1 |
And I should not see "This enrolment key is already used for another group."
+74
View File
@@ -0,0 +1,74 @@
@core @core_group
Feature: Custom profile fields in groups
In order to organize participants into groups
As a teacher
I need to be able to view and search on custom profile fields
Background:
Given the following "custom profile fields" exist:
| datatype | shortname | name | param2 |
| text | species | Species | 255 |
And the following "users" exist:
| username | firstname | lastname | profile_field_species | email |
| user1 | Robin | Hood | fox | email1@example.org |
| user2 | Little | John | bear | email2@example.org |
And the following "courses" exist:
| shortname | fullname |
| C1 | Course 1 |
And the following "course enrolments" exist:
| user | course | role |
| user1 | C1 | manager |
| user2 | C1 | manager |
And the following "groups" exist:
| name | course | idnumber |
| Canines | C1 | G1 |
And the following "group members" exist:
| user | group |
| user1 | G1 |
Given the following config values are set as admin:
| showuseridentity | username,profile_field_species |
@javascript
Scenario: Check the custom profile fields show up and can be searched on
When I am logged in as "admin"
And I am on the "Course 1" "groups" page
# Check the Overview page.
And I set the field "Participants tertiary navigation" to "Overview"
And "Robin Hood (user1, fox)" "text" should exist in the "Canines" "table_row"
And "Little John (user2, bear)" "text" should exist in the "No group" "table_row"
# Check the groups page.
And I set the field "Participants tertiary navigation" to "Groups"
And I set the field "groups" to "Canines"
And I should see "Robin Hood (user1, fox)"
And I should not see "Little John (user2, bear)"
# Check the members page.
And I press "Add/remove users"
And I should see "Robin Hood (user1, fox)"
And I should see "Little John (user2, bear)"
And I set the field "addselect" to "Little John (user2, bear)"
And I press "Add"
And I should see "Robin Hood (user1, fox)"
And I should see "Little John (user2, bear)"
And I set the field "Search" in the "#existingcell" "css_element" to "fox"
And I wait "1" seconds
And I should see "Robin Hood (user1, fox)"
And I should not see "Little John (user2, bear)"
And I set the field "Search" in the "#existingcell" "css_element" to ""
And I wait "1" seconds
And I set the field "removeselect" to "Little John (user2, bear)"
And I press "Remove"
And I set the field "removeselect" to "Robin Hood (user1, fox)"
And I press "Remove"
And I should see "Robin Hood (user1, fox)"
And I should see "Little John (user2, bear)"
And I set the field "Search" in the "#potentialcell" "css_element" to "bear"
And I wait "1" seconds
And I should see "Little John (user2, bear)"
And I should not see "Robin Hood (user1, fox)"
+62
View File
@@ -0,0 +1,62 @@
@core @core_group
Feature: Automatic deletion of groups and groupings
In order to check the expected results occur when deleting groups and groupings in different scenarios
As a teacher
I need to create groups and groupings under different scenarios and check that the expected result occurs when attempting to delete them.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "groups" exist:
| course | name | idnumber |
| C1 | Group (without ID) | |
| C1 | Group (with ID) | An ID |
And the following "groupings" exist:
| course | name | idnumber |
| C1 | Grouping (without ID) | |
| C1 | Grouping (with ID) | An ID |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page logged in as "teacher1"
@javascript
Scenario: Delete groups and groupings with and without ID numbers
Given I set the field "groups" to "Group (without ID) (0)"
And I press "Delete"
And I press "Yes"
Then the "groups" select box should not contain "Group (without ID) (0)"
And I set the field "groups" to "Group (with ID) (0)"
And I press "Delete"
And I press "Yes"
And the "groups" select box should not contain "Group (with ID) (0)"
And I set the field "Participants tertiary navigation" to "Groupings"
And I click on "Delete" "link" in the "Grouping (without ID)" "table_row"
And I press "Yes"
And I should not see "Grouping (without ID)"
And I click on "Delete" "link" in the "Grouping (with ID)" "table_row"
And I press "Yes"
And I should not see "Grouping (with ID)"
@javascript @skip_chrome_zerosize
Scenario: Delete groups and groupings with and without ID numbers without the 'moodle/course:changeidnumber' capability
Given the following "role capability" exists:
| role | editingteacher |
| moodle/course:changeidnumber | prevent |
And I am on the "Course 1" "groups" page
When I set the field "groups" to "Group (with ID) (0)"
Then the "Delete" "button" should be disabled
And I set the field "groups" to "Group (without ID) (0)"
And I press "Delete"
And I press "Yes"
And I should not see "Group (without ID)"
And I set the field "Participants tertiary navigation" to "Groupings"
And "Delete" "link" should not exist in the "Grouping (with ID)" "table_row"
And I click on "Delete" "link" in the "Grouping (without ID)" "table_row"
And I press "Yes"
And I should not see "Grouping (without ID)"
@@ -0,0 +1,73 @@
@core @core_group @core_customfield @javascript
Feature: Add and use group custom fields
In order to store an extra information about groups
As an admin
I need to create group customs fields and be able to populate them on group creation
Background:
Given the following "custom field categories" exist:
| name | component | area | itemid |
| Category for group1 | core_group | group | 0 |
| Category for grouping1 | core_group | grouping | 0 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
Scenario: Create a new group custom field and use the field for a new group
When I log in as "admin"
And I navigate to "Courses > Groups > Group custom fields" in site administration
Then I should see "Category for group1"
And I click on "Add a new custom field" "link"
And I click on "Short text" "link"
And I set the following fields to these values:
| Name | Test field |
| Short name | testfield |
And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue"
Then the following should exist in the "generaltable" table:
| Custom field | Short name | Type |
| Test field | testfield | Short text |
Then I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Create group"
Then I should see "Category for group1"
And I should see "Test field"
And I set the following fields to these values:
| Group name | My new group |
| Test field | Custom field text |
And I press "Save changes"
Then the "groups" select box should contain "My new group (0)"
And I set the field "groups" to "My new group (0)"
And I press "Edit group settings"
And the field "Test field" matches value "Custom field text"
Scenario: Create a new grouping custom field and use the field for a new grouping
When I log in as "admin"
And I navigate to "Courses > Groups > Grouping custom fields" in site administration
Then I should see "Category for grouping1"
And I click on "Add a new custom field" "link"
And I click on "Short text" "link"
And I set the following fields to these values:
| Name | Test field |
| Short name | testfield |
And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue"
Then the following should exist in the "generaltable" table:
| Custom field | Short name | Type |
| Test field | testfield | Short text |
Then I log in as "teacher1"
And I am on the "Course 1" "groupings" page
And I press "Create grouping"
Then I should see "Category for grouping1"
And I should see "Test field"
And I set the following fields to these values:
| Grouping name | My new grouping |
| Test field | Custom field text |
And I press "Save changes"
Then I should see "My new grouping"
And I click on "Edit" "link" in the "My new grouping" "table_row"
And the field "Test field" matches value "Custom field text"
@@ -0,0 +1,117 @@
@core @core_group
Feature: The description and picture of a group can be viewed by students and teachers
In order to view the description and picture of a group
As a teacher
I need to create groups and add descriptions and picture to them.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
@javascript @_file_upload
Scenario: A student can see the group description and picture when visible groups are set. Teachers can see group details.
Given I am on the "Course 1" "course editing" page logged in as "teacher1"
And I set the following fields to these values:
| Group mode | Visible groups |
And I press "Save and display"
And I am on the "Course 1" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
| Group description | Description for Group A |
# Upload group picture
And I upload "lib/tests/fixtures/gd-logo.png" file to "New picture" filemanager
And I press "Save changes"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group B |
And I press "Save changes"
And I add "Student 1 (student1@example.com)" user to "Group A" group members
And I add "Student 2 (student2@example.com)" user to "Group B" group members
And I navigate to course participants
And I click on "Student 1" "link" in the "participants" "table"
And I click on "Group A" "link"
And I should see "Description for Group A"
# As teacher, confirm that group picture is displayed
And "//img[@class='grouppicture']" "xpath_element" should exist
And ".groupinfobox" "css_element" should exist
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
And I click on "Apply filters" "button"
And I click on "Student 2" "link" in the "participants" "table"
And I click on "Group B" "link"
And I should see "Student 2" in the "participants" "table"
And ".groupinfobox" "css_element" should not exist
When I am on the "Course 1" course page logged in as student1
And I navigate to course participants
And I click on "Student 1" "link" in the "participants" "table"
And I click on "Group A" "link"
# As student, confirm that group description and picture is displayed
Then I should see "Description for Group A"
And "//img[@class='grouppicture']" "xpath_element" should exist
And I am on the "Course 1" course page logged in as student2
And I navigate to course participants
And I click on "Student 2" "link" in the "participants" "table"
And I click on "Group B" "link"
And I should see "Student 2" in the "participants" "table"
And ".groupinfobox" "css_element" should not exist
@javascript @_file_upload
Scenario: A student can not see the group description and picture when separate groups are set. Teachers can see group details.
Given I am on the "Course 1" "course editing" page logged in as "teacher1"
And I set the following fields to these values:
| Group mode | Separate groups |
And I press "Save and display"
And I am on the "Course 1" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
| Group description | Description for Group A |
# Upload group picture
And I upload "lib/tests/fixtures/gd-logo.png" file to "New picture" filemanager
And I press "Save changes"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group B |
And I press "Save changes"
And I add "Student 1 (student1@example.com)" user to "Group A" group members
And I add "Student 2 (student2@example.com)" user to "Group B" group members
And I navigate to course participants
And I click on "Student 1" "link" in the "participants" "table"
And I click on "Group A" "link"
And I should see "Description for Group A"
# As teacher, confirm that group picture is displayed
And "//img[@class='grouppicture']" "xpath_element" should exist
And ".groupinfobox" "css_element" should exist
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
And I click on "Apply filters" "button"
And I click on "Student 2" "link" in the "participants" "table"
And I click on "Group B" "link"
And ".groupinfobox" "css_element" should not exist
When I am on the "Course 1" course page logged in as student1
And I navigate to course participants
And I click on "Student 1" "link" in the "participants" "table"
And I click on "Group A" "link"
And I should see "Student 1" in the "participants" "table"
# As student, confirm that group description and picture are not displayed
Then I should not see "Description for Group A"
And "//img[@class='grouppicture']" "xpath_element" should not exist
And ".groupinfobox" "css_element" should not exist
When I am on the "Course 1" course page logged in as student2
And I navigate to course participants
And I click on "Student 2" "link" in the "participants" "table"
And I click on "Group B" "link"
And I should see "Student 2" in the "participants" "table"
And ".groupinfobox" "css_element" should not exist
+180
View File
@@ -0,0 +1,180 @@
@core @core_group @_file_upload
Feature: Importing of groups and groupings
In order to import groups and grouping
As a teacher
I need to upload a file and verify groups and groupings can be imported
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
@javascript
Scenario: Import groups and groupings as teacher
Given I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Import groups"
When I upload "group/tests/fixtures/groups_import.csv" file to "Import" filemanager
And I press "Import groups"
And I press "Continue"
Then I should see "group-id-1"
And I should see "group-id-2"
And I should see "group-id-1-duplicate"
And I should see "group-noid-1"
And I should see "group-noid-2"
# Group messaging should have been enabled for group-id-1.
And I set the field "groups" to "group-id-1"
And I press "Edit group settings"
And I should see "Yes" in the "Group messaging" "select"
And I press "Cancel"
# Group messaging should not have been enabled for group-id-2.
And I set the field "groups" to "group-id-2"
And I press "Edit group settings"
And I should see "No" in the "Group messaging" "select"
And I press "Cancel"
# Check groupings
And I set the field "Participants tertiary navigation" to "Groupings"
And I should see "Grouping-1"
And I should see "Grouping-2"
And I should see "Grouping-3"
And I should see "group-id-1" in the "Grouping-1" "table_row"
And I should see "group-id-2" in the "Grouping-2" "table_row"
And I should see "group-noid-2" in the "Grouping-2" "table_row"
And I should see "group-id-1-duplicate" in the "Grouping-3" "table_row"
And I should see "group-noid-1" in the "Grouping-3" "table_row"
@javascript
Scenario: Import groups with idnumber when the user has proper permissions for the idnumber field
Given I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Import groups"
When I upload "group/tests/fixtures/groups_import.csv" file to "Import" filemanager
And I press "Import groups"
Then I should see "Group group-id-1 added successfully"
And I should see "Group group-id-2 added successfully"
And I should see "group-id-1-duplicate: Group \"group-id-1\" with an idnumber of \"group-id-1\" already exists for this course"
And I should see "Group group-id-1-duplicate added successfully"
And I should see "Group group-noid-1 added successfully"
And I should see "Group group-noid-2 added successfully"
And I press "Continue"
And I set the field "groups" to "group-id-1"
And I press "Edit group settings"
And the field "id_idnumber" matches value "group-id-1"
And I press "Cancel"
And I set the field "groups" to "group-id-2"
And I press "Edit group settings"
And the field "id_idnumber" matches value "group-id-2"
And I press "Cancel"
And I set the field "groups" to "group-id-1-duplicate"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
And I set the field "groups" to "group-noid-1"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
And I set the field "groups" to "group-noid-2"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
@javascript
Scenario: Import groups with idnumber when the user does not have proper permissions for the idnumber field
Given I log in as "admin"
And I am on the "Course 1" "permissions" page
And I override the system permissions of "Teacher" role with:
| moodle/course:changeidnumber | Prevent |
And I log out
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Import groups"
When I upload "group/tests/fixtures/groups_import.csv" file to "Import" filemanager
And I press "Import groups"
And I press "Continue"
Then I set the field "groups" to "group-id-1"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
And I set the field "groups" to "group-id-2"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
And I set the field "groups" to "group-id-1-duplicate"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
And I set the field "groups" to "group-noid-1"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
And I set the field "groups" to "group-noid-2"
And I press "Edit group settings"
And the field "id_idnumber" matches value ""
And I press "Cancel"
@javascript
Scenario: Import groups into multiple courses as a teacher
Given I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Import groups"
When I upload "group/tests/fixtures/groups_import_multicourse.csv" file to "Import" filemanager
And I press "Import groups"
Then I should see "Group group7 added successfully"
And I should see "Unknown course named \"C-non-existing\""
And I should see "Group group8 added successfully"
And I should not see "group-will-not-be-created"
And I should see "Group group9 added successfully"
And I should see "Group group10 added successfully"
And I press "Continue"
And I should see "group10"
And I should see "group7"
And I should see "group8"
And I should not see "group9"
And I should not see "group-will-not-be-created"
And I am on the "Course 2" "groups" page
And I should see "group9"
And I should not see "group-will-not-be-created"
And I should not see "group7"
And I should not see "group8"
And I should not see "group10"
And I log out
@javascript
Scenario: Import groups with custom field
Given the following "custom field categories" exist:
| name | component | area | itemid |
| Category for group1 | core_group | group | 0 |
| Category for grouping1 | core_group | grouping | 0 |
And the following "custom fields" exist:
| name | category | type | shortname |
| Test Field1 | Category for group1 | text | groupfield1 |
| Test Field2 | Category for grouping1 | text | groupingfield1 |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Import groups"
When I upload "group/tests/fixtures/groups_import_with_customfield.csv" file to "Import" filemanager
And I press "Import groups"
Then I should see "Group Group1 added successfully"
And I should see "Group Group2 added successfully"
And I should see "Grouping Grouping1 added successfully"
And I press "Continue"
And I set the field "groups" to "Group1 (0)"
And I press "Edit group settings"
And the field "Test Field1" matches value "Group1-Custom"
And I press "Cancel"
And I set the field "groups" to "Group2 (0)"
And I press "Edit group settings"
And the field "Test Field1" matches value "Group2-Custom"
And I press "Cancel"
And I am on the "Course 1" "groupings" page
Then I should see "Grouping1"
And I click on "Edit" "link" in the "Grouping1" "table_row"
And the field "Test Field2" matches value "Grouping1-Custom"
+63
View File
@@ -0,0 +1,63 @@
@core @core_group
Feature: Uniqueness of Group ID number
In order to create unique groups and groupings
As a teacher
I need to create groups with unique identificators
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
Scenario: Group ID number uniqueness
Given I am on the "Course 1" "groups" page logged in as "teacher1"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group 1 |
| Group ID number | G1 |
And I press "Save changes"
When I press "Create group"
And I set the following fields to these values:
| Group name | Group 2 |
| Group ID number | G1 |
And I press "Save changes"
Then I should see "This ID number is already taken"
And I set the following fields to these values:
| Group ID number | G2 |
And I press "Save changes"
And I set the field "groups" to "Group 1 (0)"
And I press "Edit group settings"
And I set the following fields to these values:
| Group ID number | G2 |
And I press "Save changes"
And I should see "This ID number is already taken"
And I press "Cancel"
Scenario: Grouping ID number uniqueness
Given I am on the "Course 1" "groupings" page logged in as "teacher1"
And I press "Create grouping"
And I set the following fields to these values:
| Grouping name | Grouping 1 |
| Grouping ID number | GG1 |
And I press "Save changes"
When I press "Create grouping"
And I set the following fields to these values:
| Grouping name | Grouping 2 |
| Grouping ID number | GG1 |
And I press "Save changes"
Then I should see "This ID number is already taken"
And I set the following fields to these values:
| Grouping ID number | GG2 |
And I press "Save changes"
And I click on "Edit" "link" in the "Grouping 1" "table_row"
And I set the following fields to these values:
| Grouping ID number | GG2 |
And I press "Save changes"
And I should see "This ID number is already taken"
And I press "Cancel"
+199
View File
@@ -0,0 +1,199 @@
@core @core_group
Feature: Group overview
In order to view an overview of the groups
As a teacher
I need to visit the group overview page
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student0 | Student | 0 | student0@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
| student4 | Student | 4 | student4@example.com |
| student5 | Student | 5 | student5@example.com |
| student6 | Student | 6 | student6@example.com |
| student7 | Student | 7 | student7@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student0 | C1 | student |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| student4 | C1 | student |
| student5 | C1 | student |
| student6 | C1 | student |
| student7 | C1 | student |
And the following "groups" exist:
| name | course | idnumber |
| Group 1 | C1 | G1 |
| Group 2 | C1 | G2 |
| Group 3 | C1 | G3 |
| Group 4 | C1 | G4 |
And the following "group members" exist:
| user | group |
| student0 | G1 |
| student1 | G1 |
| student2 | G2 |
| student3 | G3 |
| student4 | G3 |
| student5 | G4 |
And the following "groupings" exist:
| name | course | idnumber |
| Grouping 1 | C1 | GG1 |
| Grouping 2 | C1 | GG2 |
And the following "grouping groups" exist:
| grouping | group |
| GG1 | G1 |
| GG1 | G2 |
| GG2 | G2 |
| GG2 | G3 |
Scenario: Filter the overview in various different ways
Given I am on the "Course 1" "groups overview" page logged in as "teacher1"
# Grouping All and Group All filter
When I select "All" from the "Grouping" singleselect
And I select "All" from the "group" singleselect
# Following groups should exist in groupings.
Then the group overview should include groups "Group 1, Group 2" in grouping "Grouping 1"
And the group overview should include groups "Group 2,Group 3" in grouping "Grouping 2"
And the group overview should include groups "Group 4" in grouping "Not in a grouping"
And the group overview should include groups "No group" in grouping "Not in a group"
# Following members should exit in group.
And "Student 0" "text" should exist in the "Group 1" "table_row"
And "Student 1" "text" should exist in the "Group 1" "table_row"
And "Student 2" "text" should exist in the "Group 2" "table_row"
And "Student 3" "text" should exist in the "Group 3" "table_row"
And "Student 4" "text" should exist in the "Group 3" "table_row"
And "Student 5" "text" should exist in the "Group 4" "table_row"
And "Student 6" "text" should exist in the "No group" "table_row"
And "Student 7" "text" should exist in the "No group" "table_row"
# Grouping 1 and Group All filter
And I select "Grouping 1" from the "Grouping" singleselect
And I select "All" from the "group" singleselect
# Following groups should exist in groupings.
And the group overview should include groups "Group 1, Group 2" in grouping "Grouping 1"
# Following groups should not exits
And "Group 3" "table_row" should not exist
And "No group" "table_row" should not exist
# Following members should exit in group.
And "Student 0" "text" should exist in the "Group 1" "table_row"
And "Student 1" "text" should exist in the "Group 1" "table_row"
And "Student 2" "text" should exist in the "Group 2" "table_row"
# Following members should not exit in group.
And I should not see "Student 3"
And I should not see "Student 4"
And I should not see "Student 5"
And I should not see "Student 6"
And I should not see "Student 7"
# Grouping 2 and Group All filter
And I select "Grouping 2" from the "Grouping" singleselect
And I select "All" from the "group" singleselect
# Following groups should exist in groupings.
And the group overview should include groups "Group 2, Group 3" in grouping "Grouping 2"
# Following groups should not exits
And "Group 1" "table_row" should not exist
And "No group" "table_row" should not exist
# Following members should exit in group.
And "Student 2" "text" should exist in the "Group 2" "table_row"
And "Student 3" "text" should exist in the "Group 3" "table_row"
And "Student 4" "text" should exist in the "Group 3" "table_row"
# Following members should not exit in group.
And I should not see "Student 0"
And I should not see "Student 1"
And I should not see "Student 5"
And I should not see "Student 6"
And I should not see "Student 7"
# No grouping and Group All filter
And I select "No grouping" from the "Grouping" singleselect
And I select "All" from the "group" singleselect
# Following groups should exist in groupings.
And the group overview should include groups "Group 4" in grouping "Not in a grouping"
And the group overview should include groups "No group" in grouping "Not in a group"
# Following groups should not exits
And "Group 1" "table_row" should not exist
And "Group 2" "table_row" should not exist
And "Group 3" "table_row" should not exist
# Following members should exit in group.
And "Student 5" "text" should exist in the "Group 4" "table_row"
And "Student 6" "text" should exist in the "No group" "table_row"
And "Student 7" "text" should exist in the "No group" "table_row"
# Following members should not exit in group.
And I should not see "Student 0"
And I should not see "Student 1"
And I should not see "Student 2"
And I should not see "Student 3"
And I should not see "Student 4"
# Grouping All and Group 1 filter
And I select "All" from the "Grouping" singleselect
And I select "Group 1" from the "group" singleselect
# Following groups should exist in groupings.
And the group overview should include groups "Group 1" in grouping "Grouping 1"
# Following groups should not exits
And "Group 2" "table_row" should not exist
And "Group 3" "table_row" should not exist
And "Group 4" "table_row" should not exist
And "No group" "table_row" should not exist
# Following members should exit in group.
And "Student 0" "text" should exist in the "Group 1" "table_row"
And "Student 1" "text" should exist in the "Group 1" "table_row"
# Following members should not exit in group.
And I should not see "Student 2"
And I should not see "Student 3"
And I should not see "Student 4"
And I should not see "Student 5"
And I should not see "Student 6"
And I should not see "Student 7"
# Grouping All and Group 2 filter
And I select "All" from the "Grouping" singleselect
And I select "Group 2" from the "group" singleselect
# Following groups should exist in groupings.
And the group overview should include groups "Group 2" in grouping "Grouping 1"
And the group overview should include groups "Group 2" in grouping "Grouping 2"
# Following groups should not exits
And "Group 1" "table_row" should not exist
And "Group 3" "table_row" should not exist
And "Group 4" "table_row" should not exist
And "No group" "table_row" should not exist
# Following members should exit in group.
And "Student 2" "text" should exist in the "Group 2" "table_row"
# Following members should not exit in group.
And I should not see "Student 0"
And I should not see "Student 1"
And I should not see "Student 3"
And I should not see "Student 4"
And I should not see "Student 5"
And I should not see "Student 6"
And I should not see "Student 7"
# Grouping All and No group filter
And I select "All" from the "Grouping" singleselect
And I select "No group" from the "group" singleselect
# Following groups should exist in groupings.
And the group overview should include groups "No group" in grouping "Not in a group"
# Following groups should not exits
And "Group 1" "table_row" should not exist
And "Group 2" "table_row" should not exist
And "Group 3" "table_row" should not exist
And "Group 4" "table_row" should not exist
# Following members should exit in group.
And "Student 6" "text" should exist in the "No group" "table_row"
And "Student 7" "text" should exist in the "No group" "table_row"
# Following members should not exit in group.
And I should not see "Student 0"
And I should not see "Student 1"
And I should not see "Student 2"
And I should not see "Student 3"
And I should not see "Student 4"
And I should not see "Student 5"
+176
View File
@@ -0,0 +1,176 @@
@core @core_group
Feature: Private groups
As a teacher
In order to organise students into groups while protecting their privacy
I want to define groups that are not visible to all students
Background:
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion | numsections |
| Course 1 | C1 | topics | 1 | 3 |
And the following "users" exist:
| username | firstname | lastname |
| teacher1 | Teacher | Teacher |
| student1 | Student | 1 |
| student2 | Student | 2 |
| student3 | Student | 3 |
| student4 | Student | 4 |
| student5 | Student | 5 |
| student6 | Student | 6 |
| student7 | Student | 7 |
| student8 | Student | 8 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| student4 | C1 | student |
| student5 | C1 | student |
| student6 | C1 | student |
| student7 | C1 | student |
| student8 | C1 | student |
And the following "groups" exist:
| name | course | idnumber | visibility | participation |
| Visible/Participation | C1 | VP | 0 | 1 |
| Only visible to members/Participation | C1 | MP | 1 | 1 |
| Only see own membership | C1 | O | 2 | 0 |
| Not visible | C1 | N | 3 | 0 |
| Visible/Non-Participation | C1 | VN | 0 | 0 |
| Only visible to members/Non-Participation | C1 | MN | 1 | 0 |
And the following "group members" exist:
| user | group |
| student1 | VP |
| student1 | VN |
| student2 | MP |
| student2 | MN |
| student3 | O |
| student4 | N |
| student5 | VP |
| student5 | VN |
| student6 | MP |
| student6 | MN |
| student7 | O |
| student8 | N |
Scenario: Participants in "Visible" groups see their membership and other members:
Given I am on the "C1" "enrolled users" page logged in as "student1"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 1 | Visible/Non-Participation, Visible/Participation |
| Student 2 | No groups |
| Student 3 | No groups |
| Student 4 | No groups |
| Student 5 | Visible/Non-Participation, Visible/Participation |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
Scenario: Participants in "Only visible to members" groups see their membership and other members, plus "Visible"
Given I am on the "C1" "enrolled users" page logged in as "student2"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 1 | Visible/Non-Participation, Visible/Participation |
| Student 2 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 3 | No groups |
| Student 4 | No groups |
| Student 5 | Visible/Non-Participation, Visible/Participation |
| Student 6 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 7 | No groups |
| Student 8 | No groups |
Scenario: Participants in "Only see own membership" groups see their membership but not other members, plus "Visible"
Given I am on the "C1" "enrolled users" page logged in as "student3"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 1 | Visible/Non-Participation, Visible/Participation |
| Student 2 | No groups |
| Student 3 | Only see own membership |
| Student 4 | No groups |
| Student 5 | Visible/Non-Participation, Visible/Participation |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
Scenario: Participants in "Not visible" groups do not see that group, do see "Visible"
Given I am on the "C1" "enrolled users" page logged in as "student4"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 1 | Visible/Non-Participation, Visible/Participation |
| Student 2 | No groups |
| Student 3 | No groups |
| Student 4 | No groups |
| Student 5 | Visible/Non-Participation, Visible/Participation |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
Scenario: View participants list as a teacher:
Given I am on the "C1" "enrolled users" page logged in as "teacher1"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 1 | Visible/Non-Participation, Visible/Participation |
| Student 2 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 3 | Only see own membership |
| Student 4 | Not visible |
| Student 5 | Visible/Non-Participation, Visible/Participation |
| Student 6 | Only visible to members/Non-Participation, Only visible to members/Participation |
| Student 7 | Only see own membership |
| Student 8 | Not visible |
@javascript
Scenario: Filtering by "Only see own membership" groups should not show other members.
Given I am on the "C1" "enrolled users" page logged in as "student3"
When I set the field "type" to "Groups"
And I set the field "Type or select..." to "Only see own membership"
And I click on "Apply filters" "button"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 3 | Only see own membership |
And the following should not exist in the "participants" table:
| First name | Groups |
| Student 7 | No groups |
@javascript
Scenario: Filtering by "No group" should show all users whose memberships I cannot see
Given I am on the "C1" "enrolled users" page logged in as "student3"
When I set the field "type" to "Groups"
And I set the field "Type or select..." to "No group"
And I click on "Apply filters" "button"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 2 | No groups |
| Student 4 | No groups |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
@javascript
Scenario: Filtering by not a member of "Only see own membership" groups I am a member of should show everyone except me
Given I am on the "C1" "enrolled users" page logged in as "student3"
When I set the field "Match" in the "Filter 1" "fieldset" to "None"
And I set the field "type" to "Groups"
And I set the field "Type or select..." to "Only see own membership"
And I click on "Apply filters" "button"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 1 | Visible/Non-Participation, Visible/Participation |
| Student 2 | No groups |
| Student 4 | No groups |
| Student 5 | Visible/Non-Participation, Visible/Participation |
| Student 6 | No groups |
| Student 7 | No groups |
| Student 8 | No groups |
@javascript
Scenario: Filtering by not a member of "No group" should only show users whose memberships I can see
Given I am on the "C1" "enrolled users" page logged in as "student3"
When I set the field "Match" in the "Filter 1" "fieldset" to "None"
And I set the field "type" to "Groups"
And I set the field "Type or select..." to "No group"
And I click on "Apply filters" "button"
Then the following should exist in the "participants" table:
| First name | Groups |
| Student 1 | Visible/Non-Participation, Visible/Participation |
| Student 3 | Only see own membership |
| Student 5 | Visible/Non-Participation, Visible/Participation |
+50
View File
@@ -0,0 +1,50 @@
@core @core_group
Feature: Test role visibility for the groups management page
In order to control access
As an admin
I need to control which roles can see each other
Background: Set up some groups
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "users" exist:
| username | firstname | lastname | email |
| learner1 | Learner | 1 | learner1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
| manager1 | Manager | 1 | manager1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| learner1 | C1 | student |
| teacher1 | C1 | editingteacher |
| manager1 | C1 | manager |
And the following "groups" exist:
| name | course | idnumber |
| Group 1 | C1 | G1 |
And the following "group members" exist:
| user | group |
| learner1 | G1 |
| teacher1 | G1 |
| manager1 | G1 |
Scenario: Check the default roles are visible
Given I log in as "manager1"
And I am on the "Course 1" "groups" page
When I set the field "groups" to "Group 1 (3)"
And I press "Show members for group"
Then "optgroup[label='No roles']" "css_element" should not exist in the "#members" "css_element"
And "optgroup[label='Student']" "css_element" should exist in the "#members" "css_element"
And "optgroup[label='Teacher']" "css_element" should exist in the "#members" "css_element"
And "optgroup[label='Manager']" "css_element" should exist in the "#members" "css_element"
And I log out
Scenario: Do not allow managers to view any roles and check they are hidden
Given I log in as "teacher1"
And I am on the "Course 1" "groups" page
When I set the field "groups" to "Group 1 (3)"
And I press "Show members for group"
Then "optgroup[label='No roles']" "css_element" should exist in the "#members" "css_element"
And "optgroup[label='Student']" "css_element" should exist in the "#members" "css_element"
And "optgroup[label='Teacher']" "css_element" should exist in the "#members" "css_element"
And "optgroup[label='Manager']" "css_element" should not exist in the "#members" "css_element"
And I log out
+159
View File
@@ -0,0 +1,159 @@
@core @core_group
Feature: Automatic updating of groups and groupings
In order to check the expected results occur when updating groups and groupings in different scenarios
As a teacher
I need to create groups and groupings under different scenarios and check that the expected result occurs when attempting to update them.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group (without ID) |
And I press "Save changes"
And I press "Create group"
And I set the following fields to these values:
| Group name | Group (with ID) |
| Group ID number | An ID |
And I press "Save changes"
And I set the field "Participants tertiary navigation" to "Groupings"
And I press "Create grouping"
And I set the following fields to these values:
| Grouping name | Grouping (without ID) |
And I press "Save changes"
And I press "Create grouping"
And I set the following fields to these values:
| Grouping name | Grouping (with ID) |
| Grouping ID number | An ID |
And I press "Save changes"
And I set the field "Participants tertiary navigation" to "Groups"
@javascript
Scenario: Update groups and groupings with ID numbers
Given I set the field "groups" to "Group (with ID)"
And I press "Edit group settings"
And the field "idnumber" matches value "An ID"
And I set the following fields to these values:
| Group name | Group (with ID) (updated) |
| Group ID number | An ID (updated) |
When I press "Save changes"
Then I should see "Group (with ID) (updated)"
And I set the field "groups" to "Group (with ID) (updated)"
And I press "Edit group settings"
And the field "idnumber" matches value "An ID (updated)"
And I press "Save changes"
And I set the field "Participants tertiary navigation" to "Groupings"
And I click on "Edit" "link" in the "Grouping (with ID)" "table_row"
And the field "idnumber" matches value "An ID"
And I set the following fields to these values:
| Grouping name | Grouping (with ID) (updated) |
| Grouping ID number | An ID (updated) |
And I press "Save changes"
And I should see "Grouping (with ID) (updated)"
And I click on "Edit" "link" in the "Grouping (with ID) (updated)" "table_row"
And the field "idnumber" matches value "An ID (updated)"
@javascript @skip_chrome_zerosize
Scenario: Update groups and groupings with ID numbers without the 'moodle/course:changeidnumber' capability
Given the following "role capability" exists:
| role | editingteacher |
| moodle/course:changeidnumber | prevent |
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I set the field "groups" to "Group (with ID)"
When I press "Edit group settings"
Then the "idnumber" "field" should be readonly
And the field "idnumber" matches value "An ID"
And I set the following fields to these values:
| Group name | Group (with ID) (updated) |
And I press "Save changes"
And I should see "Group (with ID) (updated)"
And I set the field "groups" to "Group (with ID) (updated)"
And I press "Edit group settings"
And the "idnumber" "field" should be readonly
And the field "idnumber" matches value "An ID"
And I press "Save changes"
And I set the field "Participants tertiary navigation" to "Groupings"
And I click on "Edit" "link" in the "Grouping (with ID)" "table_row"
And the "idnumber" "field" should be readonly
And the field "idnumber" matches value "An ID"
And I set the following fields to these values:
| Grouping name | Grouping (with ID) (updated) |
And I press "Save changes"
And I should see "Grouping (with ID) (updated)"
And I click on "Edit" "link" in the "Grouping (with ID) (updated)" "table_row"
And the "idnumber" "field" should be readonly
And the field "idnumber" matches value "An ID"
@javascript
Scenario: Update groups with enrolment key
Given the following "courses" exist:
| fullname | shortname |
| Course 2 | C2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C2 | editingteacher |
And I log out
And I log in as "teacher1"
And I am on the "Course 1" "groups" page
And I set the field "groups" to "Group (with ID)"
And I press "Edit group settings"
And I set the following fields to these values:
| Enrolment key | badpasswd |
When I press "Save changes"
Then I should see "Passwords must have at least 1 digit(s)"
And I set the following fields to these values:
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I set the field "groups" to "Group (with ID)"
And I press "Edit group settings"
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
And I set the field "groups" to "Group (without ID)"
And I press "Edit group settings"
And I set the following fields to these values:
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I should see "This enrolment key is already used for another group."
And I set the following fields to these values:
| Enrolment key | Abcdef-2 |
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
And I am on the "Course 2" "groups" page
And I press "Create group"
And I set the following fields to these values:
| Group name | Group A |
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
And I set the field "groups" to "Group A"
And I press "Edit group settings"
And I set the following fields to these values:
| Enrolment key | Abcdef-1 |
And I press "Save changes"
And I should not see "This enrolment key is already used for another group."
@javascript
Scenario: Visibility and Participation settings are locked once a group has members
Given I set the field "groups" to "Group (with ID)"
And I press "Edit group settings"
And "visibility" "select" should exist
And the field "Group membership visibility" matches value "Visible"
And the "participation" "checkbox" should be enabled
And the field "Show group in dropdown menu for activities in group mode" matches value "1"
When the following "group members" exist:
| user | group |
| teacher1 | An ID |
And I reload the page
Then "visibility" "select" should not exist
And "Visible" "text" should exist
And the "participation" "checkbox" should be disabled
And the field "Show group in dropdown menu for activities in group mode" matches value "1"
@@ -0,0 +1,186 @@
<?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_group\customfield;
use advanced_testcase;
use context_course;
use context_system;
use moodle_url;
use core_customfield\field_controller;
/**
* Unit tests for group custom field handler.
*
* @package core_group
* @covers \core_group\customfield\group_handler
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class group_handler_test extends advanced_testcase {
/**
* Test custom field handler.
* @var group_handler
*/
protected $handler;
/**
* Setup.
*/
public function setUp(): void {
$this->handler = group_handler::create();
}
/**
* Create group custom field for testing.
*
* @return field_controller
*/
protected function create_group_custom_field(): field_controller {
$fieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_group',
'area' => 'group',
]);
return self::getDataGenerator()->create_custom_field([
'shortname' => 'testfield1',
'type' => 'text',
'categoryid' => $fieldcategory->get('id'),
]);
}
/**
* Test configuration context.
*/
public function test_get_configuration_context(): void {
$this->assertInstanceOf(context_system::class, $this->handler->get_configuration_context());
}
/**
* Test getting config URL.
*/
public function test_get_configuration_url(): void {
$this->assertInstanceOf(moodle_url::class, $this->handler->get_configuration_url());
$this->assertEquals('/group/customfield.php', $this->handler->get_configuration_url()->out_as_local_url());
}
/**
* Test getting instance context.
*/
public function test_get_instance_context(): void {
global $COURSE;
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course();
$group = self::getDataGenerator()->create_group(['courseid' => $course->id]);
$this->assertInstanceOf(context_course::class, $this->handler->get_instance_context());
$this->assertSame(context_course::instance($COURSE->id), $this->handler->get_instance_context());
$this->assertInstanceOf(context_course::class, $this->handler->get_instance_context($group->id));
$this->assertSame(context_course::instance($course->id), $this->handler->get_instance_context($group->id));
}
/**
* Test can configure check.
*/
public function test_can_configure(): void {
$this->resetAfterTest();
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($this->handler->can_configure());
$roleid = self::getDataGenerator()->create_role();
assign_capability('moodle/group:configurecustomfields', CAP_ALLOW, $roleid, context_system::instance()->id, true);
role_assign($roleid, $user->id, context_system::instance()->id);
$this->assertTrue($this->handler->can_configure());
}
/**
* Test can edit functionality.
*/
public function test_can_edit(): void {
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course();
$contextid = context_course::instance($course->id)->id;
$group = self::getDataGenerator()->create_group(['courseid' => $course->id]);
$roleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:managegroups', CAP_ALLOW, $roleid, $contextid, true);
$field = $this->create_group_custom_field();
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($this->handler->can_edit($field, $group->id));
role_assign($roleid, $user->id, $contextid);
$this->assertTrue($this->handler->can_edit($field, $group->id));
}
/**
* Test can view functionality.
*/
public function test_can_view(): void {
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course();
$contextid = context_course::instance($course->id)->id;
$group = self::getDataGenerator()->create_group(['courseid' => $course->id]);
$manageroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:managegroups', CAP_ALLOW, $manageroleid, $contextid, true);
$viewroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:view', CAP_ALLOW, $viewroleid, $contextid, true);
$viewandmanageroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:managegroups', CAP_ALLOW, $viewandmanageroleid, $contextid, true);
assign_capability('moodle/course:view', CAP_ALLOW, $viewandmanageroleid, $contextid, true);
$field = $this->create_group_custom_field();
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$user3 = self::getDataGenerator()->create_user();
self::setUser($user1);
$this->assertFalse($this->handler->can_view($field, $group->id));
self::setUser($user2);
$this->assertFalse($this->handler->can_view($field, $group->id));
self::setUser($user3);
$this->assertFalse($this->handler->can_view($field, $group->id));
role_assign($manageroleid, $user1->id, $contextid);
role_assign($viewroleid, $user2->id, $contextid);
role_assign($viewandmanageroleid, $user3->id, $contextid);
self::setUser($user1);
$this->assertTrue($this->handler->can_view($field, $group->id));
self::setUser($user2);
$this->assertTrue($this->handler->can_view($field, $group->id));
self::setUser($user3);
$this->assertTrue($this->handler->can_view($field, $group->id));
}
}
@@ -0,0 +1,186 @@
<?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_group\customfield;
use advanced_testcase;
use context_course;
use context_system;
use moodle_url;
use core_customfield\field_controller;
/**
* Unit tests for grouping custom field handler.
*
* @package core_group
* @covers \core_group\customfield\group_handler
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grouping_handler_test extends advanced_testcase {
/**
* Test custom field handler.
* @var \core_customfield\handler
*/
protected $handler;
/**
* Setup.
*/
public function setUp(): void {
$this->handler = grouping_handler::create();
}
/**
* Create grouping custom field for testing.
*
* @return field_controller
*/
protected function create_grouping_custom_field(): field_controller {
$fieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_group',
'area' => 'grouping',
]);
return self::getDataGenerator()->create_custom_field([
'shortname' => 'testfield1',
'type' => 'text',
'categoryid' => $fieldcategory->get('id'),
]);
}
/**
* Test configuration context.
*/
public function test_get_configuration_context(): void {
$this->assertInstanceOf(context_system::class, $this->handler->get_configuration_context());
}
/**
* Test getting config URL.
*/
public function test_get_configuration_url(): void {
$this->assertInstanceOf(moodle_url::class, $this->handler->get_configuration_url());
$this->assertEquals('/group/grouping_customfield.php', $this->handler->get_configuration_url()->out_as_local_url());
}
/**
* Test getting instance context.
*/
public function test_get_instance_context(): void {
global $COURSE;
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course();
$grouping = self::getDataGenerator()->create_grouping(['courseid' => $course->id]);
$this->assertInstanceOf(context_course::class, $this->handler->get_instance_context());
$this->assertSame(context_course::instance($COURSE->id), $this->handler->get_instance_context());
$this->assertInstanceOf(context_course::class, $this->handler->get_instance_context($grouping->id));
$this->assertSame(context_course::instance($course->id), $this->handler->get_instance_context($grouping->id));
}
/**
* Test can configure check.
*/
public function test_can_configure(): void {
$this->resetAfterTest();
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($this->handler->can_configure());
$roleid = self::getDataGenerator()->create_role();
assign_capability('moodle/group:configurecustomfields', CAP_ALLOW, $roleid, context_system::instance()->id, true);
role_assign($roleid, $user->id, context_system::instance()->id);
$this->assertTrue($this->handler->can_configure());
}
/**
* Test can edit functionality.
*/
public function test_can_edit(): void {
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course();
$contextid = context_course::instance($course->id)->id;
$grouping = self::getDataGenerator()->create_grouping(['courseid' => $course->id]);
$roleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:managegroups', CAP_ALLOW, $roleid, $contextid, true);
$field = $this->create_grouping_custom_field();
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($this->handler->can_edit($field, $grouping->id));
role_assign($roleid, $user->id, $contextid);
$this->assertTrue($this->handler->can_edit($field, $grouping->id));
}
/**
* Test can view functionality.
*/
public function test_can_view(): void {
$this->resetAfterTest();
$course = self::getDataGenerator()->create_course();
$contextid = context_course::instance($course->id)->id;
$grouping = self::getDataGenerator()->create_grouping(['courseid' => $course->id]);
$manageroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:managegroups', CAP_ALLOW, $manageroleid, $contextid, true);
$viewroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:view', CAP_ALLOW, $viewroleid, $contextid, true);
$viewandmanageroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/course:managegroups', CAP_ALLOW, $viewandmanageroleid, $contextid, true);
assign_capability('moodle/course:view', CAP_ALLOW, $viewandmanageroleid, $contextid, true);
$field = $this->create_grouping_custom_field();
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$user3 = self::getDataGenerator()->create_user();
self::setUser($user1);
$this->assertFalse($this->handler->can_view($field, $grouping->id));
self::setUser($user2);
$this->assertFalse($this->handler->can_view($field, $grouping->id));
self::setUser($user3);
$this->assertFalse($this->handler->can_view($field, $grouping->id));
role_assign($manageroleid, $user1->id, $contextid);
role_assign($viewroleid, $user2->id, $contextid);
role_assign($viewandmanageroleid, $user3->id, $contextid);
self::setUser($user1);
$this->assertTrue($this->handler->can_view($field, $grouping->id));
self::setUser($user2);
$this->assertTrue($this->handler->can_view($field, $grouping->id));
self::setUser($user3);
$this->assertTrue($this->handler->can_view($field, $grouping->id));
}
}
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
groupname, description, groupidnumber,groupingname,enablemessaging
group-id-1, group-id-1, group-id-1,Grouping-1,1
group-id-2, group-id-2, group-id-2,Grouping-2,0
group-id-1-duplicate, Duplicate of group-id-1, group-id-1,Grouping-3,0
group-noid-1, group-noid-1,,Grouping-3,0
group-noid-2, group-noid-2,,Grouping-2,0
1 groupname description groupidnumber groupingname enablemessaging
2 group-id-1 group-id-1 group-id-1 Grouping-1 1
3 group-id-2 group-id-2 group-id-2 Grouping-2 0
4 group-id-1-duplicate Duplicate of group-id-1 group-id-1 Grouping-3 0
5 group-noid-1 group-noid-1 Grouping-3 0
6 group-noid-2 group-noid-2 Grouping-2 0
+6
View File
@@ -0,0 +1,6 @@
coursename,groupname
C1,group7
C-non-existing,group-will-not-be-created
C1,group8
C2,group9
,group10
1 coursename groupname
2 C1 group7
3 C-non-existing group-will-not-be-created
4 C1 group8
5 C2 group9
6 group10
@@ -0,0 +1,3 @@
groupname, description, groupidnumber, groupingname, customfield_groupfield1, grouping_customfield_groupingfield1
Group1, Group1-Desc, group-id1, Grouping1, Group1-Custom, Grouping1-Custom
Group2, Group1-Desc, group-id2, Grouping1, Group2-Custom, Grouping1-Custom
1 groupname description groupidnumber groupingname customfield_groupfield1 grouping_customfield_groupingfield1
2 Group1 Group1-Desc group-id1 Grouping1 Group1-Custom Grouping1-Custom
3 Group2 Group1-Desc group-id2 Grouping1 Group2-Custom Grouping1-Custom
+971
View File
@@ -0,0 +1,971 @@
<?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/>.
/**
* Unit tests for group lib.
*
* @package core_group
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_group;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/group/lib.php');
require_once($CFG->dirroot . '/lib/grouplib.php');
/**
* Group lib testcase.
*
* @package core_group
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends \advanced_testcase {
public function test_member_added_event(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$sink = $this->redirectEvents();
groups_add_member($group->id, $user->id, 'mod_workshop', '123');
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\group_member_added', $event);
$this->assertEquals($user->id, $event->relateduserid);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/members.php', array('group' => $event->objectid));
$this->assertEquals($url, $event->get_url());
}
public function test_member_removed_event(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$this->getDataGenerator()->create_group_member(array('userid' => $user->id, 'groupid' => $group->id));
$sink = $this->redirectEvents();
groups_remove_member($group->id, $user->id);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\group_member_removed', $event);
$this->assertEquals($user->id, $event->relateduserid);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/members.php', array('group' => $event->objectid));
$this->assertEquals($url, $event->get_url());
}
public function test_group_created_event(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$sink = $this->redirectEvents();
$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\group_created', $event);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/index.php', array('id' => $event->courseid));
$this->assertEquals($url, $event->get_url());
}
public function test_grouping_created_event(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$sink = $this->redirectEvents();
$group = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\grouping_created', $event);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/groupings.php', array('id' => $event->courseid));
$this->assertEquals($url, $event->get_url());
}
public function test_group_updated_event(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$sink = $this->redirectEvents();
$data = new \stdClass();
$data->id = $group->id;
$data->courseid = $course->id;
$data->name = 'Backend team';
$this->setCurrentTimeStart();
groups_update_group($data);
$group = $DB->get_record('groups', array('id'=>$group->id)); // Fetch record with modified timestamp.
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertTimeCurrent($group->timemodified);
$this->assertInstanceOf('\core\event\group_updated', $event);
$group->name = $data->name;
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/group.php', array('id' => $event->objectid));
$this->assertEquals($url, $event->get_url());
}
public function test_group_updated_event_does_not_require_names(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$sink = $this->redirectEvents();
$data = new \stdClass();
$data->id = $group->id;
$data->courseid = $course->id;
$this->setCurrentTimeStart();
groups_update_group($data);
$group = $DB->get_record('groups', array('id'=>$group->id)); // Fetch record with modified timestamp.
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertTimeCurrent($group->timemodified);
$this->assertInstanceOf('\core\event\group_updated', $event);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/group.php', array('id' => $event->objectid));
$this->assertEquals($url, $event->get_url());
}
public function test_grouping_updated_event(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$sink = $this->redirectEvents();
$data = new \stdClass();
$data->id = $grouping->id;
$data->courseid = $course->id;
$data->name = 'Backend team';
$this->setCurrentTimeStart();
groups_update_grouping($data);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\grouping_updated', $event);
// Get the timemodified from DB for comparison with snapshot.
$data->timemodified = $DB->get_field('groupings', 'timemodified', array('id'=>$grouping->id));
$this->assertTimeCurrent($data->timemodified);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($grouping->id, $event->objectid);
$url = new \moodle_url('/group/grouping.php', array('id' => $event->objectid));
$this->assertEquals($url, $event->get_url());
}
public function test_grouping_updated_event_does_not_require_names(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$sink = $this->redirectEvents();
$data = new \stdClass();
$data->id = $grouping->id;
$data->courseid = $course->id;
$this->setCurrentTimeStart();
groups_update_grouping($data);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\grouping_updated', $event);
// Get the timemodified from DB for comparison with snapshot.
$data->timemodified = $DB->get_field('groupings', 'timemodified', array('id'=>$grouping->id));
$this->assertTimeCurrent($data->timemodified);
// Following fields were not updated so the snapshot should have them the same as in original group.
$data->description = $grouping->description;
$data->descriptionformat = $grouping->descriptionformat;
$data->configdata = $grouping->configdata;
$data->idnumber = $grouping->idnumber;
$data->name = $grouping->name;
$data->timecreated = $grouping->timecreated;
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($grouping->id, $event->objectid);
$url = new \moodle_url('/group/grouping.php', array('id' => $event->objectid));
$this->assertEquals($url, $event->get_url());
}
public function test_group_deleted_event(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$sink = $this->redirectEvents();
groups_delete_group($group->id);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/index.php', array('id' => $event->courseid));
$this->assertEquals($url, $event->get_url());
}
public function test_grouping_deleted_event(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$group = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$sink = $this->redirectEvents();
groups_delete_grouping($group->id);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\core\event\grouping_deleted', $event);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
$this->assertEquals($group->id, $event->objectid);
$url = new \moodle_url('/group/groupings.php', array('id' => $event->courseid));
$this->assertEquals($url, $event->get_url());
}
public function test_groups_delete_group_members(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
// Test deletion of all the users.
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id)));
groups_delete_group_members($course->id);
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id)));
// Test deletion of a specific user.
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id)));
groups_delete_group_members($course->id, $user2->id);
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id)));
}
public function test_groups_remove_member(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id)));
groups_remove_member($group1->id, $user1->id);
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id)));
groups_remove_member($group1->id, $user2->id);
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id)));
groups_remove_member($group2->id, $user1->id);
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id)));
}
public function test_groups_delete_groupings_groups(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group1c2 = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
$grouping1 = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$grouping2 = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$grouping1c2 = $this->getDataGenerator()->create_grouping(array('courseid' => $course2->id));
$this->getDataGenerator()->create_grouping_group(array('groupingid' => $grouping1->id, 'groupid' => $group1->id));
$this->getDataGenerator()->create_grouping_group(array('groupingid' => $grouping1->id, 'groupid' => $group2->id));
$this->getDataGenerator()->create_grouping_group(array('groupingid' => $grouping2->id, 'groupid' => $group1->id));
$this->getDataGenerator()->create_grouping_group(array('groupingid' => $grouping1c2->id, 'groupid' => $group1c2->id));
$this->assertTrue($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping1->id)));
$this->assertTrue($DB->record_exists('groupings_groups', array('groupid' => $group2->id, 'groupingid' => $grouping1->id)));
$this->assertTrue($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping2->id)));
$this->assertTrue($DB->record_exists('groupings_groups',
array('groupid' => $group1c2->id, 'groupingid' => $grouping1c2->id)));
groups_delete_groupings_groups($course->id);
$this->assertFalse($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping1->id)));
$this->assertFalse($DB->record_exists('groupings_groups', array('groupid' => $group2->id, 'groupingid' => $grouping1->id)));
$this->assertFalse($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping2->id)));
$this->assertTrue($DB->record_exists('groupings_groups',
array('groupid' => $group1c2->id, 'groupingid' => $grouping1c2->id)));
}
public function test_groups_delete_groups(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group1c2 = $this->getDataGenerator()->create_group(array('courseid' => $course2->id));
$grouping1 = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$grouping2 = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$user1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping1->id));
$this->assertTrue($DB->record_exists('groups', array('id' => $group1->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groups', array('id' => $group2->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groups', array('id' => $group1c2->id, 'courseid' => $course2->id)));
$this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groupings', array('id' => $grouping1->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping1->id)));
groups_delete_groups($course->id);
$this->assertFalse($DB->record_exists('groups', array('id' => $group1->id, 'courseid' => $course->id)));
$this->assertFalse($DB->record_exists('groups', array('id' => $group2->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groups', array('id' => $group1c2->id, 'courseid' => $course2->id)));
$this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id)));
$this->assertTrue($DB->record_exists('groupings', array('id' => $grouping1->id, 'courseid' => $course->id)));
$this->assertFalse($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping1->id)));
}
public function test_groups_delete_groupings(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$grouping1 = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$grouping2 = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$grouping1c2 = $this->getDataGenerator()->create_grouping(array('courseid' => $course2->id));
$this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping1->id));
$this->assertTrue($DB->record_exists('groups', array('id' => $group1->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groupings', array('id' => $grouping1->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groupings', array('id' => $grouping2->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groupings', array('id' => $grouping1c2->id, 'courseid' => $course2->id)));
$this->assertTrue($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping1->id)));
groups_delete_groupings($course->id);
$this->assertTrue($DB->record_exists('groups', array('id' => $group1->id, 'courseid' => $course->id)));
$this->assertFalse($DB->record_exists('groupings', array('id' => $grouping1->id, 'courseid' => $course->id)));
$this->assertFalse($DB->record_exists('groupings', array('id' => $grouping2->id, 'courseid' => $course->id)));
$this->assertTrue($DB->record_exists('groupings', array('id' => $grouping1c2->id, 'courseid' => $course2->id)));
$this->assertFalse($DB->record_exists('groupings_groups', array('groupid' => $group1->id, 'groupingid' => $grouping1->id)));
}
/**
* Test custom field for group.
* @covers ::groups_create_group
* @covers ::groups_get_group
*/
public function test_groups_with_customfield(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course1 = self::getDataGenerator()->create_course();
$course2 = self::getDataGenerator()->create_course();
$groupfieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_group',
'area' => 'group',
]);
$groupcustomfield = self::getDataGenerator()->create_custom_field([
'shortname' => 'testgroupcustomfield1',
'type' => 'text',
'categoryid' => $groupfieldcategory->get('id'),
]);
$groupingfieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_group',
'area' => 'grouping',
]);
$groupingcustomfield = self::getDataGenerator()->create_custom_field([
'shortname' => 'testgroupingcustomfield1',
'type' => 'text',
'categoryid' => $groupingfieldcategory->get('id'),
]);
$group1 = self::getDataGenerator()->create_group([
'courseid' => $course1->id,
'customfield_testgroupcustomfield1' => 'Custom input for group1',
]);
$group2 = self::getDataGenerator()->create_group([
'courseid' => $course2->id,
'customfield_testgroupcustomfield1' => 'Custom input for group2',
]);
$grouping1 = self::getDataGenerator()->create_grouping([
'courseid' => $course1->id,
'customfield_testgroupingcustomfield1' => 'Custom input for grouping1',
]);
$grouping2 = self::getDataGenerator()->create_grouping([
'courseid' => $course2->id,
'customfield_testgroupingcustomfield1' => 'Custom input for grouping2',
]);
$grouphandler = \core_group\customfield\group_handler::create();
$data = $grouphandler->export_instance_data_object($group1->id);
$this->assertSame('Custom input for group1', $data->testgroupcustomfield1);
$data = $grouphandler->export_instance_data_object($group2->id);
$this->assertSame('Custom input for group2', $data->testgroupcustomfield1);
$groupinghandler = \core_group\customfield\grouping_handler::create();
$data = $groupinghandler->export_instance_data_object($grouping1->id);
$this->assertSame('Custom input for grouping1', $data->testgroupingcustomfield1);
$data = $groupinghandler->export_instance_data_object($grouping2->id);
$this->assertSame('Custom input for grouping2', $data->testgroupingcustomfield1);
$group1->customfield_testgroupcustomfield1 = 'Updated input for group1';
$group2->customfield_testgroupcustomfield1 = 'Updated input for group2';
groups_update_group($group1);
groups_update_group($group2);
$data = $grouphandler->export_instance_data_object($group1->id);
$this->assertSame('Updated input for group1', $data->testgroupcustomfield1);
$data = $grouphandler->export_instance_data_object($group2->id);
$this->assertSame('Updated input for group2', $data->testgroupcustomfield1);
$group = groups_get_group($group1->id, '*', IGNORE_MISSING, true);
$this->assertCount(1, $group->customfields);
$customfield = reset($group->customfields);
$this->assertSame('Updated input for group1', $customfield['value']);
$grouping1->customfield_testgroupingcustomfield1 = 'Updated input for grouping1';
$grouping2->customfield_testgroupingcustomfield1 = 'Updated input for grouping2';
groups_update_grouping($grouping1);
groups_update_grouping($grouping2);
$data = $groupinghandler->export_instance_data_object($grouping1->id);
$this->assertSame('Updated input for grouping1', $data->testgroupingcustomfield1);
$data = $groupinghandler->export_instance_data_object($grouping2->id);
$this->assertSame('Updated input for grouping2', $data->testgroupingcustomfield1);
$grouping = groups_get_grouping($grouping1->id, '*', IGNORE_MISSING, true);
$this->assertCount(1, $grouping->customfields);
$customfield = reset($grouping->customfields);
$this->assertSame('Updated input for grouping1', $customfield['value']);
}
public function test_groups_create_autogroups(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$grouping1 = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
$this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping1->id));
$this->getDataGenerator()->create_grouping_group(array('groupid' => $group3->id, 'groupingid' => $grouping1->id));
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$user4 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$this->getDataGenerator()->enrol_user($user3->id, $course->id);
$this->getDataGenerator()->enrol_user($user4->id, $course->id);
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user4->id));
// Test autocreate group based on all course users.
$users = groups_get_potential_members($course->id);
$group4 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
foreach ($users as $user) {
$this->getDataGenerator()->create_group_member(array('groupid' => $group4->id, 'userid' => $user->id));
}
$this->assertEquals(4, $DB->count_records('groups_members', array('groupid' => $group4->id)));
// Test autocreate group based on existing group.
$source = array();
$source['groupid'] = $group1->id;
$users = groups_get_potential_members($course->id, 0, $source);
$group5 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
foreach ($users as $user) {
$this->getDataGenerator()->create_group_member(array('groupid' => $group5->id, 'userid' => $user->id));
}
$this->assertEquals(2, $DB->count_records('groups_members', array('groupid' => $group5->id)));
// Test autocreate group based on existing grouping.
$source = array();
$source['groupingid'] = $grouping1->id;
$users = groups_get_potential_members($course->id, 0, $source);
$group6 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
foreach ($users as $user) {
$this->getDataGenerator()->create_group_member(array('groupid' => $group6->id, 'userid' => $user->id));
}
$this->assertEquals(2, $DB->count_records('groups_members', array('groupid' => $group6->id)));
}
/**
* Test groups_create_group enabling a group conversation.
*/
public function test_groups_create_group_with_conversation(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
// Create two groups and only one group with enablemessaging = 1.
$group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
$group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 0));
$conversations = $DB->get_records('message_conversations',
[
'contextid' => $coursecontext1->id,
'component' => 'core_group',
'itemtype' => 'groups',
'enabled' => \core_message\api::MESSAGE_CONVERSATION_ENABLED
]
);
$this->assertCount(1, $conversations);
$conversation = reset($conversations);
// Check groupid was stored in itemid on conversation area.
$this->assertEquals($group1a->id, $conversation->itemid);
$conversations = $DB->get_records('message_conversations', ['id' => $conversation->id]);
$this->assertCount(1, $conversations);
$conversation = reset($conversations);
// Check group name was stored in conversation.
$this->assertEquals($group1a->name, $conversation->name);
}
/**
* Test groups_update_group enabling and disabling a group conversation.
*/
public function test_groups_update_group_conversation(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
// Create two groups and only one group with enablemessaging = 1.
$group1a = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
$group1b = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 0));
$conversations = $DB->get_records('message_conversations',
[
'contextid' => $coursecontext1->id,
'component' => 'core_group',
'itemtype' => 'groups',
'enabled' => \core_message\api::MESSAGE_CONVERSATION_ENABLED
]
);
$this->assertCount(1, $conversations);
// Check that the conversation area is created when group messaging is enabled in the course group.
$group1b->enablemessaging = 1;
groups_update_group($group1b);
$conversations = $DB->get_records('message_conversations',
[
'contextid' => $coursecontext1->id,
'component' => 'core_group',
'itemtype' => 'groups',
'enabled' => \core_message\api::MESSAGE_CONVERSATION_ENABLED
],
'id ASC');
$this->assertCount(2, $conversations);
$conversation1a = array_shift($conversations);
$conversation1b = array_shift($conversations);
$conversation1b = $DB->get_record('message_conversations', ['id' => $conversation1b->id]);
// Check for group1b that group name was stored in conversation.
$this->assertEquals($group1b->name, $conversation1b->name);
$group1b->enablemessaging = 0;
groups_update_group($group1b);
$this->assertEquals(0, $DB->get_field("message_conversations", "enabled", ['id' => $conversation1b->id]));
// Check that the name of the conversation is changed when the name of the course group is updated.
$group1b->name = 'New group name';
groups_update_group($group1b);
$conversation1b = $DB->get_record('message_conversations', ['id' => $conversation1b->id]);
$this->assertEquals($group1b->name, $conversation1b->name);
}
/**
* Test groups_add_member to conversation.
*/
public function test_groups_add_member_conversation(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id);
$this->getDataGenerator()->enrol_user($user2->id, $course1->id);
$this->getDataGenerator()->enrol_user($user3->id, $course1->id);
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
// Add users to group1.
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id));
$conversation = \core_message\api::get_conversation_by_area(
'core_group',
'groups',
$group1->id,
$coursecontext1->id
);
// Check if the users has been added to the conversation.
$this->assertEquals(2, $DB->count_records('message_conversation_members', ['conversationid' => $conversation->id]));
// Check if the user has been added to the conversation when the conversation is disabled.
\core_message\api::disable_conversation($conversation->id);
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user3->id));
$this->assertEquals(3, $DB->count_records('message_conversation_members', ['conversationid' => $conversation->id]));
}
/**
* Test groups_remove_member to conversation.
*/
public function test_groups_remove_member_conversation(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id);
$this->getDataGenerator()->enrol_user($user2->id, $course1->id);
$this->getDataGenerator()->enrol_user($user3->id, $course1->id);
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 1));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user3->id));
$conversation = \core_message\api::get_conversation_by_area(
'core_group',
'groups',
$group1->id,
$coursecontext1->id
);
// Check if there are three users in the conversation.
$this->assertEquals(3, $DB->count_records('message_conversation_members', ['conversationid' => $conversation->id]));
// Check if after removing one member in the conversation there are two members.
groups_remove_member($group1->id, $user1->id);
$this->assertEquals(2, $DB->count_records('message_conversation_members', ['conversationid' => $conversation->id]));
// Check if the user has been removed from the conversation when the conversation is disabled.
\core_message\api::disable_conversation($conversation->id);
groups_remove_member($group1->id, $user2->id);
$this->assertEquals(1, $DB->count_records('message_conversation_members', ['conversationid' => $conversation->id]));
}
/**
* Test if you enable group messaging in a group with members these are added to the conversation.
*/
public function test_add_members_group_updated_conversation_enabled(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id);
$this->getDataGenerator()->enrol_user($user2->id, $course1->id);
$this->getDataGenerator()->enrol_user($user3->id, $course1->id);
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course1->id, 'enablemessaging' => 0));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id));
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user3->id));
$conversation = \core_message\api::get_conversation_by_area(
'core_group',
'groups',
$group1->id,
$coursecontext1->id
);
// No conversation should exist as 'enablemessaging' was set to 0.
$this->assertFalse($conversation);
// Check that the three users are in the conversation when group messaging is enabled in the course group.
$group1->enablemessaging = 1;
groups_update_group($group1);
$conversation = \core_message\api::get_conversation_by_area(
'core_group',
'groups',
$group1->id,
$coursecontext1->id
);
$this->assertEquals(3, $DB->count_records('message_conversation_members', ['conversationid' => $conversation->id]));
}
public function test_groups_get_members_by_role(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course1 = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user(['username' => 'user1', 'idnumber' => 1]);
$user2 = $this->getDataGenerator()->create_user(['username' => 'user2', 'idnumber' => 2]);
$user3 = $this->getDataGenerator()->create_user(['username' => 'user3', 'idnumber' => 3]);
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 0);
$this->getDataGenerator()->enrol_user($user2->id, $course1->id, 1);
$this->getDataGenerator()->enrol_user($user3->id, $course1->id, 1);
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course1->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user1->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user2->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $user3->id]);
// Test basic usage.
$result = groups_get_members_by_role($group1->id, $course1->id);
$this->assertEquals(1, count($result[0]->users));
$this->assertEquals(2, count($result[1]->users));
$this->assertEquals($user1->firstname, reset($result[0]->users)->firstname);
$this->assertEquals($user1->username, reset($result[0]->users)->username);
// Test with specified fields.
$result = groups_get_members_by_role($group1->id, $course1->id, 'u.firstname, u.lastname');
$this->assertEquals(1, count($result[0]->users));
$this->assertEquals($user1->firstname, reset($result[0]->users)->firstname);
$this->assertEquals($user1->lastname, reset($result[0]->users)->lastname);
$this->assertEquals(false, isset(reset($result[0]->users)->username));
// Test with sorting.
$result = groups_get_members_by_role($group1->id, $course1->id, 'u.username', 'u.username DESC');
$this->assertEquals(1, count($result[0]->users));
$this->assertEquals($user3->username, reset($result[1]->users)->username);
$result = groups_get_members_by_role($group1->id, $course1->id, 'u.username', 'u.username ASC');
$this->assertEquals(1, count($result[0]->users));
$this->assertEquals($user2->username, reset($result[1]->users)->username);
// Test with extra WHERE.
$result = groups_get_members_by_role(
$group1->id,
$course1->id,
'u.username',
null,
'u.idnumber > :number',
['number' => 2]);
$this->assertEquals(1, count($result));
$this->assertEquals(1, count($result[1]->users));
$this->assertEquals($user3->username, reset($result[1]->users)->username);
// Test with join.
set_user_preference('reptile', 'snake', $user1);
$result = groups_get_members_by_role($group1->id, $course1->id, 'u.username, up.value', null, 'up.name = :prefname',
['prefname' => 'reptile'], 'JOIN {user_preferences} up ON up.userid = u.id');
$this->assertEquals('snake', reset($result[0]->users)->value);
}
/**
* Tests set_groups_messaging
*
* @covers \core_group::set_groups_messaging
*/
public function test_set_groups_messaging(): void {
$this->resetAfterTest();
$this->setAdminUser();
$dg = $this->getDataGenerator();
$course = $dg->create_course();
// Create some groups in the course.
$groupids = [];
for ($i = 0; $i < 5; $i++) {
$group = new \stdClass();
$group->courseid = $course->id;
$group->name = 'group-'.$i;
$group->enablemessaging = 0;
$groupids[] = groups_create_group($group);
}
// They should all initially be disabled.
$alldisabledinitially = $this->check_groups_messaging_status_is($groupids, $course->id, false);
$this->assertTrue($alldisabledinitially);
// Enable messaging for all the groups.
set_groups_messaging($groupids, true);
// Check they were all enabled.
$allenabled = $this->check_groups_messaging_status_is($groupids, $course->id, true);
$this->assertTrue($allenabled);
// Disable messaging for all the groups.
set_groups_messaging($groupids, false);
// Check they were all disabled.
$alldisabled = $this->check_groups_messaging_status_is($groupids, $course->id, false);
$this->assertTrue($alldisabled);
}
/**
* Tests set group messaging where it doesn't exist
*
* @covers \core_group::set_groups_messaging
*/
public function test_set_groups_messaging_doesnt_exist(): void {
$this->resetAfterTest();
$this->setAdminUser();
$groupids = [-1];
$this->expectException('dml_exception');
set_groups_messaging($groupids, false);
}
/**
* Checks the given list of groups to verify their messaging settings.
*
* @param array $groupids array of group ids
* @param int $courseid the course the groups are in
* @param bool $desired the desired setting value
* @return bool true if all groups $enablemessaging setting matches the given $desired value, else false
*/
private function check_groups_messaging_status_is(array $groupids, int $courseid, bool $desired) {
$context = \context_course::instance($courseid);
foreach ($groupids as $groupid) {
$conversation = \core_message\api::get_conversation_by_area(
'core_group',
'groups',
$groupid,
$context->id
);
// An empty conversation means it has not been enabled yet.
if (empty($conversation)) {
$conversation = (object) [
'enabled' => 0
];
}
if ($desired !== boolval($conversation->enabled)) {
return false;
}
}
return true;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,406 @@
<?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/>.
declare(strict_types=1);
namespace core_group\reportbuilder\datasource;
use core_customfield_generator;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
use core_reportbuilder\local\filters\{boolean_select, date, select, text};
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for groups datasource
*
* @package core_group
* @covers \core_group\reportbuilder\datasource\groups
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groups_test extends core_reportbuilder_testcase {
/**
* Test default datasource
*/
public function test_datasource_default(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$userone = $this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Zoe']);
$usertwo = $this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Amy']);
$groupone = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Zebras']);
$grouptwo = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Aardvarks']);
$this->getDataGenerator()->create_group_member(['groupid' => $groupone->id, 'userid' => $userone->id]);
$this->getDataGenerator()->create_group_member(['groupid' => $groupone->id, 'userid' => $usertwo->id]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Groups', 'source' => groups::class, 'default' => 1]);
$content = $this->get_custom_report_content($report->get('id'));
// Default columns are course, group, user. Sorted by each.
$courseurl = course_get_url($course);
$this->assertEquals([
["<a href=\"{$courseurl}\">{$course->fullname}</a>", $grouptwo->name, ''],
["<a href=\"{$courseurl}\">{$course->fullname}</a>", $groupone->name, fullname($usertwo)],
["<a href=\"{$courseurl}\">{$course->fullname}</a>", $groupone->name, fullname($userone)],
], array_map('array_values', $content));
}
/**
* Test datasource groupings reports
*/
public function test_datasource_groupings(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
// Create group, add to grouping.
$groupone = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Group A']);
$grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
$this->getDataGenerator()->create_grouping_group(['groupingid' => $grouping->id, 'groupid' => $groupone->id]);
// Create second group, no grouping.
$grouptwo = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Group B']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Groups', 'source' => groups::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'grouping:name']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:name', 'sortenabled' => 1]);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(2, $content);
$this->assertEquals([
[
$course->fullname,
$grouping->name,
$groupone->name,
],
[
$course->fullname,
null,
$grouptwo->name,
],
], array_map('array_values', $content));
}
/**
* Test datasource columns that aren't added by default
*/
public function test_datasource_non_default_columns(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'student');
/** @var core_customfield_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
// Create group with custom field, and single group member.
$groupfieldcategory = $generator->create_category(['component' => 'core_group', 'area' => 'group']);
$generator->create_field(['categoryid' => $groupfieldcategory->get('id'), 'shortname' => 'hi']);
$group = $this->getDataGenerator()->create_group([
'courseid' => $course->id,
'idnumber' => 'G101',
'enrolmentkey' => 'S',
'description' => 'My group',
'customfield_hi' => 'Hello',
]);
$this->getDataGenerator()->create_group_member(['userid' => $user->id, 'groupid' => $group->id]);
// Create grouping with custom field, and single group.
$groupingfieldcategory = $generator->create_category(['component' => 'core_group', 'area' => 'grouping']);
$generator->create_field(['categoryid' => $groupingfieldcategory->get('id'), 'shortname' => 'bye']);
$grouping = $this->getDataGenerator()->create_grouping([
'courseid' => $course->id,
'idnumber' => 'GR101',
'description' => 'My grouping',
'customfield_bye' => 'Goodbye',
]);
$this->getDataGenerator()->create_grouping_group(['groupingid' => $grouping->id, 'groupid' => $group->id]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Groups', 'source' => groups::class, 'default' => 0]);
// Course (just to test join).
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:shortname']);
// Group.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:idnumber']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:description']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:enrolmentkey']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:visibility']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:participation']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:picture']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:timecreated']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:timemodified']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:customfield_hi']);
// Grouping.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'grouping:name']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'grouping:idnumber']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'grouping:description']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'grouping:timecreated']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'grouping:timemodified']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'grouping:customfield_bye']);
// Group member.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group_member:timeadded']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group_member:component']);
// User (just to test join).
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(1, $content);
[
$courseshortname,
$groupidnumber,
$groupdescription,
$groupenrolmentkey,
$groupvisibility,
$groupparticipation,
$grouppicture,
$grouptimecreated,
$grouptimemodified,
$groupcustomfield,
$groupingname,
$groupingidnumber,
$groupingdescription,
$groupingtimecreated,
$groupingtimemodified,
$groupingcustomfield,
$groupmembertimeadded,
$groupmemebercomponent,
$userusername,
] = array_values($content[0]);
$this->assertEquals($course->shortname, $courseshortname);
$this->assertEquals('G101', $groupidnumber);
$this->assertEquals(format_text($group->description), $groupdescription);
$this->assertEquals('S', $groupenrolmentkey);
$this->assertEquals('Visible', $groupvisibility);
$this->assertEquals('Yes', $groupparticipation);
$this->assertEmpty($grouppicture);
$this->assertNotEmpty($grouptimecreated);
$this->assertNotEmpty($grouptimemodified);
$this->assertEquals('Hello', $groupcustomfield);
$this->assertEquals($grouping->name, $groupingname);
$this->assertEquals('GR101', $groupingidnumber);
$this->assertEquals(format_text($grouping->description), $groupingdescription);
$this->assertNotEmpty($groupingtimecreated);
$this->assertNotEmpty($groupingtimemodified);
$this->assertEquals('Goodbye', $groupingcustomfield);
$this->assertNotEmpty($groupmembertimeadded);
$this->assertEmpty($groupmemebercomponent);
$this->assertEquals($user->username, $userusername);
}
/**
* Data provider for {@see test_datasource_filters}
*
* @return array[]
*/
public function datasource_filters_provider(): array {
return [
// Course (just to test join).
'Filter course name' => ['course:fullname', [
'course:fullname_operator' => text::IS_EQUAL_TO,
'course:fullname_value' => 'Test course',
], true],
'Filter course name (no match)' => ['course:fullname', [
'course:fullname_operator' => text::IS_NOT_EQUAL_TO,
'course:fullname_value' => 'Test course',
], false],
// Group.
'Filter group name' => ['group:name', [
'group:name_operator' => text::IS_EQUAL_TO,
'group:name_value' => 'Test group',
], true],
'Filter group name (no match)' => ['group:name', [
'group:name_operator' => text::IS_NOT_EQUAL_TO,
'group:name_value' => 'Test group',
], false],
'Filter group idnumber' => ['group:idnumber', [
'group:idnumber_operator' => text::IS_EQUAL_TO,
'group:idnumber_value' => 'G101',
], true],
'Filter group idnumber (no match)' => ['group:idnumber', [
'group:idnumber_operator' => text::IS_NOT_EQUAL_TO,
'group:idnumber_value' => 'G101',
], false],
'Filter group visibility' => ['group:visibility', [
'group:visibility_operator' => select::EQUAL_TO,
'group:visibility_value' => 0, // Visible to everyone.
], true],
'Filter group visibility (no match)' => ['group:visibility', [
'group:visibility_operator' => select::EQUAL_TO,
'group:visibility_value' => 1, // Visible to members only.
], false],
'Filter group participation' => ['group:participation', [
'group:participation_operator' => boolean_select::CHECKED,
], true],
'Filter group participation (no match)' => ['group:participation', [
'group:participation_operator' => boolean_select::NOT_CHECKED,
], false],
'Filter group time created' => ['group:timecreated', [
'group:timecreated_operator' => date::DATE_RANGE,
'group:timecreated_from' => 1622502000,
], true],
'Filter group time created (no match)' => ['group:timecreated', [
'group:timecreated_operator' => date::DATE_RANGE,
'group:timecreated_to' => 1622502000,
], false],
// Grouping.
'Filter grouping name' => ['grouping:name', [
'grouping:name_operator' => text::IS_EQUAL_TO,
'grouping:name_value' => 'Test grouping',
], true],
'Filter grouping name (no match)' => ['grouping:name', [
'grouping:name_operator' => text::IS_NOT_EQUAL_TO,
'grouping:name_value' => 'Test grouping',
], false],
'Filter grouping idnumber' => ['grouping:idnumber', [
'grouping:idnumber_operator' => text::IS_EQUAL_TO,
'grouping:idnumber_value' => 'GR101',
], true],
'Filter grouping idnumber (no match)' => ['grouping:idnumber', [
'grouping:idnumber_operator' => text::IS_NOT_EQUAL_TO,
'grouping:idnumber_value' => 'GR101',
], false],
'Filter grouping time created' => ['grouping:timecreated', [
'grouping:timecreated_operator' => date::DATE_RANGE,
'grouping:timecreated_from' => 1622502000,
], true],
'Filter grouping time created (no match)' => ['grouping:timecreated', [
'grouping:timecreated_operator' => date::DATE_RANGE,
'grouping:timecreated_to' => 1622502000,
], false],
// Group member.
'Filter group member time added' => ['group_member:timeadded', [
'group_member:timeadded_operator' => date::DATE_RANGE,
'group_member:timeadded_from' => 1622502000,
], true],
'Filter group member time added (no match)' => ['group_member:timeadded', [
'group_member:timeadded_operator' => date::DATE_RANGE,
'group_member:timeadded_to' => 1622502000,
], false],
// User (just to test join).
'Filter user username' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'testuser',
], true],
'Filter user username (no match)' => ['user:username', [
'user:username_operator' => text::IS_NOT_EQUAL_TO,
'user:username_value' => 'testuser',
], false],
];
}
/**
* Test datasource filters
*
* @param string $filtername
* @param array $filtervalues
* @param bool $expectmatch
*
* @dataProvider datasource_filters_provider
*/
public function test_datasource_filters(
string $filtername,
array $filtervalues,
bool $expectmatch
): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Test course']);
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', ['username' => 'testuser']);
$group = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'idnumber' => 'G101', 'name' => 'Test group']);
$this->getDataGenerator()->create_group_member(['userid' => $user->id, 'groupid' => $group->id]);
$grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id, 'idnumber' => 'GR101',
'name' => 'Test grouping']);
$this->getDataGenerator()->create_grouping_group(['groupingid' => $grouping->id, 'groupid' => $group->id]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create report containing single column, and given filter.
$report = $generator->create_report(['name' => 'Tasks', 'source' => groups::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'group:idnumber']);
// Add filter, set it's values.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
if ($expectmatch) {
$this->assertCount(1, $content);
$this->assertEquals('G101', reset($content[0]));
} else {
$this->assertEmpty($content);
}
}
/**
* Stress test datasource
*
* In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
*/
public function test_stress_datasource(): void {
if (!PHPUNIT_LONGTEST) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Test course']);
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', ['username' => 'testuser']);
$group = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'idnumber' => 'G101', 'name' => 'Test group']);
$this->getDataGenerator()->create_group_member(['userid' => $user->id, 'groupid' => $group->id]);
$grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id, 'idnumber' => 'GR101',
'name' => 'Test grouping']);
$this->getDataGenerator()->create_grouping_group(['groupingid' => $grouping->id, 'groupid' => $group->id]);
$this->datasource_stress_test_columns(groups::class);
$this->datasource_stress_test_columns_aggregation(groups::class);
$this->datasource_stress_test_conditions(groups::class, 'group:idnumber');
}
}
+44
View File
@@ -0,0 +1,44 @@
This files describes API changes in /group/*,
information provided here is intended especially for developers.
=== 4.3 ===
* The following external methods now return group names correctly formatted:
- `core_group_get_groups`
- `core_group_get_course_groups`
- `core_group_get_course_user_groups`
- `core_group_get_activity_allowed_groups`
* Groups now have access to create GeoPattern default images based upon their ID with their associated course context.
This can be done by calling the following:
moodle_url::make_pluginfile_url(
$coursecontext->id,
'group',
'generated',
$group->id,
'/',
'group.svg'
);
* Added group/grouping custom fields.
* groups_get_members_join() now includes visibility checks for group memberships.
* \core_group\visibility::sql_member_visibility_where() no longer prefixes the returned WHERE statement with AND, to
give the calling code greater flexibility about how to use it.
=== 4.2 ===
* `\core_group\visibility` class added to support new `visibility` field in group records. This holds the visibility constants
and helper functions for applying visibility restrictions when querying groups or group members in the database.
* Changes to the group form to support visibility features:
- New `visibility` field.
- New `participation` field.
- `participation` and `enablemessaging` fields are disabled (default: false) when `visibility` is set
to `visibility::OWN` or `visibility::NONE`.
* The following externallib functions now accept `visibility` and `participation` as optional parameters:
- create_groups()
- update_groups()
* The following externallib functions now also return `visibility` and `participation` fields in their responses:
- create_groups()
- get_groups()
- get_course_groups()
=== 3.11 ===
* The groups do not support 'hidepicture' any more, and so the column 'hidepicture'
from the table {groups} has be dropped.