Compare commits
280 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db07c09afc | |||
| 70c8bfa038 | |||
| ccca8adbc1 | |||
| 7e01d53de3 | |||
| 3317a05ec9 | |||
| b0dddbb02a | |||
| 322dae8b9c | |||
| f6be7e2a3e | |||
| aca5d1d5c6 | |||
| 07367f22f1 | |||
| 0c47aa6a92 | |||
| e23f603c41 | |||
| 9af9711c0a | |||
| 30fadc3686 | |||
| 756090ed79 | |||
| 2630591338 | |||
| e00e4487a4 | |||
| 64f1fd7d7b | |||
| 09b06bdb28 | |||
| 9e1723f4b8 | |||
| 3b4e2ae357 | |||
| d35b9dc7bb | |||
| 500cec5757 | |||
| 891e9994b4 | |||
| 83b3ee6212 | |||
| 253c16b64d | |||
| af96d4eebe | |||
| 4e6de24f31 | |||
| 7d1563333b | |||
| f7988538b2 | |||
| 6ba71820b1 | |||
| 61c5863b1a | |||
| 0091b1a153 | |||
| b12afc89e4 | |||
| 844aef124c | |||
| 476a8fcb72 | |||
| fe22fe1c83 | |||
| cb9527cd4f | |||
| e71fc4ff3b | |||
| 89fae608a6 | |||
| 34bcb7e70e | |||
| 608f7d6aa0 | |||
| c43d07d0cf | |||
| ad46a97f53 | |||
| da4e626ffa | |||
| cb4904ea25 | |||
| 0efd3066a2 | |||
| 002b1aed6a | |||
| c73c1bafc1 | |||
| e56c732209 | |||
| cff22cd5ab | |||
| 2ec3b297d7 | |||
| 1e480e2851 | |||
| a22d64816d | |||
| f540fab1dc | |||
| 1291869f90 | |||
| 7a420b6a34 | |||
| ff963d9feb | |||
| ad693b830a | |||
| c2b0463539 | |||
| f0445d5cac | |||
| 2debc731ef | |||
| 56aaf7a998 | |||
| 86cb89c5fc | |||
| 9a75b24e1c | |||
| b19ce46723 | |||
| 370843c967 | |||
| da15787ed0 | |||
| 801a2b9497 | |||
| 7a5bdf76b6 | |||
| 63f7c6cc6a | |||
| b35b012e9e | |||
| 0204fbb810 | |||
| 5cf7efb761 | |||
| 2016d4c6dd | |||
| aae18f59aa | |||
| 4d23fe8a58 | |||
| fcfcdd6508 | |||
| 45808a99b4 | |||
| e4acde27c9 | |||
| 5182b60391 | |||
| 23ab7d4973 | |||
| af8f13bac5 | |||
| e5b2b5e0ba | |||
| 9a6b07f0de | |||
| 11e53bf4b0 | |||
| 176caab765 | |||
| bd03886512 | |||
| 6f6d2d9a0f | |||
| e6af0ea94e | |||
| 86b45212e8 | |||
| b2a485e244 | |||
| 80f521eed0 | |||
| f14fdf23e3 | |||
| 25724ebfbb | |||
| c73623506d | |||
| 7450f0e33f | |||
| 55163a6ba9 | |||
| 4e04339cbd | |||
| bf3562d13e | |||
| aca3ad2008 | |||
| f34e050e9b | |||
| c2eb42fc7b | |||
| 625e015ff8 | |||
| 9d74cebd74 | |||
| 64ad970e5e | |||
| f0fb54d907 | |||
| 5da8226f72 | |||
| 2fcd7f93b9 | |||
| 9b30118456 | |||
| dd87304fcf | |||
| b08fbf99db | |||
| b3f708d4d1 | |||
| 4572a114a9 | |||
| c889381be2 | |||
| 1e07a5eef6 | |||
| 2c40c8554b | |||
| 955f4f3bb7 | |||
| 1dca48371d | |||
| c0b9a52430 | |||
| 462b8f9861 | |||
| 242ab5d5aa | |||
| bf8a28b220 | |||
| a76a1e3f5c | |||
| 78b3369ca0 | |||
| 2f0513b91c | |||
| b25405fd56 | |||
| 5565f47e9b | |||
| 2848c7d6be | |||
| 4e151ddf30 | |||
| 4b9de301c3 | |||
| dbffeb334b | |||
| 165709ff7d | |||
| 0a75d57b13 | |||
| 3182c71093 | |||
| 56fb55683a | |||
| 78229db6a1 | |||
| d04044bb0a | |||
| c1b4ccc4c6 | |||
| 5db3c95e29 | |||
| bcae2164ac | |||
| 207eed1190 | |||
| c948d9e40e | |||
| 9ccdee7687 | |||
| 1a3e68a956 | |||
| ffe67fc6df | |||
| 8a60437bea | |||
| c62143a5df | |||
| 691f2c8950 | |||
| 6ae2c78e9f | |||
| 7a4ea4dd65 | |||
| 217a7af933 | |||
| df176b292a | |||
| fa5a564a17 | |||
| dd1d91486d | |||
| f9ab045794 | |||
| 32bf836fec | |||
| e205d5accc | |||
| 9febdf9b0f | |||
| ce8695ff1f | |||
| 3e80d6ee79 | |||
| 513bafbcfd | |||
| 7fc818ebb8 | |||
| 0219831af4 | |||
| 477aa7c750 | |||
| 47e844d0f4 | |||
| c44c1b5345 | |||
| 28e9e58577 | |||
| 6a628bcd3a | |||
| 10e44e40d9 | |||
| a625583b11 | |||
| 810ab99d7d | |||
| 82a76a1b69 | |||
| ddca055e0d | |||
| b0a11d12ca | |||
| 996564f803 | |||
| 2512e6b09e | |||
| d5d6b0f7f9 | |||
| 52b4f3bb1d | |||
| 1c2f5dc1df | |||
| 75d95bc4b8 | |||
| a6c17af4c7 | |||
| 7ac2d7f598 | |||
| c4533967d1 | |||
| 5bd4a81e09 | |||
| 72f0a11f0d | |||
| 7b873db958 | |||
| b83cd14d25 | |||
| 4b0ca64617 | |||
| 7741ae3094 | |||
| aa1dc46bc8 | |||
| 27dd2e751f | |||
| f88dbfcafc | |||
| aaf45097b7 | |||
| adb0352ef4 | |||
| da4486b202 | |||
| 778470818a | |||
| 4625eeb998 | |||
| 4e7d555c18 | |||
| 8b7f262aca | |||
| d33dd13fd5 | |||
| a548bccf18 | |||
| 9977c07a69 | |||
| eedb5fde7a | |||
| eabc15f983 | |||
| 18ac43934a | |||
| 35914200b5 | |||
| 48c064505e | |||
| 417fbe1534 | |||
| c70ceb5f7b | |||
| c4735904e5 | |||
| 0792e7bdd9 | |||
| eaf09ebbc4 | |||
| d620ee8cdf | |||
| f7124a4a95 | |||
| 34f1f22b00 | |||
| a95ac513bf | |||
| a829242d47 | |||
| 2479bc2042 | |||
| 67376511ac | |||
| b65b4892ae | |||
| be159037d4 | |||
| eccadb67b7 | |||
| 94d8b7f040 | |||
| c6bf1efac6 | |||
| 50c7842d73 | |||
| 48597285cd | |||
| e13fe01c7b | |||
| 1542a9f9e1 | |||
| 310b932482 | |||
| edf923e38e | |||
| 95a5cb8038 | |||
| 771786ad4d | |||
| 5f6b32c4cb | |||
| 8ee732f391 | |||
| 21e6eea20e | |||
| 1dbc78253c | |||
| c9d48f6f25 | |||
| 894ee17f53 | |||
| cbdfc83b69 | |||
| 6c484f9fc8 | |||
| 6de35b2623 | |||
| f0c1a3789d | |||
| 8f0f474882 | |||
| 452cf5c72d | |||
| dd88b540c2 | |||
| 6a04c121af | |||
| 83eb2a624c | |||
| 1d6bee488d | |||
| 14574fdb56 | |||
| aa213563e7 | |||
| 1d01c74390 | |||
| 0e70a88d03 | |||
| 49b666b670 | |||
| 4c1beb921f | |||
| dcb43cf3b0 | |||
| aa71fcfb7f | |||
| 53557c3be1 | |||
| 16f7008c68 | |||
| 291a5449eb | |||
| 42d52002ee | |||
| c9b7b67fad | |||
| 8a2929c1a9 | |||
| 8f53f30e02 | |||
| 9ea7607613 | |||
| 26a607e8a3 | |||
| 4378d1dadc | |||
| 582eb88404 | |||
| ec0e1d74a0 | |||
| 82eacb2e48 | |||
| 8b976e77ce | |||
| cab8184fcb | |||
| 7b18faed92 | |||
| 4adb2a7695 | |||
| 410b333e47 | |||
| a8908c1a50 | |||
| b0fec7d82b | |||
| dc0cf1f273 | |||
| 5b9fdc0e9a | |||
| d9a8eb2bdb |
@@ -0,0 +1,143 @@
|
||||
name: One by One Testing
|
||||
# Run all the individual unit tests one by one, with
|
||||
# fully independent PHPUnit executions. Useful to
|
||||
# detect issues with some tests that are using stuff
|
||||
# that has been made available by others, but is not
|
||||
# available when running individually.
|
||||
#
|
||||
# Note that we aren't using PHPUnit's own isolation
|
||||
# here but completely separated runs, one for each
|
||||
# test.
|
||||
#
|
||||
# The workflow will fail reporting all the tests
|
||||
# that have failed (and will pass if no failure is
|
||||
# detected, of course).
|
||||
#
|
||||
# It's only executed via workflow dispatch (automated
|
||||
# or manual), not by push/tag. And acceptd configuration
|
||||
# of phpunit, specially useful to run it with PHPUnit's
|
||||
# own isolation or any other option.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phpunit_extra_options:
|
||||
description: Additional options to apply to PHPUnit
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
chunks: 7
|
||||
|
||||
jobs:
|
||||
collect:
|
||||
name: Collect individual unit tests
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{steps.individual-tests.outputs.matrix }}
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Looking for all individual tests
|
||||
id: individual-tests
|
||||
run: |
|
||||
count=0 # Number of individual tests found.
|
||||
while read -r testfile; do # For each test file.
|
||||
while read -r testname; do # For each unit test in a file.
|
||||
count=$((count + 1))
|
||||
# Sent it to the correct chunk file.
|
||||
chunk=$(((($count % $chunks)) + 1))
|
||||
echo "$testname $testfile" >> ./chunk_$chunk.txt
|
||||
done < <(grep "function test_" "${testfile}" | sed -r "s/^.*function (test_[a-zA-Z0-9_]+).*/\1/")
|
||||
done < <(find . -name "*_test.php")
|
||||
# Generate the matrix to run tests.
|
||||
echo "matrix=$(ls -1 chunk_*.txt | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
echo "$count individual tests collected in $chunks files"
|
||||
|
||||
- name: Upload individual tests files
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: individual_tests
|
||||
path: chunk_*.txt
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
name: Run tests
|
||||
needs: collect
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
exttests:
|
||||
image: moodlehq/moodle-exttests
|
||||
ports:
|
||||
- 8080:80
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
file: ${{ fromJson(needs.collect.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Setting up DB pgsql
|
||||
uses: m4nu56/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: 13
|
||||
postgresql db: test
|
||||
postgresql user: test
|
||||
postgresql password: test
|
||||
|
||||
- name: Setting up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.3
|
||||
ini-values: max_input_vars=5000
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download individual test files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: individual_tests # Make all the chunk files available for the next steps.
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
echo "pathtophp=$(which php)" >> $GITHUB_ENV
|
||||
cp .github/workflows/config-template.php config.php
|
||||
mkdir ../moodledata
|
||||
sudo locale-gen en_AU.UTF-8
|
||||
php admin/tool/phpunit/cli/init.php --no-composer-self-update
|
||||
|
||||
- name: Run PHPUnit test (one by one)
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
status=0
|
||||
count=0
|
||||
while read -r line; do # For each line in the chunk file
|
||||
count=$((count + 1))
|
||||
filter="${line% *}"
|
||||
file="${line#* }"
|
||||
# Run the individual unit test and report problems if needed to.
|
||||
if ! php vendor/bin/phpunit \
|
||||
--fail-on-empty-test-suite \
|
||||
--fail-on-warning \
|
||||
--fail-on-risky \
|
||||
--filter "$filter" ${{ inputs.phpunit_extra_options }} \
|
||||
"$file" >/dev/null 2>&1; then
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "Problems found, list of PHPUnit commands failing:"
|
||||
fi
|
||||
echo "vendor/bin/phpunit --filter '${filter}' ${{ inputs.phpunit_extra_options }} $file"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
done < ${{ matrix.file }}
|
||||
echo "Finished: $count individual tests executed, $status tests failed"
|
||||
exit $status
|
||||
+1
-1
@@ -5,6 +5,6 @@ define("core_admin/bulk_user_actions",["exports","core_reportbuilder/local/selec
|
||||
* @module core_admin/bulk_user_actions
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,reportSelectors=_interopRequireWildcard(reportSelectors),tableEvents=_interopRequireWildcard(tableEvents),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),CustomEvents=_interopRequireWildcard(CustomEvents),_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const Selectors_bulkActionsForm="form#user-bulk-action-form",Selectors_userReportWrapper='[data-region="report-user-list-wrapper"]',Selectors_checkbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',Selectors_masterCheckbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',Selectors_checkedRows='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked';_exports.init=()=>{var _userBulkForm$closest;const userBulkForm=document.querySelector(Selectors_bulkActionsForm),userReport=null==userBulkForm||null===(_userBulkForm$closest=userBulkForm.closest(Selectors_userReportWrapper))||void 0===_userBulkForm$closest?void 0:_userBulkForm$closest.querySelector(reportSelectors.regions.report);if(!userBulkForm||!userReport)return;const actionSelect=userBulkForm.querySelector("select");CustomEvents.define(actionSelect,[CustomEvents.events.accessibleChange]),(0,_jquery.default)(actionSelect).on(CustomEvents.events.accessibleChange,(event=>{if(event.target.value&&"0"!=="".concat(event.target.value)){const e=new Event("submit",{cancelable:!0});userBulkForm.dispatchEvent(e),e.defaultPrevented||(FormChangeChecker.markFormSubmitted(userBulkForm),userBulkForm.submit())}}));const updateUserIds=()=>{const selectedUsers=[...userReport.querySelectorAll(Selectors_checkedRows)],selectedUserIds=selectedUsers.map((check=>parseInt(check.value)));userBulkForm.querySelector('[name="userids"]').value=selectedUserIds.join(","),actionSelect.disabled=0===selectedUsers.length;const selectedUsersNames=selectedUsers.map((check=>document.querySelector('label[for="'.concat(check.id,'"]')).textContent));userBulkForm.data={userids:selectedUserIds,usernames:selectedUsersNames}};updateUserIds(),document.addEventListener("change",(event=>{(event.target.matches(Selectors_checkbox)||event.target.matches(Selectors_masterCheckbox))&&userReport.contains(event.target)&&updateUserIds()})),document.addEventListener(tableEvents.tableContentRefreshed,(event=>{userReport.contains(event.target)&&updateUserIds()}))}}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,reportSelectors=_interopRequireWildcard(reportSelectors),tableEvents=_interopRequireWildcard(tableEvents),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),CustomEvents=_interopRequireWildcard(CustomEvents),_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const Selectors_bulkActionsForm="form#user-bulk-action-form",Selectors_userReportWrapper='[data-region="report-user-list-wrapper"]',Selectors_checkbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',Selectors_masterCheckbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',Selectors_checkedRows='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked';_exports.init=()=>{var _userBulkForm$closest;const userBulkForm=document.querySelector(Selectors_bulkActionsForm),userReport=null==userBulkForm||null===(_userBulkForm$closest=userBulkForm.closest(Selectors_userReportWrapper))||void 0===_userBulkForm$closest?void 0:_userBulkForm$closest.querySelector(reportSelectors.regions.report);if(!userBulkForm||!userReport)return;const actionSelect=userBulkForm.querySelector("select");CustomEvents.define(actionSelect,[CustomEvents.events.accessibleChange]),(0,_jquery.default)(actionSelect).on(CustomEvents.events.accessibleChange,(event=>{if(event.target.value&&"0"!=="".concat(event.target.value)){const e=new Event("submit",{cancelable:!0});userBulkForm.dispatchEvent(e),e.defaultPrevented||(FormChangeChecker.markFormSubmitted(userBulkForm),userBulkForm.submit())}}));const updateUserIds=()=>{const selectedUsers=[...userReport.querySelectorAll(Selectors_checkedRows)],selectedUserIds=selectedUsers.map((check=>parseInt(check.value)));userBulkForm.querySelector('[name="userids"]').value=selectedUserIds.join(","),actionSelect.disabled=0===selectedUsers.length,actionSelect.disabled&&(actionSelect.value="0");const selectedUsersNames=selectedUsers.map((check=>document.querySelector('label[for="'.concat(check.id,'"]')).textContent));userBulkForm.data={userids:selectedUserIds,usernames:selectedUsersNames}};updateUserIds(),document.addEventListener("change",(event=>{(event.target.matches(Selectors_checkbox)||event.target.matches(Selectors_masterCheckbox))&&userReport.contains(event.target)&&updateUserIds()})),document.addEventListener(tableEvents.tableContentRefreshed,(event=>{userReport.contains(event.target)&&updateUserIds()}))}}));
|
||||
|
||||
//# sourceMappingURL=bulk_user_actions.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -65,7 +65,13 @@ export const init = () => {
|
||||
const selectedUsers = [...userReport.querySelectorAll(Selectors.checkedRows)];
|
||||
const selectedUserIds = selectedUsers.map(check => parseInt(check.value));
|
||||
userBulkForm.querySelector('[name="userids"]').value = selectedUserIds.join(',');
|
||||
|
||||
// Disable the action selector if nothing selected, and reset the current selection.
|
||||
actionSelect.disabled = selectedUsers.length === 0;
|
||||
if (actionSelect.disabled) {
|
||||
actionSelect.value = "0";
|
||||
}
|
||||
|
||||
const selectedUsersNames = selectedUsers.map(check => document.querySelector(`label[for="${check.id}"]`).textContent);
|
||||
// Add the user ids and names to the form data attributes so they can be available from the
|
||||
// other JS modules that listen to the form submit event.
|
||||
|
||||
@@ -184,6 +184,7 @@ class users extends system_report {
|
||||
$entityuseralias = $entityuser->get_table_alias('user');
|
||||
|
||||
$filters = [
|
||||
'user:fullname',
|
||||
'user:firstname',
|
||||
'user:lastname',
|
||||
'user:username',
|
||||
|
||||
@@ -29,6 +29,14 @@ use stdClass;
|
||||
* @coversDefaultClass \core_adminpresets\manager
|
||||
*/
|
||||
class manager_test extends \advanced_testcase {
|
||||
/**
|
||||
* Include required libraries.
|
||||
*/
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of protected get_site_settings method.
|
||||
*
|
||||
|
||||
@@ -159,3 +159,24 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
|
||||
And I should not see "User One" in the "reportbuilder-table" "table"
|
||||
And I should not see "User Three" in the "reportbuilder-table" "table"
|
||||
And I should not see "User Four" in the "reportbuilder-table" "table"
|
||||
|
||||
@javascript
|
||||
Scenario: Filter users by full name
|
||||
When I click on "Filters" "button"
|
||||
And I set the following fields in the "Full name" "core_reportbuilder > Filter" to these values:
|
||||
| Full name operator | Is equal to |
|
||||
| Full name value | User One |
|
||||
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
|
||||
Then I should see "User One" in the "reportbuilder-table" "table"
|
||||
And I should not see "User Two" in the "reportbuilder-table" "table"
|
||||
And I should not see "User Three" in the "reportbuilder-table" "table"
|
||||
And I should not see "User Four" in the "reportbuilder-table" "table"
|
||||
And I click on "Reset all" "button" in the "[data-region='report-filters']" "css_element"
|
||||
And I set the following fields in the "Full name" "core_reportbuilder > Filter" to these values:
|
||||
| Full name operator | Is equal to |
|
||||
| Full name value | User Two |
|
||||
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
|
||||
And I should see "User Two" in the "reportbuilder-table" "table"
|
||||
And I should not see "User One" in the "reportbuilder-table" "table"
|
||||
And I should not see "User Three" in the "reportbuilder-table" "table"
|
||||
And I should not see "User Four" in the "reportbuilder-table" "table"
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_behat;
|
||||
|
||||
use behat_config_util;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
@@ -39,7 +43,7 @@ require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
|
||||
* @copyright 2016 Rajesh Taneja
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
class manager_util_test extends \advanced_testcase {
|
||||
|
||||
/** @var array Fixtures features which are available. */
|
||||
private $featurepaths = array(
|
||||
|
||||
@@ -48,5 +48,8 @@ function xmldb_tool_cohortroles_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -33,5 +33,8 @@ function xmldb_tool_customlang_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ class hook_callbacks {
|
||||
$hook->add_html(
|
||||
html_writer::div(
|
||||
html_writer::link($url, get_string('dataretentionsummary', 'tool_dataprivacy')),
|
||||
'tool_dataprivacy',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,5 +103,8 @@ function xmldb_tool_dataprivacy_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class edit_license extends moodleform {
|
||||
$mform = $this->_form;
|
||||
|
||||
$mform->addElement('text', 'shortname', get_string('shortname', 'tool_licensemanager'));
|
||||
$mform->setType('shortname', PARAM_ALPHANUMEXT);
|
||||
$mform->setType('shortname', PARAM_NOTAGS);
|
||||
// Shortname is only editable when user is creating a license.
|
||||
if ($this->action != manager::ACTION_CREATE) {
|
||||
$mform->freeze('shortname');
|
||||
|
||||
@@ -29,7 +29,7 @@ require_once($CFG->libdir . '/licenselib.php');
|
||||
require_admin();
|
||||
|
||||
$action = optional_param('action', '', PARAM_ALPHANUMEXT);
|
||||
$license = optional_param('license', '', PARAM_SAFEDIR);
|
||||
$license = optional_param('license', '', PARAM_NOTAGS);
|
||||
|
||||
// Route via the manager.
|
||||
$licensemanager = new \tool_licensemanager\manager();
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_tool_log_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,5 +32,8 @@ function xmldb_logstore_database_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,5 +32,8 @@ function xmldb_logstore_standard_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -76,10 +76,11 @@ class verification_field extends \MoodleQuickForm_text {
|
||||
return parent::__construct($elementname, $elementlabel, $attributes);
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Returns HTML for this form element.
|
||||
*
|
||||
* phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHtml(): string {
|
||||
@@ -101,7 +102,6 @@ class verification_field extends \MoodleQuickForm_text {
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
/**
|
||||
* Setup and return the script for autosubmission while inside the secure layout.
|
||||
|
||||
@@ -349,12 +349,10 @@ class manager {
|
||||
try {
|
||||
// Clear locked user factors, they may now reauth with anything.
|
||||
@$DB->set_field('tool_mfa', 'lockcounter', 0, ['userid' => $USER->id]);
|
||||
// @codingStandardsIgnoreStart
|
||||
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
||||
} catch (\Exception $e) {
|
||||
// This occurs when upgrade.php hasn't been run. Nothing to do here.
|
||||
// Coding standards ignored, they break on empty catches.
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
// Fire post pass state factor actions.
|
||||
$factors = factor::get_active_user_factor_types();
|
||||
|
||||
@@ -48,5 +48,8 @@ function xmldb_factor_auth_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -22,13 +22,14 @@
|
||||
* @copyright Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// Ignore coding standards for login check, this page does not require login.
|
||||
// @codingStandardsIgnoreStart
|
||||
// phpcs:disable moodle.Files.RequireLogin.Missing
|
||||
require_once(__DIR__ . '/../../../../../config.php');
|
||||
|
||||
$instanceid = required_param('instance', PARAM_INT);
|
||||
$pass = optional_param('pass', '0', PARAM_INT);
|
||||
$secret = optional_param('secret', 0, PARAM_INT);
|
||||
// @codingStandardsIgnoreEnds
|
||||
|
||||
$context = context_system::instance();
|
||||
$PAGE->set_context($context);
|
||||
|
||||
@@ -63,7 +63,7 @@ $string['settings:countrycode_help'] = 'The calling code without the leading + a
|
||||
See this link for a list of calling codes: {$a}';
|
||||
$string['settings:duration'] = 'Validity duration';
|
||||
$string['settings:duration_help'] = 'The period of time that the code is valid.';
|
||||
$string['settings:gateway'] = 'SMS Gateway';
|
||||
$string['settings:gateway'] = 'SMS gateway';
|
||||
$string['settings:gateway_help'] = 'The SMS provider you wish to send messages via';
|
||||
$string['setupfactor'] = 'Set up SMS';
|
||||
$string['setupfactorbutton'] = 'Set up';
|
||||
|
||||
@@ -462,13 +462,13 @@ class api {
|
||||
public static function get_qrlogin_key(stdClass $mobilesettings) {
|
||||
global $USER;
|
||||
// Delete previous keys.
|
||||
delete_user_key('tool_mobile', $USER->id);
|
||||
delete_user_key('tool_mobile/qrlogin', $USER->id);
|
||||
|
||||
// Create a new key.
|
||||
$iprestriction = !empty($mobilesettings->qrsameipcheck) ? getremoteaddr(null) : null;
|
||||
$qrkeyttl = !empty($mobilesettings->qrkeyttl) ? $mobilesettings->qrkeyttl : self::LOGIN_QR_KEY_TTL;
|
||||
$validuntil = time() + $qrkeyttl;
|
||||
return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil);
|
||||
return create_user_key('tool_mobile/qrlogin', $USER->id, null, $iprestriction, $validuntil);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -648,8 +648,8 @@ class external extends external_api {
|
||||
api::check_autologin_prerequisites($params['userid']); // Checks https, avoid site admins using this...
|
||||
|
||||
// Validate and delete the key.
|
||||
$key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
|
||||
delete_user_key('tool_mobile', $params['userid']);
|
||||
$key = validate_user_key($params['qrloginkey'], 'tool_mobile/qrlogin', null);
|
||||
delete_user_key('tool_mobile/qrlogin', $params['userid']);
|
||||
|
||||
// Double check key belong to user.
|
||||
if ($key->userid != $params['userid']) {
|
||||
|
||||
@@ -66,7 +66,7 @@ class provider implements
|
||||
FROM {user_private_key} k
|
||||
JOIN {user} u ON k.userid = u.id
|
||||
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
|
||||
WHERE k.userid = :userid AND k.script = 'tool_mobile'";
|
||||
WHERE k.userid = :userid AND (k.script = 'tool_mobile' OR k.script = 'tool_mobile/qrlogin')";
|
||||
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
|
||||
$contextlist = new contextlist();
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
@@ -88,6 +88,7 @@ class provider implements
|
||||
|
||||
// Add users based on userkey.
|
||||
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile');
|
||||
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile/qrlogin');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +109,7 @@ class provider implements
|
||||
}
|
||||
// Export associated userkeys.
|
||||
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile');
|
||||
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile/qrlogin');
|
||||
}
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
@@ -138,6 +140,7 @@ class provider implements
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
@@ -158,6 +161,7 @@ class provider implements
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,5 +182,6 @@ class provider implements
|
||||
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_tool_mobile_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,14 @@ if (empty($SESSION->justloggedin) &&
|
||||
throw new moodle_exception('pluginnotenabledorconfigured', 'tool_mobile');
|
||||
}
|
||||
|
||||
// If the user is using the inapp (embedded) browser, we need to set the Secure and Partitioned attributes to the session cookie.
|
||||
if (\core_useragent::is_moodle_app()) {
|
||||
\core\session\utility\cookie_helper::add_attributes_to_cookie_response_header(
|
||||
"MoodleSession{$CFG->sessioncookie}",
|
||||
['Secure', 'Partitioned'],
|
||||
);
|
||||
}
|
||||
|
||||
require_login(0, false);
|
||||
|
||||
// Require an active user: not guest, not suspended.
|
||||
|
||||
@@ -87,7 +87,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$context1 = \context_user::instance($user1->id);
|
||||
$context2 = \context_user::instance($user2->id);
|
||||
$key1 = get_user_key('tool_mobile', $user1->id);
|
||||
$key2 = get_user_key('tool_mobile', $user2->id);
|
||||
$key2 = get_user_key('tool_mobile/qrlogin', $user1->id);
|
||||
$key3 = get_user_key('tool_mobile', $user2->id);
|
||||
|
||||
// Ensure only user1 is found in context1.
|
||||
$userlist = new \core_privacy\local\request\userlist($context1, $component);
|
||||
@@ -175,12 +176,15 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$context1 = \context_user::instance($user1->id);
|
||||
$context2 = \context_user::instance($user2->id);
|
||||
$keyvalue1 = get_user_key('tool_mobile', $user1->id);
|
||||
$keyvalue2 = get_user_key('tool_mobile', $user2->id);
|
||||
$keyvalue2 = get_user_key('tool_mobile/qrlogin', $user1->id);
|
||||
$keyvalue3 = get_user_key('tool_mobile', $user2->id);
|
||||
$key1 = $DB->get_record('user_private_key', ['value' => $keyvalue1]);
|
||||
|
||||
// Before deletion, we should have 2 user_private_keys.
|
||||
// Before deletion, we should have 2 user_private_keys for tool_mobile and one for tool_mobile/qrlogin.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(2, $count);
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
|
||||
$this->assertEquals(1, $count);
|
||||
|
||||
// Ensure deleting wrong user in the user context does nothing.
|
||||
$approveduserids = [$user2->id];
|
||||
@@ -198,6 +202,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
// Ensure only user1's data is deleted, user2's remains.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(1, $count);
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
|
||||
$this->assertEquals(0, $count);
|
||||
|
||||
$params = ['script' => $component];
|
||||
$userid = $DB->get_field_select('user_private_key', 'userid', 'script = :script', $params);
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_tool_monitor_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_tool_moodlenet_upgrade(int $oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ if ($cancel) {
|
||||
}
|
||||
redirect($url);
|
||||
} else if ($continue) {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
// Handle backups.
|
||||
if (strtolower($importinfo->get_resource()->get_extension()) == 'mbz') {
|
||||
|
||||
@@ -81,7 +81,7 @@ switch ($config->type) {
|
||||
}
|
||||
|
||||
if ($import && $module) {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($importinfo->get_resource(), $module, $strategy);
|
||||
if (is_null($handlerinfo)) {
|
||||
|
||||
@@ -1034,7 +1034,9 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
if ($user->policyagreed != $allresponded) {
|
||||
// MDL-80973: At this point, the policyagreed value in DB could be 0 but $user->policyagreed could be 1 (as it was copied from $USER).
|
||||
// So we need to ensure that the value in DB is set true if all policies were responded.
|
||||
if ($user->policyagreed != $allresponded || $allresponded) {
|
||||
$user->policyagreed = $allresponded;
|
||||
$DB->set_field('user', 'policyagreed', $allresponded, ['id' => $user->id]);
|
||||
}
|
||||
|
||||
@@ -39,5 +39,8 @@ function xmldb_tool_policy_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -579,7 +579,7 @@ class api_test extends \advanced_testcase {
|
||||
* Test that accepting policy updates 'policyagreed'
|
||||
*/
|
||||
public function test_accept_policies() {
|
||||
global $DB;
|
||||
global $DB, $USER;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
@@ -623,6 +623,12 @@ class api_test extends \advanced_testcase {
|
||||
|
||||
api::accept_policies([$policy3->id]);
|
||||
$this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $user2->id]));
|
||||
|
||||
// Ensure policies are always accepted when all are responded regardless the $USER->policyagreed value.
|
||||
$USER->policyagreed = 1;
|
||||
$DB->set_field('user', 'policyagreed', 0, ['id' => $user2->id]);
|
||||
api::accept_policies([$policy3->id]);
|
||||
$this->assertEquals(1, $DB->get_field('user', 'policyagreed', ['id' => $user2->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -247,10 +247,10 @@ class tool_uploadcourse_course {
|
||||
* Log an error
|
||||
*
|
||||
* @param string $code error code.
|
||||
* @param lang_string $message error message.
|
||||
* @param string $message error message.
|
||||
* @return void
|
||||
*/
|
||||
protected function error($code, lang_string $message) {
|
||||
protected function error($code, string $message) {
|
||||
if (array_key_exists($code, $this->errors)) {
|
||||
throw new coding_exception('Error code already defined');
|
||||
}
|
||||
@@ -1129,34 +1129,7 @@ class tool_uploadcourse_course {
|
||||
break;
|
||||
}
|
||||
|
||||
// Now update values.
|
||||
$modifiedinstance = $instance;
|
||||
|
||||
// Sort out the start, end and date.
|
||||
$modifiedinstance->enrolstartdate = (isset($method['startdate']) ? strtotime($method['startdate']) : 0);
|
||||
$modifiedinstance->enrolenddate = (isset($method['enddate']) ? strtotime($method['enddate']) : 0);
|
||||
|
||||
// Is the enrolment period set?
|
||||
if (isset($method['enrolperiod']) && !empty($method['enrolperiod'])) {
|
||||
if (preg_match('/^\d+$/', $method['enrolperiod'])) {
|
||||
$method['enrolperiod'] = (int)$method['enrolperiod'];
|
||||
} else {
|
||||
// Try and convert period to seconds.
|
||||
$method['enrolperiod'] = strtotime('1970-01-01 GMT + ' . $method['enrolperiod']);
|
||||
}
|
||||
$modifiedinstance->enrolperiod = $method['enrolperiod'];
|
||||
}
|
||||
if ($instance->enrolstartdate > 0 && isset($method['enrolperiod'])) {
|
||||
$modifiedinstance->enrolenddate = $instance->enrolstartdate + $method['enrolperiod'];
|
||||
}
|
||||
if ($instance->enrolenddate > 0) {
|
||||
$modifiedinstance->enrolperiod = $instance->enrolenddate - $instance->enrolstartdate;
|
||||
}
|
||||
if ($instance->enrolenddate < $instance->enrolstartdate) {
|
||||
$modifiedinstance->enrolenddate = $instance->enrolstartdate;
|
||||
}
|
||||
|
||||
// Sort out the given role.
|
||||
// Validate role context again since course is created.
|
||||
if (isset($method['role']) || isset($method['roleid'])) {
|
||||
if (isset($method['role'])) {
|
||||
$role = $method['role'];
|
||||
@@ -1166,22 +1139,14 @@ class tool_uploadcourse_course {
|
||||
$role = $DB->get_field('role', 'shortname', ['id' => $roleid], MUST_EXIST);
|
||||
}
|
||||
if (!$this->validate_role_context($course->id, $roleid)) {
|
||||
$this->error('contextrolenotallowed',
|
||||
new lang_string('contextrolenotallowed', 'core_role', $role));
|
||||
$this->error('contextrolenotallowed', new lang_string('contextrolenotallowed', 'core_role', $role));
|
||||
break;
|
||||
}
|
||||
|
||||
$roleids = tool_uploadcourse_helper::get_role_ids();
|
||||
if (in_array($roleid, $roleids)) {
|
||||
$modifiedinstance->roleid = $roleid;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort out custom instance name.
|
||||
if (isset($method['name'])) {
|
||||
$modifiedinstance->name = $method['name'];
|
||||
}
|
||||
|
||||
// Now update values.
|
||||
// Sort out plugin specific fields.
|
||||
$modifiedinstance = $plugin->update_enrol_plugin_data($course->id, $method, $instance);
|
||||
$plugin->update_instance($instance, $modifiedinstance);
|
||||
} else {
|
||||
foreach ($errors as $key => $message) {
|
||||
|
||||
@@ -31,7 +31,7 @@ Feature: An admin can create courses with cohort enrolments using a CSV file
|
||||
| enrol_plugins_enabled | manual,guest,self |
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_cohort.csv" file to "File" filemanager
|
||||
When I click on "Preview" "button"
|
||||
Then I should see "Cohort sync plugin is disabled"
|
||||
Then I should see "Cohort sync enrol plugin is disabled"
|
||||
|
||||
@javascript
|
||||
Scenario: Validation of cohorts for uploaded courses
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
@tool @tool_uploadcourse @_file_upload
|
||||
Feature: An admin can create courses with guest enrolments using a CSV file
|
||||
In order to create courses using a CSV file with guest enrolment
|
||||
As an admin
|
||||
I need to be able to upload a CSV file and navigate through the import process
|
||||
|
||||
Background:
|
||||
Given the following "categories" exist:
|
||||
| name | category | idnumber |
|
||||
| Cat 0 | 0 | CAT0 |
|
||||
| Cat 1 | CAT0 | CAT1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | CAT1 |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
|
||||
@javascript
|
||||
Scenario: Validation of password for uploaded courses with guest enrolments
|
||||
# usepasswordpolicy is not set.
|
||||
Given I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_guest.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 2"
|
||||
And I should see "Courses updated: 1"
|
||||
And I should see "Courses errors: 0"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And the field "Password" matches value "test"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And the field "Password" matches value ""
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And the field "Password" matches value "Test123@"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I press "Continue"
|
||||
|
||||
# Policy is used, but password not required so it will not be generated if omitted.
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| usepasswordpolicy | 1 | enrol_guest |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_guest.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "Passwords must be at least 8 characters long."
|
||||
And I should see "Passwords must have at least 1 digit(s)."
|
||||
And I should see "Passwords must have at least 1 upper case letter(s)."
|
||||
And I should see "The password must have at least 1 special character(s) such as *, -, or #."
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 0"
|
||||
And I should see "Courses updated: 2"
|
||||
And I should see "Courses errors: 1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And "Guest access" "table_row" should not exist
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And the field "Password" matches value ""
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And the field "Password" matches value "Test123@"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I press "Continue"
|
||||
|
||||
# Policy is used and password not required so it will be generated if omitted.
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| requirepassword | 1 | enrol_guest |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_guest.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "Passwords must be at least 8 characters long."
|
||||
And I should see "Passwords must have at least 1 digit(s)."
|
||||
And I should see "Passwords must have at least 1 upper case letter(s)."
|
||||
And I should see "The password must have at least 1 special character(s) such as *, -, or #."
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 0"
|
||||
And I should see "Courses updated: 2"
|
||||
And I should see "Courses errors: 1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And "Guest access" "table_row" should not exist
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And the field "Password" does not match value ""
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And the field "Password" matches value "Test123@"
|
||||
@@ -0,0 +1,122 @@
|
||||
@tool @tool_uploadcourse @_file_upload
|
||||
Feature: An admin can create courses with self enrolments using a CSV file
|
||||
In order to create courses using a CSV file with self enrolment
|
||||
As an admin
|
||||
I need to be able to upload a CSV file and navigate through the import process
|
||||
|
||||
Background:
|
||||
Given the following "categories" exist:
|
||||
| name | category | idnumber |
|
||||
| Cat 0 | 0 | CAT0 |
|
||||
| Cat 1 | CAT0 | CAT1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | CAT1 |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber | enrolmentkey |
|
||||
| group1 | C1 | G1 | test |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
|
||||
@javascript
|
||||
Scenario: Validation of password for uploaded courses with self enrolments
|
||||
# usepasswordpolicy is not set.
|
||||
Given I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 2"
|
||||
And I should see "Courses updated: 1"
|
||||
And I should see "Courses errors: 0"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "test"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value ""
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "Test123@"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
|
||||
# Policy is used, but password not required so it will not be generated if omitted.
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| usepasswordpolicy | 1 | enrol_self |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "Passwords must be at least 8 characters long."
|
||||
And I should see "Passwords must have at least 1 digit(s)."
|
||||
And I should see "Passwords must have at least 1 upper case letter(s)."
|
||||
And I should see "The password must have at least 1 special character(s) such as *, -, or #."
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 0"
|
||||
And I should see "Courses updated: 2"
|
||||
And I should see "Courses errors: 1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And "Self enrolment (Student)" "table_row" should not exist
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value ""
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "Test123@"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
|
||||
# Policy is used and password not required so it will be generated if omitted.
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| requirepassword | 1 | enrol_self |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "Passwords must be at least 8 characters long."
|
||||
And I should see "Passwords must have at least 1 digit(s)."
|
||||
And I should see "Passwords must have at least 1 upper case letter(s)."
|
||||
And I should see "The password must have at least 1 special character(s) such as *, -, or #."
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 0"
|
||||
And I should see "Courses updated: 2"
|
||||
And I should see "Courses errors: 1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And "Self enrolment (Student)" "table_row" should not exist
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" does not match value ""
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "Test123@"
|
||||
|
||||
# Use group enrolment keys is set to yes, so the password must be different from group enrolment key.
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And I add "Self enrolment" enrolment method in "Course 1" with:
|
||||
| Enrolment key | Abcejfd12@ |
|
||||
| Use group enrolment keys | Yes |
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| usepasswordpolicy | 0 | enrol_self |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "This enrolment key is already used as a group enrolment key."
|
||||
@@ -0,0 +1,4 @@
|
||||
shortname,fullname,category_idnumber,enrolment_1,enrolment_1_role,enrolment_1_password
|
||||
C1,Course 1,CAT1,guest,student,test
|
||||
C2,Course 2,CAT1,guest,student,
|
||||
C3,Course 3,CAT1,guest,student,Test123@
|
||||
|
@@ -0,0 +1,4 @@
|
||||
shortname,fullname,category_idnumber,enrolment_1,enrolment_1_role,enrolment_1_password
|
||||
C1,Course 1,CAT1,self,student,test
|
||||
C2,Course 2,CAT1,self,student,
|
||||
C3,Course 3,CAT1,self,student,Test123@
|
||||
|
@@ -107,6 +107,7 @@ class helper_test extends \advanced_testcase {
|
||||
|
||||
public function test_get_restore_content_dir() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
|
||||
@@ -49,5 +49,8 @@ function xmldb_tool_usertours_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
$sitecontext = context_system::instance();
|
||||
$site = get_site();
|
||||
|
||||
if (!has_capability('moodle/user:update', $sitecontext) and !has_capability('moodle/user:delete', $sitecontext)) {
|
||||
throw new \moodle_exception('nopermissions', 'error', '', 'edit/delete users');
|
||||
}
|
||||
|
||||
$returnurl = new moodle_url('/admin/user.php');
|
||||
|
||||
$PAGE->set_primary_active_tab('siteadminnode');
|
||||
|
||||
@@ -148,6 +148,10 @@ class user_bulk_action_form extends moodleform {
|
||||
public function definition() {
|
||||
$mform =& $this->_form;
|
||||
|
||||
// Most bulk actions perform a redirect on selection, so we shouldn't trigger formchange warnings (specifically because
|
||||
// the user must have _already_ changed the current form by selecting users to perform the action on).
|
||||
$mform->disable_form_change_checker();
|
||||
|
||||
$mform->addElement('hidden', 'returnurl');
|
||||
$mform->setType('returnurl', PARAM_LOCALURL);
|
||||
|
||||
|
||||
@@ -14,18 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Any element analysers can analyse.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_analytics;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Any element analysers can analyse.
|
||||
*
|
||||
@@ -37,9 +27,8 @@ interface analysable {
|
||||
|
||||
/**
|
||||
* Max timestamp.
|
||||
* We are limited by both PHP's max int value and DB (cross-db) max int allowed. Use the smallest one.
|
||||
*/
|
||||
const MAX_TIME = PHP_INT_MAX < 9999999999 ? PHP_MAX_INT : 9999999999;
|
||||
const MAX_TIME = 9999999999;
|
||||
|
||||
/**
|
||||
* The analysable unique identifier in the site.
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_cas_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_db_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_email_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_ldap_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_auth_lti_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_manual_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_mnet_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_none_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_auth_oauth2_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_auth_shibboleth_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ abstract class info {
|
||||
/** @var tree Availability configuration, decoded from JSON; null if unset */
|
||||
protected $availabilitytree;
|
||||
|
||||
/** @var array The groups the current user belongs to. */
|
||||
protected $groups;
|
||||
/** @var array The groups each user belongs to. */
|
||||
protected $groups = [];
|
||||
|
||||
/** @var array|null Array of information about current restore if any */
|
||||
protected static $restoreinfo = null;
|
||||
@@ -61,14 +61,12 @@ abstract class info {
|
||||
* @param \stdClass $course Course object
|
||||
* @param int $visible Value of visible flag (eye icon)
|
||||
* @param string $availability Availability definition (JSON format) or null
|
||||
* @throws \coding_exception If data is not valid JSON format
|
||||
*/
|
||||
public function __construct($course, $visible, $availability) {
|
||||
// Set basic values.
|
||||
$this->course = $course;
|
||||
$this->visible = (bool)$visible;
|
||||
$this->availability = $availability;
|
||||
$this->groups = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -806,22 +804,26 @@ abstract class info {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns groups that the current user belongs to on the course. Note: If not already
|
||||
* Returns groups that the given user belongs to on the course. Note: If not already
|
||||
* available, this may make a database query.
|
||||
*
|
||||
* This will include groups the user is not allowed to see themselves, so check visibility
|
||||
* before displaying groups to the user.
|
||||
*
|
||||
* @param int $groupingid Grouping ID or 0 (default) for all groups
|
||||
* @param int $userid User ID or 0 (default) for current user
|
||||
* @return int[] Array of int (group id) => int (same group id again); empty array if none
|
||||
*/
|
||||
public function get_groups(int $groupingid = 0): array {
|
||||
public function get_groups(int $groupingid = 0, int $userid = 0): array {
|
||||
global $USER;
|
||||
if (is_null($this->groups)) {
|
||||
$allgroups = groups_get_user_groups($this->course->id, $USER->id, true);
|
||||
$this->groups = $allgroups;
|
||||
if (empty($userid)) {
|
||||
$userid = $USER->id;
|
||||
}
|
||||
if (!array_key_exists($userid, $this->groups)) {
|
||||
$allgroups = groups_get_user_groups($this->course->id, $userid, true);
|
||||
$this->groups[$userid] = $allgroups;
|
||||
} else {
|
||||
$allgroups = $this->groups;
|
||||
$allgroups = $this->groups[$userid];
|
||||
}
|
||||
if (!isset($allgroups[$groupingid])) {
|
||||
return [];
|
||||
|
||||
+2
-2
@@ -46,13 +46,13 @@ M.availability_grade.form.getNode = function(json) {
|
||||
html += '<option value="' + grade.id + '">' + grade.name + '</option>';
|
||||
}
|
||||
html += '</select></span></label> <br><span class="availability-group mb-3">' +
|
||||
'<label><input type="checkbox" class="form-check-input mx-1" name="min"/>' +
|
||||
'<label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="min"/>' +
|
||||
M.util.get_string('option_min', 'availability_grade') +
|
||||
'</label> <label><span class="accesshide">' + M.util.get_string('label_min', 'availability_grade') +
|
||||
'</span><input type="text" class="form-control mx-1" name="minval" title="' +
|
||||
M.util.get_string('label_min', 'availability_grade') + '"/></label>%</span><br>' +
|
||||
'<span class="availability-group mb-3">' +
|
||||
'<label><input type="checkbox" class="form-check-input mx-1" name="max"/>' +
|
||||
'<label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="max"/>' +
|
||||
M.util.get_string('option_max', 'availability_grade') +
|
||||
'</label> <label><span class="accesshide">' + M.util.get_string('label_max', 'availability_grade') +
|
||||
'</span><input type="text" class="form-control mx-1" name="maxval" title="' +
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
YUI.add("moodle-availability_grade-form",function(r,a){M.availability_grade=M.availability_grade||{},M.availability_grade.form=r.Object(M.core_availability.plugin),M.availability_grade.form.grades=null,M.availability_grade.form.initInner=function(a){this.grades=a,this.nodesSoFar=0},M.availability_grade.form.getNode=function(a){var e,i,l,t,n;for(this.nodesSoFar++,e='<label class="mb-3"><span class="pr-3">'+M.util.get_string("title","availability_grade")+'</span> <span class="availability-group"><select name="id" class="custom-select"><option value="0">'+M.util.get_string("choosedots","moodle")+"</option>",i=0;i<this.grades.length;i++)e+='<option value="'+(l=this.grades[i]).id+'">'+l.name+"</option>";return e+='</select></span></label> <br><span class="availability-group mb-3"><label><input type="checkbox" class="form-check-input mx-1" name="min"/>'+M.util.get_string("option_min","availability_grade")+'</label> <label><span class="accesshide">'+M.util.get_string("label_min","availability_grade")+'</span><input type="text" class="form-control mx-1" name="minval" title="'+M.util.get_string("label_min","availability_grade")+'"/></label>%</span><br><span class="availability-group mb-3"><label><input type="checkbox" class="form-check-input mx-1" name="max"/>'+M.util.get_string("option_max","availability_grade")+'</label> <label><span class="accesshide">'+M.util.get_string("label_max","availability_grade")+'</span><input type="text" class="form-control mx-1" name="maxval" title="'+M.util.get_string("label_max","availability_grade")+'"/></label>%</span>',t=r.Node.create('<div class="d-inline-block d-flex flex-wrap align-items-center">'+e+"</div>"),a.id!==undefined&&t.one("select[name=id] > option[value="+a.id+"]")&&t.one("select[name=id]").set("value",""+a.id),a.min!==undefined&&(t.one("input[name=min]").set("checked",!0),t.one("input[name=minval]").set("value",a.min)),a.max!==undefined&&(t.one("input[name=max]").set("checked",!0),t.one("input[name=maxval]").set("value",a.max)),n=function(a,e){var i=a.ancestor("label").next("label").one("input"),a=a.get("checked");return i.set("disabled",!a),e&&a&&i.focus(),a},t.all("input[type=checkbox]").each(n),M.availability_grade.form.addedEvents||(M.availability_grade.form.addedEvents=!0,(a=r.one(".availability-field")).delegate("change",function(){M.core_availability.form.update()},".availability_grade select[name=id]"),a.delegate("click",function(){n(this,!0),M.core_availability.form.update()},".availability_grade input[type=checkbox]"),a.delegate("valuechange",function(){M.core_availability.form.update()},".availability_grade input[type=text]")),t},M.availability_grade.form.fillValue=function(a,e){a.id=parseInt(e.one("select[name=id]").get("value"),10),e.one("input[name=min]").get("checked")&&(a.min=this.getValue("minval",e)),e.one("input[name=max]").get("checked")&&(a.max=this.getValue("maxval",e))},M.availability_grade.form.getValue=function(a,e){a=e.one("input[name="+a+"]").get("value");return!/^[0-9]+([.,][0-9]+)?$/.test(a)||(e=parseFloat(a.replace(",",".")))<0||100<e?a:e},M.availability_grade.form.fillErrors=function(a,e){var i={};this.fillValue(i,e),0===i.id&&a.push("availability_grade:error_selectgradeid"),i.min!==undefined&&"string"==typeof i.min||i.max!==undefined&&"string"==typeof i.max?a.push("availability_grade:error_invalidnumber"):i.min!==undefined&&i.max!==undefined&&i.max<=i.min&&a.push("availability_grade:error_backwardrange")}},"@VERSION@",{requires:["base","node","event","moodle-core_availability-form"]});
|
||||
YUI.add("moodle-availability_grade-form",function(r,a){M.availability_grade=M.availability_grade||{},M.availability_grade.form=r.Object(M.core_availability.plugin),M.availability_grade.form.grades=null,M.availability_grade.form.initInner=function(a){this.grades=a,this.nodesSoFar=0},M.availability_grade.form.getNode=function(a){var e,i,l,t,n;for(this.nodesSoFar++,e='<label class="mb-3"><span class="pr-3">'+M.util.get_string("title","availability_grade")+'</span> <span class="availability-group"><select name="id" class="custom-select"><option value="0">'+M.util.get_string("choosedots","moodle")+"</option>",i=0;i<this.grades.length;i++)e+='<option value="'+(l=this.grades[i]).id+'">'+l.name+"</option>";return e+='</select></span></label> <br><span class="availability-group mb-3"><label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="min"/>'+M.util.get_string("option_min","availability_grade")+'</label> <label><span class="accesshide">'+M.util.get_string("label_min","availability_grade")+'</span><input type="text" class="form-control mx-1" name="minval" title="'+M.util.get_string("label_min","availability_grade")+'"/></label>%</span><br><span class="availability-group mb-3"><label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="max"/>'+M.util.get_string("option_max","availability_grade")+'</label> <label><span class="accesshide">'+M.util.get_string("label_max","availability_grade")+'</span><input type="text" class="form-control mx-1" name="maxval" title="'+M.util.get_string("label_max","availability_grade")+'"/></label>%</span>',t=r.Node.create('<div class="d-inline-block d-flex flex-wrap align-items-center">'+e+"</div>"),a.id!==undefined&&t.one("select[name=id] > option[value="+a.id+"]")&&t.one("select[name=id]").set("value",""+a.id),a.min!==undefined&&(t.one("input[name=min]").set("checked",!0),t.one("input[name=minval]").set("value",a.min)),a.max!==undefined&&(t.one("input[name=max]").set("checked",!0),t.one("input[name=maxval]").set("value",a.max)),n=function(a,e){var i=a.ancestor("label").next("label").one("input"),a=a.get("checked");return i.set("disabled",!a),e&&a&&i.focus(),a},t.all("input[type=checkbox]").each(n),M.availability_grade.form.addedEvents||(M.availability_grade.form.addedEvents=!0,(a=r.one(".availability-field")).delegate("change",function(){M.core_availability.form.update()},".availability_grade select[name=id]"),a.delegate("click",function(){n(this,!0),M.core_availability.form.update()},".availability_grade input[type=checkbox]"),a.delegate("valuechange",function(){M.core_availability.form.update()},".availability_grade input[type=text]")),t},M.availability_grade.form.fillValue=function(a,e){a.id=parseInt(e.one("select[name=id]").get("value"),10),e.one("input[name=min]").get("checked")&&(a.min=this.getValue("minval",e)),e.one("input[name=max]").get("checked")&&(a.max=this.getValue("maxval",e))},M.availability_grade.form.getValue=function(a,e){a=e.one("input[name="+a+"]").get("value");return!/^[0-9]+([.,][0-9]+)?$/.test(a)||(e=parseFloat(a.replace(",",".")))<0||100<e?a:e},M.availability_grade.form.fillErrors=function(a,e){var i={};this.fillValue(i,e),0===i.id&&a.push("availability_grade:error_selectgradeid"),i.min!==undefined&&"string"==typeof i.min||i.max!==undefined&&"string"==typeof i.max?a.push("availability_grade:error_invalidnumber"):i.min!==undefined&&i.max!==undefined&&i.max<=i.min&&a.push("availability_grade:error_backwardrange")}},"@VERSION@",{requires:["base","node","event","moodle-core_availability-form"]});
|
||||
+2
-2
@@ -46,13 +46,13 @@ M.availability_grade.form.getNode = function(json) {
|
||||
html += '<option value="' + grade.id + '">' + grade.name + '</option>';
|
||||
}
|
||||
html += '</select></span></label> <br><span class="availability-group mb-3">' +
|
||||
'<label><input type="checkbox" class="form-check-input mx-1" name="min"/>' +
|
||||
'<label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="min"/>' +
|
||||
M.util.get_string('option_min', 'availability_grade') +
|
||||
'</label> <label><span class="accesshide">' + M.util.get_string('label_min', 'availability_grade') +
|
||||
'</span><input type="text" class="form-control mx-1" name="minval" title="' +
|
||||
M.util.get_string('label_min', 'availability_grade') + '"/></label>%</span><br>' +
|
||||
'<span class="availability-group mb-3">' +
|
||||
'<label><input type="checkbox" class="form-check-input mx-1" name="max"/>' +
|
||||
'<label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="max"/>' +
|
||||
M.util.get_string('option_max', 'availability_grade') +
|
||||
'</label> <label><span class="accesshide">' + M.util.get_string('label_max', 'availability_grade') +
|
||||
'</span><input type="text" class="form-control mx-1" name="maxval" title="' +
|
||||
|
||||
+2
-2
@@ -44,13 +44,13 @@ M.availability_grade.form.getNode = function(json) {
|
||||
html += '<option value="' + grade.id + '">' + grade.name + '</option>';
|
||||
}
|
||||
html += '</select></span></label> <br><span class="availability-group mb-3">' +
|
||||
'<label><input type="checkbox" class="form-check-input mx-1" name="min"/>' +
|
||||
'<label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="min"/>' +
|
||||
M.util.get_string('option_min', 'availability_grade') +
|
||||
'</label> <label><span class="accesshide">' + M.util.get_string('label_min', 'availability_grade') +
|
||||
'</span><input type="text" class="form-control mx-1" name="minval" title="' +
|
||||
M.util.get_string('label_min', 'availability_grade') + '"/></label>%</span><br>' +
|
||||
'<span class="availability-group mb-3">' +
|
||||
'<label><input type="checkbox" class="form-check-input mx-1" name="max"/>' +
|
||||
'<label><input type="checkbox" class="form-check-input position-static mt-0 mx-1" name="max"/>' +
|
||||
M.util.get_string('option_max', 'availability_grade') +
|
||||
'</label> <label><span class="accesshide">' + M.util.get_string('label_max', 'availability_grade') +
|
||||
'</span><input type="text" class="form-control mx-1" name="maxval" title="' +
|
||||
|
||||
@@ -71,7 +71,7 @@ class condition extends \core_availability\condition {
|
||||
$allow = true;
|
||||
if (!has_capability('moodle/site:accessallgroups', $context, $userid)) {
|
||||
// Get all groups the user belongs to.
|
||||
$groups = $info->get_groups();
|
||||
$groups = $info->get_groups(0, $userid);
|
||||
if ($this->groupid) {
|
||||
$allow = in_array($this->groupid, $groups);
|
||||
} else {
|
||||
|
||||
@@ -47,9 +47,9 @@ class condition_test extends \advanced_testcase {
|
||||
// Make a test course and user.
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$user = $generator->create_user();
|
||||
$generator->enrol_user($user->id, $course->id);
|
||||
$this->setUser($user);
|
||||
$user = $generator->create_and_enrol($course);
|
||||
$usertwo = $generator->create_and_enrol($course);
|
||||
|
||||
$info = new \core_availability\mock_info($course, $user->id);
|
||||
|
||||
// Make 2 test groups, one in a grouping and one not.
|
||||
@@ -76,6 +76,8 @@ class condition_test extends \advanced_testcase {
|
||||
// Recheck.
|
||||
$this->assertTrue($cond->is_available(false, $info, true, $user->id));
|
||||
$this->assertFalse($cond->is_available(true, $info, true, $user->id));
|
||||
$this->assertFalse($cond->is_available(false, $info, true, $usertwo->id));
|
||||
$this->assertTrue($cond->is_available(true, $info, true, $usertwo->id));
|
||||
$information = $cond->get_description(false, true, $info);
|
||||
$information = \core_availability\info::format_info($information, $course);
|
||||
$this->assertMatchesRegularExpression('~do not belong to.*G1!~', $information);
|
||||
@@ -83,11 +85,14 @@ class condition_test extends \advanced_testcase {
|
||||
// Check group 2 works also.
|
||||
$cond = new condition((object)array('id' => (int)$group2->id));
|
||||
$this->assertTrue($cond->is_available(false, $info, true, $user->id));
|
||||
$this->assertFalse($cond->is_available(false, $info, true, $usertwo->id));
|
||||
|
||||
// What about an 'any group' condition?
|
||||
$cond = new condition((object)array());
|
||||
$this->assertTrue($cond->is_available(false, $info, true, $user->id));
|
||||
$this->assertFalse($cond->is_available(true, $info, true, $user->id));
|
||||
$this->assertFalse($cond->is_available(false, $info, true, $usertwo->id));
|
||||
$this->assertTrue($cond->is_available(true, $info, true, $usertwo->id));
|
||||
$information = $cond->get_description(false, true, $info);
|
||||
$information = \core_availability\info::format_info($information, $course);
|
||||
$this->assertMatchesRegularExpression('~do not belong to any~', $information);
|
||||
|
||||
@@ -2,6 +2,10 @@ This files describes API changes in /availability/*.
|
||||
|
||||
The information here is intended only for developers.
|
||||
|
||||
=== 4.4.1 ===
|
||||
* The base class `info::get_groups` method has a `$userid` parameter to specify for which user you want to retrieve course groups (defaults
|
||||
to current user)
|
||||
|
||||
=== 4.3 ===
|
||||
* The `availability_info templatable` no longer exports 'showmore' information, and any component rendering avaialable_info
|
||||
should handle showmore/showless behaviour by itself.
|
||||
|
||||
@@ -1235,7 +1235,7 @@ class restore_groups_structure_step extends restore_structure_step {
|
||||
*/
|
||||
public function process_groupcustomfield($data) {
|
||||
$newgroup = $this->get_mapping('group', $data['groupid']);
|
||||
$data['groupid'] = $newgroup->newitemid;
|
||||
$data['groupid'] = $newgroup->newitemid ?? $data['groupid'];
|
||||
$handler = \core_group\customfield\group_handler::create();
|
||||
$handler->restore_instance_data_from_backup($this->task, $data);
|
||||
}
|
||||
@@ -1292,7 +1292,7 @@ class restore_groups_structure_step extends restore_structure_step {
|
||||
*/
|
||||
public function process_groupingcustomfield($data) {
|
||||
$newgroup = $this->get_mapping('grouping', $data['groupingid']);
|
||||
$data['groupingid'] = $newgroup->newitemid;
|
||||
$data['groupingid'] = $newgroup->newitemid ?? $data['groupingid'];
|
||||
$handler = \core_group\customfield\grouping_handler::create();
|
||||
$handler->restore_instance_data_from_backup($this->task, $data);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
||||
require_once($CFG->dirroot . '/backup/moodle2/backup_custom_fields.php');
|
||||
require_once($CFG->dirroot . '/backup/moodle2/backup_subplugin.class.php');
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
namespace core_backup;
|
||||
|
||||
use memory_xml_output;
|
||||
use phpunit_util;
|
||||
use xml_contenttransformer;
|
||||
use xml_output;
|
||||
use xml_writer;
|
||||
@@ -314,7 +315,7 @@ class writer_test extends \basic_testcase {
|
||||
$fcontents = file_get_contents($CFG->dirroot . '/backup/util/xml/tests/fixtures/test1.xml');
|
||||
|
||||
// Normalise carriage return characters.
|
||||
$fcontents = str_replace("\r\n", "\n", $fcontents);
|
||||
$fcontents = phpunit_util::normalise_line_endings($fcontents);
|
||||
$this->assertEquals(trim($result), trim($fcontents));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use core_badges_generator;
|
||||
use core_reportbuilder_generator;
|
||||
use core_reportbuilder_testcase;
|
||||
use core_reportbuilder\local\filters\{boolean_select, date, select, tags, text};
|
||||
use core_reportbuilder\manager;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -37,7 +38,7 @@ require_once("{$CFG->libdir}/badgeslib.php");
|
||||
* @copyright 2022 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class badges_test extends core_reportbuilder_testcase {
|
||||
final class badges_test extends core_reportbuilder_testcase {
|
||||
|
||||
/**
|
||||
* Test default datasource
|
||||
@@ -200,12 +201,53 @@ class badges_test extends core_reportbuilder_testcase {
|
||||
$this->assertEquals($course->fullname, $coursename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a report containing "expiry" as both a condition and a filter
|
||||
*
|
||||
* This is really testing that it's possible to do so, for a filter instance that returns SQL parameters
|
||||
*/
|
||||
public function test_report_expiry_condition_and_filter(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
/** @var core_badges_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_badges');
|
||||
|
||||
$badgeone = $generator->create_badge(['name' => 'Badge 1', 'expiredate' => 10]);
|
||||
$badgetwo = $generator->create_badge(['name' => 'Badge 2', 'expiredate' => 20]);
|
||||
$badgethree = $generator->create_badge(['name' => 'Badge 3', 'expiredate' => 30]);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
$report = $generator->create_report(['name' => 'Badges', 'source' => badges::class, 'default' => 0]);
|
||||
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:name']);
|
||||
$generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:expiry']);
|
||||
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:expiry']);
|
||||
|
||||
// Load report instance, set condition.
|
||||
$instance = manager::get_report_from_persistent($report);
|
||||
$instance->set_condition_values([
|
||||
'badge:expiry_operator' => date::DATE_RANGE,
|
||||
'badge:expiry_from' => 15,
|
||||
]);
|
||||
|
||||
// Set filter.
|
||||
$content = $this->get_custom_report_content($report->get('id'), 0, [
|
||||
'badge:expiry_operator' => date::DATE_RANGE,
|
||||
'badge:expiry_to' => 25,
|
||||
]);
|
||||
|
||||
$this->assertEquals([
|
||||
[$badgetwo->name],
|
||||
], array_map('array_values', $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_datasource_filters}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function datasource_filters_provider(): array {
|
||||
public static function datasource_filters_provider(): array {
|
||||
return [
|
||||
// Badge.
|
||||
'Filter badge name' => ['badge:name', [
|
||||
|
||||
@@ -52,5 +52,8 @@ function xmldb_block_badges_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -52,5 +52,8 @@ function xmldb_block_calendar_month_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -52,5 +52,8 @@ function xmldb_block_calendar_upcoming_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -53,5 +53,8 @@ function xmldb_block_completionstatus_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -53,5 +53,8 @@ function xmldb_block_course_summary_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -52,5 +52,8 @@ function xmldb_block_feedback_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_block_html_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_block_myoverview_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,5 +60,8 @@ function xmldb_block_navigation_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -52,5 +52,8 @@ function xmldb_block_recent_activity_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class helper {
|
||||
FROM {block_recentlyaccesseditems} rai
|
||||
JOIN {course} c ON c.id = rai.courseid
|
||||
WHERE userid = :userid
|
||||
ORDER BY rai.timeaccess DESC";
|
||||
ORDER BY rai.timeaccess DESC, rai.id DESC";
|
||||
$records = $DB->get_records_sql($sql, $paramsql);
|
||||
$order = 0;
|
||||
|
||||
|
||||
@@ -52,5 +52,8 @@ function xmldb_block_recentlyaccesseditems_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_block_rss_client_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ class block_section_links extends block_base {
|
||||
return [
|
||||
'course-view-weeks' => true,
|
||||
'course-view-topics' => true,
|
||||
'section-view-weeks' => true,
|
||||
'section-view-topics' => true,
|
||||
'course-view-section-weeks' => true,
|
||||
'course-view-section-topics' => true,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -54,5 +54,8 @@ function xmldb_block_section_links_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -53,5 +53,8 @@ function xmldb_block_selfcompletion_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,5 +60,8 @@ function xmldb_block_settings_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ function xmldb_block_tag_youtube_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -52,5 +52,8 @@ function xmldb_block_timeline_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Vendored
+10
-3
@@ -234,6 +234,15 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
|
||||
// We only need the first record for the single redis.
|
||||
if (!$clustermode) {
|
||||
// Handle the case when the server is not a Unix domain socket.
|
||||
if ($port !== 0) {
|
||||
// We only need the first record for the single redis.
|
||||
$serverchunks = explode(':', $trimmedservers[0]);
|
||||
// Get the last chunk as the port.
|
||||
$port = array_pop($serverchunks);
|
||||
// Combine the rest of the chunks back into a string as the server.
|
||||
$server = implode(':', $serverchunks);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -259,8 +268,6 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
if ($clustermode) {
|
||||
$redis = new RedisCluster(null, $trimmedservers, 1, 1, true, $password, !empty($opts) ? $opts : null);
|
||||
} else {
|
||||
// We only need the first record for the single redis.
|
||||
list($server, $port) = explode(':', $trimmedservers[0]);
|
||||
$redis = new Redis();
|
||||
$redis->connect($server, $port, 1, null, 100, 1, $opts);
|
||||
if (!empty($password)) {
|
||||
@@ -469,7 +476,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
$ttlparams[] = $key;
|
||||
}
|
||||
}
|
||||
if ($usettl) {
|
||||
if ($usettl && count($ttlparams) > 0) {
|
||||
// Store all the key values with current time.
|
||||
$this->redis->zAdd($this->hash . self::TTL_SUFFIX, [], ...$ttlparams);
|
||||
// The return value to the zAdd function never indicates whether the operation succeeded
|
||||
|
||||
+10
-1
@@ -29,7 +29,7 @@ namespace cachestore_redis;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \cachestore_redis
|
||||
*/
|
||||
class ttl_test extends \advanced_testcase {
|
||||
final class ttl_test extends \advanced_testcase {
|
||||
/** @var \cachestore_redis|null Cache store */
|
||||
protected $store = null;
|
||||
|
||||
@@ -66,6 +66,15 @@ class ttl_test extends \advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test calling set_many with an empty array
|
||||
*
|
||||
* Trivial test to ensure we don't trigger an ArgumentCountError when calling zAdd with invalid parameters
|
||||
*/
|
||||
public function test_set_many_empty(): void {
|
||||
$this->assertEquals(0, $this->store->set_many([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests expiring data.
|
||||
*/
|
||||
|
||||
@@ -3588,6 +3588,18 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
|
||||
}
|
||||
}
|
||||
|
||||
// Check if $data has events.
|
||||
if (isset($data->events)) {
|
||||
// Let's check and sanitize all "name" in $data->events before it's sent to front end.
|
||||
foreach ($data->events as $d) {
|
||||
$name = $d->name ?? null;
|
||||
// Encode special characters if our decoded name does not match the original name.
|
||||
if ($name && (html_entity_decode($name) !== $name)) {
|
||||
$d->name = htmlspecialchars(html_entity_decode($name), ENT_QUOTES, 'utf-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$data, $template];
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,25 @@ Feature: Perform basic calendar functionality
|
||||
Then I should see "Mediocre event"
|
||||
And ".location-content" "css_element" should not exist
|
||||
|
||||
@javascript @editor_tiny
|
||||
Scenario: Edit a newly created event using TinyMCE editor
|
||||
Given I log in as "teacher1"
|
||||
And I follow "Dashboard"
|
||||
And I click on "New event" "button"
|
||||
And I set the field "Event title" to "Newly created event"
|
||||
When I press "Save"
|
||||
Then I should see "Newly created event"
|
||||
And I click on "Newly created event" "link"
|
||||
And I click on "Edit" "button" in the "Newly created event" "dialogue"
|
||||
And I click on "Show more..." "link"
|
||||
And I click on the "Link" button for the "Description" TinyMCE editor
|
||||
And I set the field "Text to display" to "Read more..."
|
||||
And I set the field "Enter a URL" to "https://moodle.org/"
|
||||
And I click on "Create link" "button" in the "Create link" "dialogue"
|
||||
And I press "Save"
|
||||
And I click on "Newly created event" "link"
|
||||
And I should see "Read more..."
|
||||
|
||||
@javascript
|
||||
Scenario: Module events editing
|
||||
Given I log in as "teacher1"
|
||||
|
||||
@@ -59,17 +59,21 @@ abstract class matrix_client {
|
||||
*
|
||||
* @param string $serverurl The URL of the API server
|
||||
* @param string $accesstoken The admin access token to use
|
||||
* @return matrix_client
|
||||
* @return matrix_client|null
|
||||
*/
|
||||
public static function instance(
|
||||
string $serverurl,
|
||||
string $accesstoken,
|
||||
): matrix_client {
|
||||
): ?matrix_client {
|
||||
// Fetch the list of supported API versions.
|
||||
$clientversions = self::get_supported_versions();
|
||||
|
||||
// Fetch the supported versions from the server.
|
||||
$serversupports = self::query_server_supports($serverurl);
|
||||
if ($serversupports === null) {
|
||||
// Unable to fetch the server versions.
|
||||
return null;
|
||||
}
|
||||
$serverversions = $serversupports->versions;
|
||||
|
||||
// Calculate the intersections and sort to determine the highest combined version.
|
||||
@@ -189,9 +193,9 @@ abstract class matrix_client {
|
||||
* - https://spec.matrix.org/latest/client-server-api/#get_matrixclientversions
|
||||
*
|
||||
* @param string $serverurl The server base
|
||||
* @return \stdClass The list of supported versions and a list of enabled unstable features
|
||||
* @return null|\stdClass The list of supported versions and a list of enabled unstable features
|
||||
*/
|
||||
protected static function query_server_supports(string $serverurl): \stdClass {
|
||||
protected static function query_server_supports(string $serverurl): ?\stdClass {
|
||||
// Attempt to return from the cache first.
|
||||
$cache = \cache::make('communication_matrix', 'serverversions');
|
||||
$serverkey = sha1($serverurl);
|
||||
@@ -200,17 +204,21 @@ abstract class matrix_client {
|
||||
}
|
||||
|
||||
// Not in the cache - fetch and store in the cache.
|
||||
$client = static::get_http_client();
|
||||
$response = $client->get("{$serverurl}/_matrix/client/versions");
|
||||
$supportsdata = json_decode(
|
||||
json: $response->getBody(),
|
||||
associative: false,
|
||||
flags: JSON_THROW_ON_ERROR,
|
||||
);
|
||||
try {
|
||||
$client = static::get_http_client();
|
||||
$response = $client->get("{$serverurl}/_matrix/client/versions");
|
||||
|
||||
$cache->set($serverkey, $supportsdata);
|
||||
$supportsdata = json_decode(
|
||||
json: $response->getBody(),
|
||||
associative: false,
|
||||
flags: JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
return $supportsdata;
|
||||
$cache->set($serverkey, $supportsdata);
|
||||
return $supportsdata;
|
||||
} catch (\GuzzleHttp\Exception\TransferException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace communication_matrix;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class matrix_user_manager {
|
||||
|
||||
/**
|
||||
* Prefix for Matrix usernames when they are detected as numeric.
|
||||
*/
|
||||
const MATRIX_USER_PREFIX = 'user';
|
||||
|
||||
/**
|
||||
* Gets matrix user id from moodle.
|
||||
*
|
||||
@@ -56,6 +62,11 @@ class matrix_user_manager {
|
||||
$username = preg_replace('/[@#$%^&*()+{}|<>?!,]/i', '.', $username);
|
||||
$username = ltrim(rtrim($username, '.'), '.');
|
||||
|
||||
// Matrix/Synapse servers will not allow numeric usernames.
|
||||
if (is_numeric($username)) {
|
||||
$username = self::MATRIX_USER_PREFIX . $username;
|
||||
}
|
||||
|
||||
$homeserver = self::get_formatted_matrix_home_server();
|
||||
|
||||
return "@{$username}:{$homeserver}";
|
||||
|
||||
@@ -54,5 +54,8 @@ function xmldb_communication_matrix_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.3.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v4.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,11 @@ class matrix_user_manager_test extends \advanced_testcase {
|
||||
'colin.creavey',
|
||||
'@colin.creavey:matrix.example.org',
|
||||
],
|
||||
'numeric username' => [
|
||||
'https://matrix.example.org',
|
||||
'123456',
|
||||
'@' . matrix_user_manager::MATRIX_USER_PREFIX . '123456:matrix.example.org',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -114,16 +114,25 @@ abstract class activity_custom_completion {
|
||||
/**
|
||||
* Fetches the overall completion status of this activity instance for a user based on its available custom completion rules.
|
||||
*
|
||||
* @return int The completion state (e.g. COMPLETION_COMPLETE, COMPLETION_INCOMPLETE).
|
||||
* @return int The completion state (e.g. COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE_FAIL).
|
||||
*/
|
||||
public function get_overall_completion_state(): int {
|
||||
$iscompletefail = false;
|
||||
foreach ($this->get_available_custom_rules() as $rule) {
|
||||
$state = $this->get_state($rule);
|
||||
// Return early if one of the custom completion rules is not yet complete.
|
||||
if ($state == COMPLETION_INCOMPLETE) {
|
||||
return $state;
|
||||
}
|
||||
if (!$iscompletefail) {
|
||||
$iscompletefail = ($state === COMPLETION_COMPLETE_FAIL || $state === COMPLETION_COMPLETE_FAIL_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
if ($iscompletefail) {
|
||||
return COMPLETION_COMPLETE_FAIL;
|
||||
}
|
||||
|
||||
// If this was reached, then all custom rules have been marked complete.
|
||||
return COMPLETION_COMPLETE;
|
||||
}
|
||||
|
||||
@@ -207,7 +207,10 @@ class cm_completion_details {
|
||||
$completionstates = [COMPLETION_COMPLETE];
|
||||
} else if ($this->is_automatic()) {
|
||||
// Successfull completion states depend on the completion settings.
|
||||
if (isset($this->completiondata->passgrade)) {
|
||||
if (property_exists($this->completiondata, 'customcompletion') && !empty($this->completiondata->customcompletion)) {
|
||||
// If the module has any failed custom completion rule the state could be COMPLETION_COMPLETE_FAIL.
|
||||
$completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS];
|
||||
} else if (isset($this->completiondata->passgrade)) {
|
||||
// Passing grade is required. Don't mark it as complete when state is COMPLETION_COMPLETE_FAIL.
|
||||
$completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS];
|
||||
} else {
|
||||
|
||||
@@ -56,25 +56,37 @@ class activity_custom_completion_test extends advanced_testcase {
|
||||
['completionsubmit', 'completioncreate'],
|
||||
[COMPLETION_INCOMPLETE, COMPLETION_COMPLETE],
|
||||
1,
|
||||
COMPLETION_INCOMPLETE
|
||||
COMPLETION_INCOMPLETE,
|
||||
],
|
||||
'First complete, second incomplete' => [
|
||||
['completionsubmit', 'completioncreate'],
|
||||
[COMPLETION_COMPLETE, COMPLETION_INCOMPLETE],
|
||||
2,
|
||||
COMPLETION_INCOMPLETE
|
||||
COMPLETION_INCOMPLETE,
|
||||
],
|
||||
'First complete, second failed' => [
|
||||
['completionsubmit', 'completioncreate'],
|
||||
[COMPLETION_COMPLETE, COMPLETION_COMPLETE_FAIL],
|
||||
2,
|
||||
COMPLETION_COMPLETE_FAIL,
|
||||
],
|
||||
'First complete, second incomplete, third failed' => [
|
||||
['completionsubmit', 'completioncreate'],
|
||||
[COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE_FAIL],
|
||||
2,
|
||||
COMPLETION_INCOMPLETE,
|
||||
],
|
||||
'All complete' => [
|
||||
['completionsubmit', 'completioncreate'],
|
||||
[COMPLETION_COMPLETE, COMPLETION_COMPLETE],
|
||||
2,
|
||||
COMPLETION_COMPLETE
|
||||
COMPLETION_COMPLETE,
|
||||
],
|
||||
'No rules' => [
|
||||
[],
|
||||
[],
|
||||
0,
|
||||
COMPLETION_COMPLETE
|
||||
COMPLETION_COMPLETE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
This file describes API changes in /completion/* - completion,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 4.4.1 ===
|
||||
* get_overall_completion_state() function could also return COMPLETION_COMPLETE_FAIL and not only COMPLETION_COMPLETE
|
||||
and COMPLETION_INCOMPLETE
|
||||
|
||||
=== 4.4 ===
|
||||
* A new method, cm_completion_details::is_overall_complete() has been added to calculate whether a module should be considered or
|
||||
not completed, based on their completion options and the current value for the overall complete state.
|
||||
|
||||
Vendored
+1
-1
@@ -8,6 +8,6 @@ define("core_course/copy_modal",["exports","core/str","core/modal","core/ajax","
|
||||
* @author Matt Porritt <mattp@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 3.9
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),ajax=_interopRequireWildcard(ajax),Fragment=_interopRequireWildcard(Fragment),_notification=_interopRequireDefault(_notification),Config=_interopRequireWildcard(Config);class CopyModal{static init(context){return new CopyModal(context)}constructor(context){this.contextid=context,this.registerEventListeners()}registerEventListeners(){document.addEventListener("click",(e=>{const copyAction=e.target.closest(".action-copy");if(!copyAction)return;e.preventDefault();const url=new URL(copyAction.href),params=new URLSearchParams(url.search);this.fetchCourseData(params.get("id")).then((_ref=>{let[course]=_ref;return this.createModal(course)})).catch((error=>_notification.default.exception(error)))}))}fetchCourseData(courseid){return ajax.call([{methodname:"core_course_get_courses",args:{options:{ids:[courseid]}}}])[0]}submitBackupRequest(jsonformdata){return ajax.call([{methodname:"core_backup_submit_copy_form",args:{jsonformdata:jsonformdata}}])[0]}createModal(course){let formdata=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const params={jsonformdata:JSON.stringify(formdata),courseid:course.id};return _modal.default.create({title:(0,_str.get_string)("copycoursetitle","backup",course.shortname),body:Fragment.loadFragment("course","new_base_form",this.contextid,params),large:!0,show:!0,removeOnClose:!0}).then((modal=>(modal.getRoot().on("click","#id_submitreturn",(e=>{this.processModalForm(course,modal,e)})),modal.getRoot().on("click","#id_cancel",(e=>{e.preventDefault(),modal.hide()})),modal.getRoot().on("click","#id_submitdisplay",(e=>{e.formredirect=!0,this.processModalForm(course,modal,e)})),modal)))}processModalForm(course,modal,e){e.preventDefault();const copyform=modal.getRoot().find("form").serialize(),formjson=JSON.stringify(copyform),invalid=modal.getRoot()[0].querySelectorAll('[aria-invalid="true"], .error');invalid.length?invalid[0].focus():(modal.hide(),this.submitBackupRequest(formjson).then((()=>{if(1==e.formredirect){const redirect="".concat(Config.wwwroot,"/backup/copyprogress.php?id=").concat(course.id);window.location.assign(redirect)}})).catch((()=>{this.createModal(course,copyform)})))}}return _exports.default=CopyModal,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),ajax=_interopRequireWildcard(ajax),Fragment=_interopRequireWildcard(Fragment),_notification=_interopRequireDefault(_notification),Config=_interopRequireWildcard(Config);class CopyModal{static init(context){return new CopyModal(context)}constructor(context){this.contextid=context,this.registerEventListeners()}registerEventListeners(){document.addEventListener("click",(e=>{const copyAction=e.target.closest(".action-copy");if(!copyAction)return;e.preventDefault();const url=new URL(copyAction.href),params=new URLSearchParams(url.search);this.fetchCourseData(params.get("id")).then((_ref=>{let[course]=_ref;return this.createModal(course)})).catch((error=>_notification.default.exception(error)))}))}fetchCourseData(courseid){return ajax.call([{methodname:"core_course_get_courses",args:{options:{ids:[courseid]}}}])[0]}submitBackupRequest(jsonformdata){return ajax.call([{methodname:"core_backup_submit_copy_form",args:{jsonformdata:jsonformdata}}])[0]}createModal(course){let formdata=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const params={jsonformdata:JSON.stringify(formdata),courseid:course.id};return _modal.default.create({title:(0,_str.get_string)("copycoursetitle","backup",course.shortname),body:Fragment.loadFragment("course","new_base_form",this.contextid,params),large:!0,show:!0,removeOnClose:!0}).then((modal=>(modal.getRoot().on("click","#id_submitreturn",(e=>{this.processModalForm(course,modal,e)})),modal.getRoot().on("click","#id_cancel",(e=>{e.preventDefault(),modal.destroy()})),modal.getRoot().on("click","#id_submitdisplay",(e=>{e.formredirect=!0,this.processModalForm(course,modal,e)})),modal)))}processModalForm(course,modal,e){e.preventDefault();const copyform=modal.getRoot().find("form").serialize(),formjson=JSON.stringify(copyform),invalid=modal.getRoot()[0].querySelectorAll('[aria-invalid="true"], .error');invalid.length?invalid[0].focus():(modal.destroy(),this.submitBackupRequest(formjson).then((()=>{if(1==e.formredirect){const redirect="".concat(Config.wwwroot,"/backup/copyprogress.php?id=").concat(course.id);window.location.assign(redirect)}})).catch((()=>{this.createModal(course,copyform)})))}}return _exports.default=CopyModal,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=copy_modal.min.js.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user