Compare commits
347 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b864c78a31 | |||
| 5b81c07712 | |||
| 7299eb910d | |||
| 43bfcc4c77 | |||
| 044e23f691 | |||
| 8a5563445d | |||
| 8de0b97f7d | |||
| 8ee9748839 | |||
| 2d82b93072 | |||
| a387d795f7 | |||
| 80d8f7f1be | |||
| bb82843548 | |||
| 3463f7fa5d | |||
| 005814360f | |||
| 8e9e4ca362 | |||
| 799f7fb18f | |||
| 978a6bb8ec | |||
| 7405121cd2 | |||
| aa40f64f98 | |||
| b7baec898a | |||
| aeabe6324b | |||
| c445846da4 | |||
| 8376eec99d | |||
| a20c78d894 | |||
| fe6d102184 | |||
| e719cbb687 | |||
| b5cce2514d | |||
| 81ff190dfb | |||
| 7268fe0424 | |||
| c2b7e33b5d | |||
| 0dcd79d2e8 | |||
| 918b7c9c87 | |||
| 06634f0add | |||
| d7ba5b5d6f | |||
| a61a86c61d | |||
| 673faab506 | |||
| 3a8149ccc2 | |||
| 4d64b0b5fe | |||
| 58d350d53d | |||
| 7ad4bc126b | |||
| dac806d47d | |||
| 5ea303bacb | |||
| d597211fa6 | |||
| fcea42b77e | |||
| 9b7854dbad | |||
| 500bde134f | |||
| dc230cf98e | |||
| dc516a038e | |||
| bd1d89a94a | |||
| 7d4104a8eb | |||
| da216a5545 | |||
| 08d0ea0aad | |||
| 50c1e2507d | |||
| ec71e226d4 | |||
| 9b11c29932 | |||
| 2af81f271d | |||
| 1fa5b0c03c | |||
| 160019f2e3 | |||
| 8412f2adb0 | |||
| 1d3bd4e102 | |||
| 54e2850687 | |||
| 1c1f42d5d3 | |||
| d070caea1a | |||
| dd6549e0be | |||
| 0b66c7a43a | |||
| 093b2d9eb5 | |||
| 7172bf1f73 | |||
| 540ce30050 | |||
| 4fb6de6324 | |||
| d9d1cd7c55 | |||
| 6774281d65 | |||
| e22131aa8f | |||
| 81ecef2c1b | |||
| d0e3eb464a | |||
| 5e439eefde | |||
| d066f500bf | |||
| bb898838d6 | |||
| 81cc2e6b41 | |||
| fcfc240a77 | |||
| f3fee9f4d2 | |||
| 302610302a | |||
| fa417ba8a3 | |||
| 9d14b80f5b | |||
| bdbd1b3af0 | |||
| 2b6ca7e207 | |||
| 023ac9229f | |||
| 5f02141a7a | |||
| 54cffd7c2e | |||
| 5e80881587 | |||
| c6c77554ef | |||
| dcfd3b29a9 | |||
| 293bc2d615 | |||
| eeccbf0ebd | |||
| 8cded7cd9f | |||
| 652d8971ec | |||
| 8fbede49ae | |||
| 37e16d4da2 | |||
| 5b325dc2e9 | |||
| 06d3ff9ea2 | |||
| 6aa9d215c8 | |||
| 0faa70e56a | |||
| 0ab77c7287 | |||
| 3d0bbf1a0f | |||
| 9287e1e0ea | |||
| e4bf4cf37e | |||
| 4dfb5cf6e7 | |||
| 4fc412b538 | |||
| 9e6b6b4925 | |||
| 0e52ea009d | |||
| 7a5389f1cc | |||
| e7a5867a88 | |||
| 741c9e003d | |||
| f464b00ab8 | |||
| 2f4fa118db | |||
| 2144007387 | |||
| 6176c8fc88 | |||
| e62f86b983 | |||
| 089260dae2 | |||
| 661e595500 | |||
| 1aba16cbea | |||
| c9a5b23d7a | |||
| 636ad3d3a0 | |||
| 3f39fcf27e | |||
| 1097414910 | |||
| 364c3b1fa9 | |||
| 27ebb8fa2d | |||
| 08c8e9bba2 | |||
| b4ab42a911 | |||
| a85ff11cba | |||
| 9d2ddbf7fe | |||
| 34eee61037 | |||
| ff72ae15f3 | |||
| 8a4a9fc096 | |||
| 4ab2e98a64 | |||
| e5ed497363 | |||
| 26ed26289d | |||
| fdcc78fee0 | |||
| 43e0217956 | |||
| 7ae8b4dbed | |||
| 097b706970 | |||
| 2d3acb52df | |||
| 118240ef22 | |||
| d7b3d443aa | |||
| 75247caff8 | |||
| bb23523870 | |||
| e4b7d8b323 | |||
| 93ac861ec5 | |||
| ce6131e6f7 | |||
| 3e0a29dee5 | |||
| c1fb24274e | |||
| ec6d835341 | |||
| 73d873e752 | |||
| 623ca576bd | |||
| df2fe09963 | |||
| 4b31bb97f5 | |||
| b6355c6490 | |||
| 40c59b5865 | |||
| 814fc9cbc1 | |||
| 77ef43cb2e | |||
| 0ab8dfd19f | |||
| 38bad90a7e | |||
| 85ca739855 | |||
| 57f473425a | |||
| 982427b990 | |||
| 15a855e371 | |||
| 72ceec4d94 | |||
| fd89171c91 | |||
| 9e06598722 | |||
| 25ca760ccd | |||
| 8cca821b62 | |||
| 7c28bd0d36 | |||
| 743fe53e6b | |||
| af51642584 | |||
| 0dc67b9d10 | |||
| 8966d10fd6 | |||
| 8ee6c1b4ed | |||
| f4dba7ab54 | |||
| 42d71d6502 | |||
| 34c8e455ea | |||
| e81a91d18f | |||
| c877d579e7 | |||
| ef9310d1e7 | |||
| 64439605c6 | |||
| 7ae00a1542 | |||
| 8576612fbb | |||
| 4fc0a470fb | |||
| f05f5b909f | |||
| 90829a7550 | |||
| afafcca723 | |||
| 02663758fb | |||
| 1bec71f1b6 | |||
| 507d27ba9b | |||
| 65397a0a86 | |||
| a916576147 | |||
| 258c8ca75c | |||
| 3afa59e702 | |||
| b729d36baf | |||
| f5bb4eebce | |||
| 91e96925c9 | |||
| b903d21ea2 | |||
| 1ad4c0e91c | |||
| 6aafd5fa61 | |||
| e2d166e526 | |||
| ae949341cd | |||
| ef5112211b | |||
| ea6c1d65af | |||
| bdba78514f | |||
| 1d35066ba5 | |||
| cb26fec31f | |||
| 1e6be54edd | |||
| 1fc5e599c5 | |||
| 53c1445753 | |||
| 53e5b88001 | |||
| 17e976a2a7 | |||
| 230c3cedc6 | |||
| 09d76ce833 | |||
| 3b504d259b | |||
| a24b426062 | |||
| e8c58c9a3b | |||
| f51798f962 | |||
| 46e658d506 | |||
| c72574f800 | |||
| 70abc44e05 | |||
| 966b0dd3df | |||
| ad770fe215 | |||
| 832d237c94 | |||
| cb8b72034e | |||
| 31c047d2d3 | |||
| acf152ca8b | |||
| 4dc23af4ad | |||
| d24c88543d | |||
| 382f91cf8c | |||
| 109b772c06 | |||
| 937c47e90e | |||
| 1735591d32 | |||
| 6846a839f5 | |||
| 11123d3aba | |||
| eb61b04180 | |||
| 9fa5637b56 | |||
| ab112ec2b7 | |||
| 801ca38d89 | |||
| fa0e0b9a39 | |||
| df99098fb6 | |||
| a63ddc2ec3 | |||
| 740d11fb43 | |||
| e4bdc416d8 | |||
| 7030ec9af2 | |||
| 34f8f8fe90 | |||
| aeebd7a189 | |||
| ffbb559293 | |||
| 6687efe4d5 | |||
| efdaed1f70 | |||
| 56143593c4 | |||
| 1a5322ab4b | |||
| 449104d62a | |||
| 437f447816 | |||
| ef8fdb4762 | |||
| 476474da69 | |||
| 2e534d0c99 | |||
| c09e748a9a | |||
| 8a0f9db07f | |||
| 0487f8ed9a | |||
| bb7e105ca9 | |||
| cca0bc9ec5 | |||
| 65886c04da | |||
| 472ea45f92 | |||
| b62844e26c | |||
| e72dd8c3d9 | |||
| 4419deedab | |||
| 3bd0fd816f | |||
| c5569f2965 | |||
| 5d16d55949 | |||
| 2da8a31849 | |||
| f9460201fd | |||
| 5851941cef | |||
| 28a2586852 | |||
| 6579bdbdb7 | |||
| 12e4a722bd | |||
| 7d2d64a559 | |||
| cef970f6d5 | |||
| 7fb8631e3e | |||
| 40d67d22b2 | |||
| 2700e9b29f | |||
| 0f6bed1637 | |||
| 9c9b10b709 | |||
| 02a066b847 | |||
| 1d8d3ba456 | |||
| 18f6f90021 | |||
| d2215442ac | |||
| 591305c989 | |||
| 0cc918fbce | |||
| 1085ea44b8 | |||
| 424992eeff | |||
| 359d436178 | |||
| 222af18607 | |||
| 37f2404bd0 | |||
| bfedfcbc4a | |||
| 3faffbc882 | |||
| eb0b6e7619 | |||
| b8cf30d2e9 | |||
| 441afd68b0 | |||
| 09ed58c5a1 | |||
| f9022d9fa9 | |||
| ce4bdc144e | |||
| 2d4fb04857 | |||
| f2b007ee49 | |||
| c70361bd9b | |||
| eb9de04a11 | |||
| 0c15fe263e | |||
| bd8e040536 | |||
| d652929260 | |||
| fc09484ec1 | |||
| 028dc3ff0a | |||
| b1926ad5f2 | |||
| 882a63d966 | |||
| 707ff5a4f2 | |||
| a76a02901f | |||
| bc220f8d72 | |||
| 6b412c403b | |||
| cb20f32f46 | |||
| 7d96732e31 | |||
| 47d6bbcb9e | |||
| 7731cb8127 | |||
| eb80cbd249 | |||
| 43f1e99dac | |||
| 4fca1452d4 | |||
| e4af35b9b8 | |||
| c2f415e57c | |||
| 872f3a03dc | |||
| cac3162bf3 | |||
| eab0470f4f | |||
| 229da267a6 | |||
| a616371921 | |||
| a1df353c67 | |||
| bc794a82a2 | |||
| 497c7ee517 | |||
| 1f2f41a2d9 | |||
| 76d6feb23e | |||
| 432628f3a3 | |||
| ebc4a00bca | |||
| 3a60aec87d | |||
| 7ee9daa15e | |||
| f98da613d6 | |||
| 0c2b15935c | |||
| 8763bba518 | |||
| 3bb3afdcab | |||
| 098cf7b38d |
@@ -6,6 +6,10 @@
|
||||
],
|
||||
'env': {
|
||||
'browser': true,
|
||||
// Enable ES6+ features by default.
|
||||
// See http://eslint.org/docs/user-guide/configuring#specifying-environments
|
||||
// Note: The YUI override must exactly match this when disabling the ES6+ version because those features are not supported by Shifter.
|
||||
'es2020': true,
|
||||
'amd': true
|
||||
},
|
||||
'globals': {
|
||||
@@ -83,6 +87,7 @@
|
||||
'no-octal-escape': 'error',
|
||||
'no-proto': 'error',
|
||||
'no-redeclare': 'warn',
|
||||
'no-restricted-globals': ['error', { 'name': 'Notification' }],
|
||||
'no-return-assign': 'error',
|
||||
'no-script-url': 'error',
|
||||
'no-self-assign': 'error',
|
||||
@@ -196,6 +201,10 @@
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/yui/src/**/*.js"],
|
||||
'env': {
|
||||
// Disable ES6+ for YUI files.
|
||||
'es2020': false,
|
||||
},
|
||||
// Disable some rules which we can't safely define for YUI rollups.
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
@@ -221,10 +230,6 @@
|
||||
},
|
||||
{
|
||||
files: ["**/amd/src/*.js", "**/amd/src/**/*.js", "Gruntfile.js", ".grunt/*.js", ".grunt/tasks/*.js", "jsdoc.conf.js"],
|
||||
// We support es6 now. Woot!
|
||||
env: {
|
||||
es6: true
|
||||
},
|
||||
// We're using babel transpiling so use their parser
|
||||
// for linting.
|
||||
parser: '@babel/eslint-parser',
|
||||
@@ -299,7 +304,6 @@
|
||||
],
|
||||
},
|
||||
parserOptions: {
|
||||
'ecmaVersion': 9,
|
||||
'sourceType': 'module',
|
||||
'requireConfigFile': false,
|
||||
}
|
||||
|
||||
+1
-1
@@ -54,5 +54,5 @@ moodle-plugin-ci.phar
|
||||
/admin/tool/componentlibrary/docs
|
||||
/admin/tool/componentlibrary/hugo/site/data/my-index.json
|
||||
.hugo_build.lock
|
||||
.phpcs.xml
|
||||
phpcs.xml
|
||||
jsconfig.json
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = grunt => {
|
||||
{
|
||||
rule: {
|
||||
_attrs: {
|
||||
ref: './.phpcs.xml.dist',
|
||||
ref: './phpcs.xml.dist',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -52,7 +52,7 @@ module.exports = grunt => {
|
||||
});
|
||||
});
|
||||
|
||||
grunt.file.write('.phpcs.xml', toXML(config, {
|
||||
grunt.file.write('phpcs.xml', toXML(config, {
|
||||
header: true,
|
||||
indent: ' ',
|
||||
}) + "\n");
|
||||
|
||||
@@ -3371,6 +3371,7 @@
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.3.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_81" message="unsupportedphpversion81" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -3557,6 +3558,7 @@
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.3.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_81" message="unsupportedphpversion81" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
|
||||
+18
-3
@@ -46,11 +46,23 @@
|
||||
|
||||
$formcontinue = new single_button(new moodle_url('index.php', array('confirm' => md5($mnet->public_key))), get_string('yes'));
|
||||
$formcancel = new single_button(new moodle_url('index.php', array()), get_string('no'));
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->confirm(get_string("deletekeycheck", "mnet"), $formcontinue, $formcancel);
|
||||
echo $OUTPUT->footer();
|
||||
exit;
|
||||
} else {
|
||||
// We're deleting
|
||||
|
||||
// If no/cancel then redirect back to the network setting page.
|
||||
if (!isset($form->confirm)) {
|
||||
redirect(
|
||||
new moodle_url('/admin/mnet/index.php'),
|
||||
get_string('keydeletedcancelled', 'mnet'),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_SUCCESS
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($SESSION->mnet_confirm_delete_key)) {
|
||||
// fail - you're being attacked?
|
||||
@@ -63,8 +75,12 @@
|
||||
|
||||
if($time < time() - 60) {
|
||||
// fail - you're out of time.
|
||||
print_error ('deleteoutoftime', 'mnet', 'index.php');
|
||||
exit;
|
||||
redirect(
|
||||
new moodle_url('/admin/mnet/index.php'),
|
||||
get_string('deleteoutoftime', 'mnet'),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
if ($key != md5(sha1($mnet->keypair['keypair_PEM']))) {
|
||||
@@ -75,7 +91,6 @@
|
||||
|
||||
$mnet->replace_keys();
|
||||
redirect('index.php', get_string('keydeleted','mnet'));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$hosts = $DB->get_records_select('mnet_host', "id <> ? AND deleted = 0", array($CFG->mnet_localhost_id), 'wwwroot ASC');
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
namespace core_adminpresets\local\setting;
|
||||
|
||||
use admin_setting_configcheckbox;
|
||||
|
||||
/**
|
||||
* Checkbox setting.
|
||||
*
|
||||
@@ -26,15 +28,15 @@ namespace core_adminpresets\local\setting;
|
||||
*/
|
||||
class adminpresets_admin_setting_configcheckbox extends adminpresets_setting {
|
||||
|
||||
protected function set_value($value) {
|
||||
$this->value = clean_param($value, PARAM_BOOL);
|
||||
$this->set_visiblevalue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visible name for the setting selected value
|
||||
*/
|
||||
protected function set_visiblevalue() {
|
||||
if ($this->value) {
|
||||
/** @var admin_setting_configcheckbox $settingdata */
|
||||
$settingdata = $this->get_settingdata();
|
||||
|
||||
// We need to compare the "yes" value of the inner checkbox, which isn't necessarily boolean.
|
||||
if ($this->value == $settingdata->yes) {
|
||||
$str = get_string('yes');
|
||||
} else {
|
||||
$str = get_string('no');
|
||||
|
||||
@@ -51,12 +51,12 @@
|
||||
<div class="form-password">
|
||||
<span data-passwordunmask="wrapper" data-passwordunmaskid="{{ id }}">
|
||||
<span data-passwordunmask="editor">
|
||||
<input type="hidden"
|
||||
<input type="password"
|
||||
name="{{ name }}"
|
||||
id="{{ id }}"
|
||||
value="{{ value }}"
|
||||
data-size="{{ size }}"
|
||||
class="form-control d-inline-block"
|
||||
class="form-control d-none"
|
||||
>
|
||||
</span>
|
||||
<a href="#" data-passwordunmask="edit" title="{{ edithint }}">
|
||||
|
||||
@@ -18,7 +18,7 @@ Feature: An administrator can browse user accounts
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
# Name field always present, email field is default for showidentity.
|
||||
Then the following should exist in the "users" table:
|
||||
| First name / Surname | Email address |
|
||||
| First name / Last name | Email address |
|
||||
| User One | one@example.com |
|
||||
| User Two | two@example.com |
|
||||
# Should not see other identity fields or non-default name fields.
|
||||
@@ -34,7 +34,7 @@ Feature: An administrator can browse user accounts
|
||||
| alternativefullnameformat | firstnamephonetic lastname |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "users" table:
|
||||
| First name - phonetic / Surname | Email address |
|
||||
| First name - phonetic / Last name | Email address |
|
||||
| Yewzer One | one@example.com |
|
||||
| Yoozare Two | two@example.com |
|
||||
|
||||
@@ -43,7 +43,7 @@ Feature: An administrator can browse user accounts
|
||||
| showuseridentity | department,profile_field_frog |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "users" table:
|
||||
| First name / Surname | Favourite frog | Department |
|
||||
| First name / Last name | Favourite frog | Department |
|
||||
| User One | Kermit | Attack |
|
||||
| User Two | Tree | Defence |
|
||||
And I should not see "Email address" in the "table" "css_element"
|
||||
|
||||
@@ -15,7 +15,7 @@ Feature: Allowing multiple accounts to have the same email address
|
||||
And I set the following fields to these values:
|
||||
| Username | s2 |
|
||||
| First name | Jane |
|
||||
| Surname | Doe |
|
||||
| Last name | Doe |
|
||||
| Email address | <email> |
|
||||
| New password | test |
|
||||
And I press "Create user"
|
||||
|
||||
@@ -8,7 +8,7 @@ Feature: An administrator can configure the available user list filters
|
||||
When I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then I should see "User full name"
|
||||
And I should not see "Surname" in the "New filter" "fieldset"
|
||||
And I should not see "Last name" in the "New filter" "fieldset"
|
||||
And I should not see "Firstname" in the "New filter" "fieldset"
|
||||
And I should not see "Username" in the "New filter" "fieldset"
|
||||
And I should not see "Email address" in the "New filter" "fieldset"
|
||||
@@ -30,7 +30,7 @@ Feature: An administrator can configure the available user list filters
|
||||
And I should not see "MNet ID provider" in the "New filter" "fieldset"
|
||||
And I navigate to "Users > Accounts > Bulk user actions" in site administration
|
||||
Then I should see "User full name"
|
||||
And I should not see "Surname" in the "New filter" "fieldset"
|
||||
And I should not see "Last name" in the "New filter" "fieldset"
|
||||
And I should not see "Firstname" in the "New filter" "fieldset"
|
||||
And I should not see "Username" in the "New filter" "fieldset"
|
||||
And I should not see "Email address" in the "New filter" "fieldset"
|
||||
@@ -57,7 +57,7 @@ Feature: An administrator can configure the available user list filters
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then I should see "User full name"
|
||||
And I should not see "Surname" in the "New filter" "fieldset"
|
||||
And I should not see "Last name" in the "New filter" "fieldset"
|
||||
And I should not see "Firstname" in the "New filter" "fieldset"
|
||||
And I should see "Username" in the "New filter" "fieldset"
|
||||
And I should see "Email address" in the "New filter" "fieldset"
|
||||
@@ -79,7 +79,7 @@ Feature: An administrator can configure the available user list filters
|
||||
And I should not see "MNet ID provider" in the "New filter" "fieldset"
|
||||
And I navigate to "Users > Accounts > Bulk user actions" in site administration
|
||||
Then I should see "User full name"
|
||||
And I should not see "Surname" in the "New filter" "fieldset"
|
||||
And I should not see "Last name" in the "New filter" "fieldset"
|
||||
And I should not see "Firstname" in the "New filter" "fieldset"
|
||||
And I should see "Username" in the "New filter" "fieldset"
|
||||
And I should see "Email address" in the "New filter" "fieldset"
|
||||
@@ -106,7 +106,7 @@ Feature: An administrator can configure the available user list filters
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then I should see "User full name"
|
||||
And I should not see "Surname" in the "New filter" "fieldset"
|
||||
And I should not see "Last name" in the "New filter" "fieldset"
|
||||
And I should not see "Firstname" in the "New filter" "fieldset"
|
||||
And I should not see "Username" in the "New filter" "fieldset"
|
||||
And I should not see "Email address" in the "New filter" "fieldset"
|
||||
@@ -128,7 +128,7 @@ Feature: An administrator can configure the available user list filters
|
||||
And I should not see "MNet ID provider" in the "New filter" "fieldset"
|
||||
And I navigate to "Users > Accounts > Bulk user actions" in site administration
|
||||
Then I should see "User full name"
|
||||
And I should not see "Surname" in the "New filter" "fieldset"
|
||||
And I should not see "Last name" in the "New filter" "fieldset"
|
||||
And I should not see "Firstname" in the "New filter" "fieldset"
|
||||
And I should not see "Username" in the "New filter" "fieldset"
|
||||
And I should not see "Email address" in the "New filter" "fieldset"
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
define("tool_behat/steps",["exports","core/ajax","core/templates","core/pending"],(function(_exports,_ajax,_templates,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Enhancements for the step definitions page.
|
||||
*
|
||||
* @module tool_behat/steps
|
||||
* @copyright 2022 Catalyst IT EU
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending);_exports.init=()=>{document.addEventListener("change",(async e=>{const entityElement=e.target.closest(".entities"),stepElement=e.target.closest(".stepcontent");if(!entityElement||!stepElement)return;const pendingPromise=new _pending.default("tool_behat/steps:change"),entityData=await(entityType=e.target.value,_ajax.default.call([{methodname:"tool_behat_get_entity_generator",args:{entitytype:entityType}}])[0]);var entityType;const{html:html,js:js}=await(entityData=>{var _entityData$required;return null!==(_entityData$required=entityData.required)&&void 0!==_entityData$required&&_entityData$required.length?_templates.default.renderForPromise("tool_behat/steprequiredfields",{fields:entityData.required}):Promise.resolve({html:"",js:""})})(entityData),stepRequiredFields=stepElement.querySelector(".steprequiredfields");stepRequiredFields?await _templates.default.replaceNode(stepRequiredFields,html,js):await _templates.default.appendNodeContents(stepElement,html,js),pendingPromise.resolve()}))}}));
|
||||
|
||||
//# sourceMappingURL=steps.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"steps.min.js","sources":["../src/steps.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\nimport Ajax from 'core/ajax';\nimport Templates from 'core/templates';\nimport PendingJS from 'core/pending';\n\n/**\n * Enhancements for the step definitions page.\n *\n * @module tool_behat/steps\n * @copyright 2022 Catalyst IT EU\n * @author Mark Johnson <mark.johnson@catalyst-eu.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Call the get_entity_generator web service function\n *\n * Takes the name of an entity generator and returns an object containing a list of the required fields.\n *\n * @param {String} entityType\n * @returns {Promise}\n */\nconst getGeneratorEntities = (entityType) => Ajax.call([{\n methodname: 'tool_behat_get_entity_generator',\n args: {entitytype: entityType}\n}])[0];\n\n/**\n * Render HTML for required fields\n *\n * Takes the entity data returned from getGeneratorEntities and renders the HTML to display the required fields.\n *\n * @param {String} entityData\n * @return {Promise}\n */\nconst getRequiredFieldsContent = (entityData) => {\n if (!entityData.required?.length) {\n return Promise.resolve({\n html: '',\n js: ''\n });\n }\n return Templates.renderForPromise('tool_behat/steprequiredfields', {fields: entityData.required});\n};\n\nexport const init = () => {\n // When an entity is selected in the \"the following exist\" step, fetch and display the required fields.\n document.addEventListener('change', async(e) => {\n const entityElement = e.target.closest('.entities');\n const stepElement = e.target.closest('.stepcontent');\n if (!entityElement || !stepElement) {\n return;\n }\n\n const pendingPromise = new PendingJS('tool_behat/steps:change');\n\n const entityData = await getGeneratorEntities(e.target.value);\n const {html, js} = await getRequiredFieldsContent(entityData);\n\n const stepRequiredFields = stepElement.querySelector('.steprequiredfields');\n if (stepRequiredFields) {\n await Templates.replaceNode(stepRequiredFields, html, js);\n } else {\n await Templates.appendNodeContents(stepElement, html, js);\n }\n pendingPromise.resolve();\n });\n};\n"],"names":["document","addEventListener","async","entityElement","e","target","closest","stepElement","pendingPromise","PendingJS","entityData","entityType","value","Ajax","call","methodname","args","entitytype","html","js","required","_entityData$required","length","Templates","renderForPromise","fields","Promise","resolve","getRequiredFieldsContent","stepRequiredFields","querySelector","replaceNode","appendNodeContents"],"mappings":";;;;;;;;4NA2DoB,KAEhBA,SAASC,iBAAiB,UAAUC,MAAAA,UAC1BC,cAAgBC,EAAEC,OAAOC,QAAQ,aACjCC,YAAcH,EAAEC,OAAOC,QAAQ,oBAChCH,gBAAkBI,yBAIjBC,eAAiB,IAAIC,iBAAU,2BAE/BC,iBAlCgBC,WAkCwBP,EAAEC,OAAOO,MAlClBC,cAAKC,KAAK,CAAC,CACpDC,WAAY,kCACZC,KAAM,CAACC,WAAYN,eACnB,IAH0BA,IAAAA,iBAmChBO,KAACA,KAADC,GAAOA,SAtBaT,CAAAA,0EACzBA,WAAWU,0CAAXC,qBAAqBC,OAMnBC,mBAAUC,iBAAiB,gCAAiC,CAACC,OAAQf,WAAWU,WAL5EM,QAAQC,QAAQ,CACnBT,KAAM,GACNC,GAAI,MAkBiBS,CAAyBlB,YAE5CmB,mBAAqBtB,YAAYuB,cAAc,uBACjDD,yBACMN,mBAAUQ,YAAYF,mBAAoBX,KAAMC,UAEhDI,mBAAUS,mBAAmBzB,YAAaW,KAAMC,IAE1DX,eAAemB"}
|
||||
@@ -0,0 +1,82 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
import Templates from 'core/templates';
|
||||
import PendingJS from 'core/pending';
|
||||
|
||||
/**
|
||||
* Enhancements for the step definitions page.
|
||||
*
|
||||
* @module tool_behat/steps
|
||||
* @copyright 2022 Catalyst IT EU
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Call the get_entity_generator web service function
|
||||
*
|
||||
* Takes the name of an entity generator and returns an object containing a list of the required fields.
|
||||
*
|
||||
* @param {String} entityType
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const getGeneratorEntities = (entityType) => Ajax.call([{
|
||||
methodname: 'tool_behat_get_entity_generator',
|
||||
args: {entitytype: entityType}
|
||||
}])[0];
|
||||
|
||||
/**
|
||||
* Render HTML for required fields
|
||||
*
|
||||
* Takes the entity data returned from getGeneratorEntities and renders the HTML to display the required fields.
|
||||
*
|
||||
* @param {String} entityData
|
||||
* @return {Promise}
|
||||
*/
|
||||
const getRequiredFieldsContent = (entityData) => {
|
||||
if (!entityData.required?.length) {
|
||||
return Promise.resolve({
|
||||
html: '',
|
||||
js: ''
|
||||
});
|
||||
}
|
||||
return Templates.renderForPromise('tool_behat/steprequiredfields', {fields: entityData.required});
|
||||
};
|
||||
|
||||
export const init = () => {
|
||||
// When an entity is selected in the "the following exist" step, fetch and display the required fields.
|
||||
document.addEventListener('change', async(e) => {
|
||||
const entityElement = e.target.closest('.entities');
|
||||
const stepElement = e.target.closest('.stepcontent');
|
||||
if (!entityElement || !stepElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingPromise = new PendingJS('tool_behat/steps:change');
|
||||
|
||||
const entityData = await getGeneratorEntities(e.target.value);
|
||||
const {html, js} = await getRequiredFieldsContent(entityData);
|
||||
|
||||
const stepRequiredFields = stepElement.querySelector('.steprequiredfields');
|
||||
if (stepRequiredFields) {
|
||||
await Templates.replaceNode(stepRequiredFields, html, js);
|
||||
} else {
|
||||
await Templates.appendNodeContents(stepElement, html, js);
|
||||
}
|
||||
pendingPromise.resolve();
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Return data about an entity generator.
|
||||
*
|
||||
* @package tool_behat
|
||||
* @copyright 2022 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_behat\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/externallib.php');
|
||||
|
||||
/**
|
||||
* External function for getting properties of entity generators.
|
||||
*/
|
||||
class get_entity_generator extends \external_api {
|
||||
|
||||
/**
|
||||
* Define parameters for external function.
|
||||
*
|
||||
* The parameter is either in the format 'entity' or 'component_name > entity'. There is no appropriate param type for a
|
||||
* string like this containing angle brackets, so we will do PARAM_RAW. The value will be parsed by
|
||||
* behat_data_generators::parse_entity_type, which validates the format of the parameter and throws an exception if it is not
|
||||
* correct.
|
||||
*
|
||||
* @return \external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): \external_function_parameters {
|
||||
return new \external_function_parameters([
|
||||
'entitytype' => new \external_value(PARAM_RAW, 'Entity type that can be created by a generator.'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of the required fields for a given entity type.
|
||||
*
|
||||
* @param string $entitytype
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(string $entitytype): array {
|
||||
global $CFG;
|
||||
|
||||
// Ensure we can load Behat and Facebook namespaces in behat libraries.
|
||||
require_once("{$CFG->dirroot}/vendor/autoload.php");
|
||||
require_once("{$CFG->libdir}/tests/behat/behat_data_generators.php");
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), ['entitytype' => $entitytype]);
|
||||
$context = \context_system::instance();
|
||||
self::validate_context($context);
|
||||
require_capability('moodle/site:config', $context);
|
||||
|
||||
$generators = new \behat_data_generators();
|
||||
$entity = $generators->get_entity($params['entitytype']);
|
||||
return ['required' => $entity['required']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define return values.
|
||||
*
|
||||
* Return required fields
|
||||
*
|
||||
* @return \external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): \external_single_structure {
|
||||
return new \external_single_structure([
|
||||
'required' => new \external_multiple_structure(
|
||||
new \external_value(PARAM_TEXT, 'Required field'),
|
||||
'Required fields',
|
||||
VALUE_OPTIONAL
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Define web service functions for tool_behat
|
||||
*
|
||||
* @package tool_behat
|
||||
* @copyright 2022 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$functions = [
|
||||
'tool_behat_get_entity_generator' => [
|
||||
'classname' => 'tool_behat\\external\\get_entity_generator',
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Get the generator details for an entity',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
'capabilities' => 'moodle/site:config'
|
||||
]
|
||||
];
|
||||
@@ -56,6 +56,7 @@ if ($components) {
|
||||
$form = new steps_definitions_form(null, array('components' => $componentswithsteps));
|
||||
|
||||
// Output contents.
|
||||
$PAGE->requires->js_call_amd('tool_behat/steps', 'init');
|
||||
$renderer = $PAGE->get_renderer('tool_behat');
|
||||
echo $renderer->render_stepsdefinitions($steps, $form);
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/behat/classes/behat_generator_base.php');
|
||||
|
||||
/**
|
||||
* Renderer for behat tool web features
|
||||
*
|
||||
@@ -106,6 +108,32 @@ class tool_behat_renderer extends plugin_renderer_base {
|
||||
},
|
||||
$stepsdefinitions
|
||||
);
|
||||
|
||||
$elementstrings = [];
|
||||
$count = 1;
|
||||
$stepsdefinitions = preg_replace_callback('/(the following ")ELEMENT\d?_STRING(" exist:)/',
|
||||
function($matches) use (&$elementstrings, &$count) {
|
||||
// Replace element type arguments with a user-friendly select.
|
||||
if (empty($elementstrings)) {
|
||||
$behatgenerators = new behat_data_generators();
|
||||
$componententities = $behatgenerators->get_all_entities();
|
||||
ksort($componententities);
|
||||
$elementstrings = [];
|
||||
foreach ($componententities as $component => $entities) {
|
||||
asort($entities);
|
||||
foreach ($entities as $entity) {
|
||||
$string = ($component === 'core') ? $entity : $component . ' > ' . $entity;
|
||||
$elementstrings[$string] = $string;
|
||||
}
|
||||
}
|
||||
}
|
||||
$select = html_writer::select($elementstrings, 'entities' . $count, '', ['' => 'choosedots'],
|
||||
['class' => 'entities']);
|
||||
$count++;
|
||||
return $matches[1] . $select . $matches[2];
|
||||
},
|
||||
$stepsdefinitions
|
||||
);
|
||||
}
|
||||
|
||||
// Steps definitions.
|
||||
|
||||
@@ -29,3 +29,9 @@
|
||||
#page-admin-tool-behat-index .steps-definitions .stepregex {
|
||||
color: #060;
|
||||
}
|
||||
|
||||
#page-admin-tool-behat-index .steprequiredfields {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{{!
|
||||
This file is part of Moodle - https://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template tool_behat/steprequiredfields
|
||||
|
||||
Display a Gherkin-style table row showing required columns for a step.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"fields": [
|
||||
"user",
|
||||
"role",
|
||||
"context"
|
||||
]
|
||||
}
|
||||
}}
|
||||
<pre class="steprequiredfields">
|
||||
|{{#fields}} {{{.}}} |{{/fields}}
|
||||
</pre>
|
||||
@@ -13,6 +13,10 @@ Feature: List the system steps definitions
|
||||
Scenario: Accessing the list
|
||||
Then I should see "Step definitions"
|
||||
And I should not see "There aren't steps definitions matching this filter"
|
||||
And I should not see "the following \"ELEMENT_STRING\" exist:"
|
||||
And "entities1" "select" should exist
|
||||
And the "entities1" select box should contain "users"
|
||||
And the "entities1" select box should contain "mod_assign > submissions"
|
||||
|
||||
@javascript
|
||||
Scenario: Filtering by type
|
||||
@@ -34,3 +38,11 @@ Feature: List the system steps definitions
|
||||
Then I should not see "There aren't steps definitions matching this filter"
|
||||
And I should see "Checks the provided element and selector type exists in the current page."
|
||||
And I should see "Checks that an element and selector type exists in another element and selector type on the current page."
|
||||
|
||||
@javascript
|
||||
Scenario: Get required fields
|
||||
Given I set the field "entities1" to "users"
|
||||
And I should see "| username |"
|
||||
When I set the field "entities1" to "mod_quiz > user overrides"
|
||||
Then I should not see "| username |"
|
||||
And I should see "| quiz | user |"
|
||||
|
||||
@@ -13,24 +13,24 @@ Feature: Transform steps arguments
|
||||
And I open my profile in edit mode
|
||||
|
||||
Scenario: Use nasty strings on steps arguments
|
||||
When I set the field "Surname" to "$NASTYSTRING1"
|
||||
When I set the field "Last name" to "$NASTYSTRING1"
|
||||
And I set the field "Description" to "$NASTYSTRING2"
|
||||
And I set the field "City/town" to "$NASTYSTRING3"
|
||||
And I press "Update profile"
|
||||
And I click on "Edit profile" "link" in the "region-main" "region"
|
||||
Then I should not see "NASTYSTRING"
|
||||
And the field "Surname" matches value "$NASTYSTRING1"
|
||||
And the field "Last name" matches value "$NASTYSTRING1"
|
||||
And the field "City/town" matches value "$NASTYSTRING3"
|
||||
|
||||
Scenario: Use nasty strings on table nodes
|
||||
When I set the following fields to these values:
|
||||
| Surname | $NASTYSTRING1 |
|
||||
| Last name | $NASTYSTRING1 |
|
||||
| Description | $NASTYSTRING2 |
|
||||
| City/town | $NASTYSTRING3 |
|
||||
And I press "Update profile"
|
||||
And I click on "Edit profile" "link" in the "region-main" "region"
|
||||
Then I should not see "NASTYSTRING"
|
||||
And the field "Surname" matches value "$NASTYSTRING1"
|
||||
And the field "Last name" matches value "$NASTYSTRING1"
|
||||
And the field "City/town" matches value "$NASTYSTRING3"
|
||||
|
||||
Scenario: Use double quotes
|
||||
@@ -48,11 +48,11 @@ Feature: Transform steps arguments
|
||||
Scenario: Nasty strings with other contents
|
||||
When I set the field "First name" to "My Firstname $NASTYSTRING1"
|
||||
And I set the following fields to these values:
|
||||
| Surname | My Surname $NASTYSTRING2 |
|
||||
| Last name | My Last name $NASTYSTRING2 |
|
||||
And I press "Update profile"
|
||||
And I click on "Edit profile" "link" in the "region-main" "region"
|
||||
Then I should not see "NASTYSTRING"
|
||||
And I should see "My Firstname"
|
||||
And I should see "My Surname"
|
||||
And I should see "My Last name"
|
||||
And the field "First name" matches value "My Firstname $NASTYSTRING1"
|
||||
And the field "Surname" matches value "My Surname $NASTYSTRING2"
|
||||
And the field "Last name" matches value "My Last name $NASTYSTRING2"
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for get_entity_generator web service
|
||||
*
|
||||
* @package tool_behat
|
||||
* @copyright 2022 onwards Catalyst IT EU {@link https://catalyst-eu.net}
|
||||
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_behat\external;
|
||||
|
||||
/**
|
||||
* Tests for get_entity_generator web service
|
||||
*
|
||||
* @covers \tool_behat\external\get_entity_generator
|
||||
*/
|
||||
class get_entity_generator_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Log in as admin
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUp(): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the generator for a core entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_execute_core_entity(): void {
|
||||
$generator = get_entity_generator::execute('users');
|
||||
$this->assertEquals(['required' => ['username']], $generator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the generator for the plugin entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_execute_plugin_entity(): void {
|
||||
$generator = get_entity_generator::execute('mod_book > chapters');
|
||||
$this->assertEquals(['required' => ['book', 'title', 'content']], $generator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the generator for an entity with no required fields.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_execute_no_requried(): void {
|
||||
$generator = get_entity_generator::execute('mod_forum > posts');
|
||||
$this->assertEquals(['required' => []], $generator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the generator for a core entity that does not exist.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_execute_invalid_entity(): void {
|
||||
$this->expectException('coding_exception');
|
||||
get_entity_generator::execute('foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get a generator form a plugin that does not exist.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_execute_invalid_plugin(): void {
|
||||
$this->expectException('coding_exception');
|
||||
get_entity_generator::execute('foo > bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get a generator for an entity that does not exist, from a plugin that does.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_execute_invalid_plugin_entity(): void {
|
||||
$this->expectException('coding_exception');
|
||||
get_entity_generator::execute('mod_book > bar');
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2022041900; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2022041901; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2022041200; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_behat'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -81,6 +81,30 @@ $activity-icon-colors: (
|
||||
);
|
||||
{{</ highlight >}}
|
||||
|
||||
### Custom activity icons
|
||||
|
||||
Some activities allow icons to be customised. This can be done by implementing callback XXX_get_coursemodule_info() returning instance of object e.g. mod/lti/lib.php
|
||||
|
||||
{{< php >}}
|
||||
$info = new cached_cm_info();
|
||||
$info->iconurl = new moodle_url('https://moodle.org/theme/moodleorg/pix/moodle_logo_small.svg');
|
||||
{{< /php >}}
|
||||
|
||||
To get this customised icon, use:
|
||||
|
||||
{{< php >}}
|
||||
$iconurl = get_fast_modinfo($courseid)->get_cm($cmid)->get_icon_url()->out(false);
|
||||
{{< /php >}}
|
||||
|
||||
<div class="media mb-3">
|
||||
<div class="activityiconcontainer lti mr-3">
|
||||
<img alt="lti icon" title="lti icon" src="https://moodle.org/theme/moodleorg/pix/moodle_logo_small.svg" class="activityicon "> </div>
|
||||
<div class="media-body align-self-center">
|
||||
<div class="text-uppercase small">external</div>
|
||||
<div class="activityname"><a href="#">External tool module</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Examples
|
||||
|
||||
<div class="media mb-3">
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@
|
||||
* @copyright 2018 Adrian Greeve
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("tool_dataprivacy/expand_contract",["jquery","core/url","core/str"],(function($,url,str){var expandedImage=$('<img alt="" src="'+url.imageUrl("t/expanded")+'"/>'),collapsedImage=$('<img alt="" src="'+url.imageUrl("t/collapsed")+'"/>'),CLASSES_EXPAND="fa-caret-right",CLASSES_COLLAPSE="fa-caret-down";return{expandCollapse:function(targetnode,thisnode){targetnode.hasClass("hide")?(targetnode.removeClass("hide"),targetnode.addClass("visible"),targetnode.attr("aria-expanded",!0),thisnode.find(":header i.fa").removeClass(CLASSES_EXPAND),thisnode.find(":header i.fa").addClass(CLASSES_COLLAPSE),thisnode.find(":header img.icon").attr("src",expandedImage.attr("src"))):(targetnode.removeClass("visible"),targetnode.addClass("hide"),targetnode.attr("aria-expanded",!1),thisnode.find(":header i.fa").removeClass(CLASSES_COLLAPSE),thisnode.find(":header i.fa").addClass(CLASSES_EXPAND),thisnode.find(":header img.icon").attr("src",collapsedImage.attr("src")))},expandCollapseAll:function(nextstate){var currentstate="visible"==nextstate?"hide":"visible",ariaexpandedstate="visible"==nextstate,iconclassnow="visible"==nextstate?CLASSES_EXPAND:CLASSES_COLLAPSE,iconclassnext="visible"==nextstate?CLASSES_COLLAPSE:CLASSES_EXPAND,imagenow="visible"==nextstate?expandedImage.attr("src"):collapsedImage.attr("src");$("."+currentstate).each((function(){$(this).removeClass(currentstate),$(this).addClass(nextstate),$(this).attr("aria-expanded",ariaexpandedstate)})),$(".tool_dataprivacy-expand-all").data("visibilityState",currentstate),str.get_string(currentstate,"tool_dataprivacy").then((function(langString){$(".tool_dataprivacy-expand-all").html(langString)})).catch(Notification.exception),$(":header i.fa").each((function(){$(this).removeClass(iconclassnow),$(this).addClass(iconclassnext)})),$(":header img.icon").each((function(){$(this).attr("src",imagenow)}))}}}));
|
||||
define("tool_dataprivacy/expand_contract",["jquery","core/url","core/str","core/notification"],(function($,url,str,Notification){var expandedImage=$('<img alt="" src="'+url.imageUrl("t/expanded")+'"/>'),collapsedImage=$('<img alt="" src="'+url.imageUrl("t/collapsed")+'"/>'),CLASSES_EXPAND="fa-caret-right",CLASSES_COLLAPSE="fa-caret-down";return{expandCollapse:function(targetnode,thisnode){targetnode.hasClass("hide")?(targetnode.removeClass("hide"),targetnode.addClass("visible"),targetnode.attr("aria-expanded",!0),thisnode.find(":header i.fa").removeClass(CLASSES_EXPAND),thisnode.find(":header i.fa").addClass(CLASSES_COLLAPSE),thisnode.find(":header img.icon").attr("src",expandedImage.attr("src"))):(targetnode.removeClass("visible"),targetnode.addClass("hide"),targetnode.attr("aria-expanded",!1),thisnode.find(":header i.fa").removeClass(CLASSES_COLLAPSE),thisnode.find(":header i.fa").addClass(CLASSES_EXPAND),thisnode.find(":header img.icon").attr("src",collapsedImage.attr("src")))},expandCollapseAll:function(nextstate){var currentstate="visible"==nextstate?"hide":"visible",ariaexpandedstate="visible"==nextstate,iconclassnow="visible"==nextstate?CLASSES_EXPAND:CLASSES_COLLAPSE,iconclassnext="visible"==nextstate?CLASSES_COLLAPSE:CLASSES_EXPAND,imagenow="visible"==nextstate?expandedImage.attr("src"):collapsedImage.attr("src");$("."+currentstate).each((function(){$(this).removeClass(currentstate),$(this).addClass(nextstate),$(this).attr("aria-expanded",ariaexpandedstate)})),$(".tool_dataprivacy-expand-all").data("visibilityState",currentstate),str.get_string(currentstate,"tool_dataprivacy").then((function(langString){$(".tool_dataprivacy-expand-all").html(langString)})).catch(Notification.exception),$(":header i.fa").each((function(){$(this).removeClass(iconclassnow),$(this).addClass(iconclassnext)})),$(":header img.icon").each((function(){$(this).attr("src",imagenow)}))}}}));
|
||||
|
||||
//# sourceMappingURL=expand_contract.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
define(['jquery', 'core/url', 'core/str', 'core/notification'], function($, url, str, Notification) {
|
||||
|
||||
var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
|
||||
var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
|
||||
|
||||
@@ -79,6 +79,19 @@ class locale {
|
||||
* @return string|false Returns the new current locale, or FALSE on error.
|
||||
*/
|
||||
protected function set_locale(int $category = LC_ALL, string $locale = '0') {
|
||||
return setlocale($category, $locale);
|
||||
if (strlen($locale) <= 255 || PHP_OS_FAMILY === 'BSD' || PHP_OS_FAMILY === 'Darwin') {
|
||||
// We can set the whole locale all together.
|
||||
return setlocale($category, $locale);
|
||||
}
|
||||
|
||||
// Too long locale with linux or windows, let's split it into known and supported categories.
|
||||
$split = explode(';', $locale);
|
||||
foreach ($split as $element) {
|
||||
[$category, $value] = explode('=', $element);
|
||||
if (defined($category)) { // Only if the category exists, there are OS differences.
|
||||
setlocale(constant($category), $value);
|
||||
}
|
||||
}
|
||||
return setlocale(LC_ALL, 0); // Finally, return the complete configured locale.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tests for \tool_langimport\locale class.
|
||||
*
|
||||
* @package tool_langimport
|
||||
* @copyright 2018 Université Rennes 2 {@link https://www.univ-rennes2.fr}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_langimport;
|
||||
|
||||
/**
|
||||
@@ -29,6 +21,7 @@ namespace tool_langimport;
|
||||
*
|
||||
* @package tool_langimport
|
||||
* @category test
|
||||
* @coversDefaultClass \tool_langimport\locale
|
||||
* @copyright 2018 Université Rennes 2 {@link https://www.univ-rennes2.fr}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
@@ -36,6 +29,7 @@ class locale_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test that \tool_langimport\locale::check_locale_availability() works as expected.
|
||||
*
|
||||
* @covers ::check_locale_availability
|
||||
* @return void
|
||||
*/
|
||||
public function test_check_locale_availability() {
|
||||
@@ -43,7 +37,7 @@ class locale_test extends \advanced_testcase {
|
||||
// - first setlocale() call which backup current locale
|
||||
// - second setlocale() call which try to set new 'es' locale
|
||||
// - third setlocale() call which restore locale.
|
||||
$mock = $this->getMockBuilder(\tool_langimport\locale::class)
|
||||
$mock = $this->getMockBuilder(locale::class)
|
||||
->onlyMethods(['set_locale'])
|
||||
->getMock();
|
||||
$mock->method('set_locale')->will($this->onConsecutiveCalls('en', 'es', 'en'));
|
||||
@@ -56,7 +50,7 @@ class locale_test extends \advanced_testcase {
|
||||
// - first setlocale() call which backup current locale
|
||||
// - second setlocale() call which fail to set new locale
|
||||
// - third setlocale() call which restore locale.
|
||||
$mock = $this->getMockBuilder(\tool_langimport\locale::class)
|
||||
$mock = $this->getMockBuilder(locale::class)
|
||||
->onlyMethods(['set_locale'])
|
||||
->getMock();
|
||||
$mock->method('set_locale')->will($this->onConsecutiveCalls('en', false, 'en'));
|
||||
@@ -66,8 +60,104 @@ class locale_test extends \advanced_testcase {
|
||||
$this->assertFalse($result);
|
||||
|
||||
// Test an invalid parameter.
|
||||
$locale = new \tool_langimport\locale();
|
||||
$locale = new locale();
|
||||
$this->expectException(\coding_exception::class);
|
||||
$locale->check_locale_availability('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test \tool_langimport\locale::set_locale() own logic.
|
||||
*
|
||||
* We have to explicitly test set_locale() own logic and results,
|
||||
* that effectively sets the current locale, so we need to restore
|
||||
* the original locale after every test (ugly, from a purist unit test
|
||||
* point of view, but needed).
|
||||
*
|
||||
* @dataProvider set_locale_provider
|
||||
* @covers ::set_locale
|
||||
*
|
||||
* @param string $set locale string to be set.
|
||||
* @param string $ret expected results returned after setting the locale.
|
||||
*/
|
||||
public function test_set_locale(string $set, string $ret) {
|
||||
// Make set_locale() public.
|
||||
$loc = new locale();
|
||||
$rc = new \ReflectionClass(locale::class);
|
||||
$rm = $rc->getMethod('set_locale');
|
||||
$rm->setAccessible(true);
|
||||
|
||||
// Capture current locale for later restore (funnily, using the set_locale() method itself.
|
||||
$originallocale = $rm->invokeArgs($loc, [LC_ALL, 0]);
|
||||
|
||||
// Assert we get the locale defined as expected.
|
||||
$this->assertEquals($ret, $rm->invokeArgs($loc, [LC_ALL, $set]));
|
||||
|
||||
// We have finished, restore the original locale, so this doesn't affect other tests at distance.
|
||||
// (again, funnily, using the very same set_locale() method).
|
||||
$rm->invokeArgs($loc, [LC_ALL, $originallocale]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_set_locale().
|
||||
*
|
||||
* Provides a locale to be set (as 'set') and a expected return value (as 'ret'). Note that
|
||||
* some of the locales are OS dependent, so only the ones matching the OS will be provided.
|
||||
*
|
||||
* We make extensive use of the en_AU.UTF-8/English_Australia.1252 locale that is mandatory to
|
||||
* be installed in any system running PHPUnit tests.
|
||||
*/
|
||||
public function set_locale_provider(): array {
|
||||
// Let's list the allowed categories by OS.
|
||||
$bsdallowed = ['LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME'];
|
||||
$winallowed = ['LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME'];
|
||||
$linuxallowed = [
|
||||
'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME',
|
||||
'LC_PAPER', 'LC_NAME', 'LC_ADDRESS', 'LC_TELEPHONE', 'LC_MEASUREMENT', 'LC_IDENTIFICATION'
|
||||
];
|
||||
|
||||
// The base locale name is also OS dependent.
|
||||
$baselocale = get_string('locale', 'langconfig');
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$baselocale = get_string('localewin', 'langconfig');
|
||||
}
|
||||
|
||||
// Here we'll go accumulating cases to be provided.
|
||||
$cases = [];
|
||||
|
||||
// First, the simplest case, just pass a locale name, without categories.
|
||||
$cases['rawlocale'] = [
|
||||
'set' => $baselocale,
|
||||
'ret' => $baselocale,
|
||||
];
|
||||
|
||||
// Now, let's fill ALL LC categories, we should get back the locale name if all them are set with same value.
|
||||
// Note that this case is the one that, under Linux only, covers the changes performed to the set_locale() method.
|
||||
// Pick the correct categories depending on the OS.
|
||||
$oscategories = $bsdallowed; // Default to BSD/Dawrwin ones because they are the standard 6 supported by PHP.
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$oscategories = $winallowed;
|
||||
} else if (PHP_OS_FAMILY === 'Linux') {
|
||||
$oscategories = $linuxallowed;
|
||||
}
|
||||
|
||||
$localestr = '';
|
||||
foreach ($oscategories as $category) {
|
||||
// Format is different by OS too, so let build the string conditionally.
|
||||
if (PHP_OS_FAMILY === 'BSD' || PHP_OS_FAMILY === 'Darwin') {
|
||||
// BSD uses slashes (/) separated list of the 6 values in exact order.
|
||||
$localestr .= '/' . $baselocale;
|
||||
} else {
|
||||
// Linux/Windows use semicolon (;) separated list of category=value pairs.
|
||||
$localestr .= ';' . $category . '=' . $baselocale;
|
||||
}
|
||||
}
|
||||
$cases['allcategories'] = [
|
||||
'set' => trim($localestr, ';/'),
|
||||
'ret' => $baselocale,
|
||||
];
|
||||
|
||||
// Return all the built cases.
|
||||
return $cases;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ trait store {
|
||||
throw new \coding_exception("Store $called doesn't define classes in correct namespaces.");
|
||||
}
|
||||
$this->component = $parts[0];
|
||||
$this->store = str_replace('logstore_', '', $this->store);
|
||||
$this->store = str_replace('logstore_', '', $this->component);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -278,7 +278,7 @@ class external extends external_api {
|
||||
));
|
||||
|
||||
$competency = api::read_competency($params['competencyid']);
|
||||
$framework = api::read_framework($competency->get_competencyframeworkid());
|
||||
$framework = api::read_framework($competency->get('competencyframeworkid'));
|
||||
self::validate_context($framework->get_context());
|
||||
$renderable = new output\competency_summary($competency, $framework, $params['includerelated'], $params['includecourses']);
|
||||
$renderer = $PAGE->get_renderer('tool_lp');
|
||||
|
||||
@@ -229,7 +229,7 @@ class page_helper {
|
||||
$PAGE->set_pagelayout('standard');
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading($title);
|
||||
$PAGE->set_heading(fullname($user));
|
||||
|
||||
if (!empty($plan)) {
|
||||
$PAGE->navbar->add($title, $planurl);
|
||||
@@ -303,7 +303,7 @@ class page_helper {
|
||||
$PAGE->set_pagelayout('standard');
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading($title);
|
||||
$PAGE->set_heading(fullname($user));
|
||||
|
||||
if (!empty($evidence)) {
|
||||
$PAGE->navbar->add($title, $evidenceurl);
|
||||
|
||||
@@ -125,7 +125,7 @@ $string['qrcodetypeurl'] = 'QR code with site URL';
|
||||
$string['qrcodetypelogin'] = 'QR code with automatic login';
|
||||
$string['qrkeyttl'] = 'QR authentication key duration';
|
||||
$string['qrkeyttl_desc'] = 'The length of time for which a QR code for automatic login is valid.';
|
||||
$string['readingthisemailgettheapp'] = 'Reading this in an email? <a href="{$a}">Download the mobile app and receive notifications on your mobile device</a>.';
|
||||
$string['readingthisemailgettheapp'] = 'Are you reading this in an email? <a href="{$a}">Download the mobile app and receive notifications on your mobile device</a>.';
|
||||
$string['remoteaddons'] = 'Remote add-ons';
|
||||
$string['scanqrcode'] = 'Scan QR code';
|
||||
$string['selfsignedoruntrustedcertificatewarning'] = 'It seems that the HTTPS certificate is self-signed or not trusted. The mobile app will only work with trusted sites. Please use any online SSL checker to diagnose the problem. If it indicates that your certificate is OK, you can ignore this warning.';
|
||||
|
||||
@@ -19,7 +19,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -53,7 +53,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -98,7 +98,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -157,7 +157,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -210,7 +210,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -522,7 +522,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
When I press "Create my new account"
|
||||
Then I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -647,7 +647,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -716,7 +716,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -858,7 +858,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
|
||||
@@ -86,7 +86,7 @@ Feature: Optional policies
|
||||
| Email address | user3@address.invalid |
|
||||
| Email (again) | user3@address.invalid |
|
||||
| First name | User3 |
|
||||
| Surname | L3 |
|
||||
| Last name | L3 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user3@address.invalid"
|
||||
|
||||
@@ -51,6 +51,12 @@ if ($listurl) {
|
||||
$PAGE->navbar->add($listurl, $listurlnav);
|
||||
}
|
||||
|
||||
// Add a new nav item to make $listurl clickable for the Boost theme.
|
||||
if (isset($script)) {
|
||||
$lastrunnav = get_string('lastrun', 'tool_profiling');
|
||||
$PAGE->navbar->add($lastrunnav);
|
||||
}
|
||||
|
||||
// Header
|
||||
echo $OUTPUT->header();
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ $string['importok'] = 'File "{$a}" imported successfully.';
|
||||
$string['importprefix'] = 'Import prefix';
|
||||
$string['importproblem'] = 'Some problem happened importing the file "{$a}".';
|
||||
$string['lastrunof'] = 'Summary of last run of {$a}';
|
||||
$string['lastrun'] = 'Summary of last run';
|
||||
$string['markreferencerun'] = 'Mark as reference run/comment';
|
||||
$string['memory'] = 'Memory used';
|
||||
$string['pluginname'] = 'Profiling runs';
|
||||
|
||||
@@ -243,6 +243,16 @@ class category_bin extends base_bin {
|
||||
throw new \moodle_exception("Could not create course to restore into.");
|
||||
}
|
||||
|
||||
// As far as recycle bin is using MODE_AUTOMATED, it observes the General restore settings.
|
||||
// For recycle bin we want to ensure that backup files are always restore the users and groups information.
|
||||
// In order to achieve that, we hack the setting here via $CFG->forced_plugin_settings,
|
||||
// so it won't interfere other operations.
|
||||
// See MDL-65218 and MDL-35773 for more information.
|
||||
// This hack will be removed once recycle bin switches to use its own backup mode, with
|
||||
// own preferences and 100% separate from MOODLE_AUTOMATED.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
$CFG->forced_plugin_settings['restore'] = ['restore_general_users' => 1, 'restore_general_groups' => 1];
|
||||
|
||||
// Define the import.
|
||||
$controller = new \restore_controller(
|
||||
$tempdir,
|
||||
@@ -277,6 +287,10 @@ class category_bin extends base_bin {
|
||||
// Run the import.
|
||||
$controller->execute_plan();
|
||||
|
||||
// We don't need the forced setting anymore, hence unsetting it.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
unset($CFG->forced_plugin_settings['restore']);
|
||||
|
||||
// Have finished with the controller, let's destroy it, freeing mem and resources.
|
||||
$controller->destroy();
|
||||
|
||||
|
||||
@@ -234,6 +234,16 @@ class course_bin extends base_bin {
|
||||
$fb = get_file_packer('application/vnd.moodle.backup');
|
||||
$fb->extract_to_pathname($file, $fulltempdir);
|
||||
|
||||
// As far as recycle bin is using MODE_AUTOMATED, it observes the General restore settings.
|
||||
// For recycle bin we want to ensure that backup files are always restore the users and groups information.
|
||||
// In order to achieve that, we hack the setting here via $CFG->forced_plugin_settings,
|
||||
// so it won't interfere other operations.
|
||||
// See MDL-65218 and MDL-35773 for more information.
|
||||
// This hack will be removed once recycle bin switches to use its own backup mode, with
|
||||
// own preferences and 100% separate from MOODLE_AUTOMATED.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
$CFG->forced_plugin_settings['restore'] = ['restore_general_users' => 1, 'restore_general_groups' => 1];
|
||||
|
||||
// Define the import.
|
||||
$controller = new \restore_controller(
|
||||
$tempdir,
|
||||
@@ -264,6 +274,10 @@ class course_bin extends base_bin {
|
||||
// Run the import.
|
||||
$controller->execute_plan();
|
||||
|
||||
// We don't need the forced setting anymore, hence unsetting it.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
unset($CFG->forced_plugin_settings['restore']);
|
||||
|
||||
// Have finished with the controller, let's destroy it, freeing mem and resources.
|
||||
$controller->destroy();
|
||||
|
||||
|
||||
@@ -22,35 +22,30 @@ Feature: Backup user data
|
||||
And the following "activities" exist:
|
||||
| activity | course | section | name | intro |
|
||||
| quiz | C1 | 1 | Quiz 1 | Test quiz description |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | TF1 | First question |
|
||||
| Test questions | truefalse | TF2 | Second question |
|
||||
And quiz "Quiz 1" contains the following questions:
|
||||
| question | page |
|
||||
| TF1 | 1 |
|
||||
| TF2 | 1 |
|
||||
|
||||
@javascript
|
||||
Scenario: Delete and restore a quiz with user data
|
||||
Given I am on the "Quiz 1" "quiz activity" page logged in as teacher1
|
||||
And I add a "True/False" question to the "Quiz 1" quiz with:
|
||||
| Question name | TF1 |
|
||||
| Question text | First question |
|
||||
| General feedback | Thank you, this is the general feedback |
|
||||
| Correct answer | False |
|
||||
| Feedback for the response 'True'. | So you think it is true |
|
||||
| Feedback for the response 'False'. | So you think it is false |
|
||||
And I add a "True/False" question to the "Quiz 1" quiz with:
|
||||
| Question name | TF2 |
|
||||
| Question text | Second question |
|
||||
| General feedback | Thank you, this is the general feedback |
|
||||
| Correct answer | False |
|
||||
| Feedback for the response 'True'. | So you think it is true |
|
||||
| Feedback for the response 'False'. | So you think it is false |
|
||||
And I set the field "maxgrade" to "10.0"
|
||||
And I press "savechanges"
|
||||
And I log out
|
||||
When I am on the "Quiz 1" "quiz activity" page logged in as student1
|
||||
Scenario Outline: Delete and restore a quiz with user data
|
||||
Given the following config values are set as admin:
|
||||
| restore_general_users | <include_user> | restore |
|
||||
And I am on the "Quiz 1" "quiz activity" page logged in as student1
|
||||
And I press "Attempt quiz"
|
||||
And I click on "True" "radio" in the "First question" "question"
|
||||
And I click on "False" "radio" in the "Second question" "question"
|
||||
And I press "Finish attempt"
|
||||
And I press "Submit all and finish"
|
||||
And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
|
||||
And I should see "5.00 out of 10.00"
|
||||
And I should see "50.00 out of 100.00"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
@@ -63,5 +58,10 @@ Feature: Backup user data
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I navigate to "User report" in the course gradebook
|
||||
Then "Quiz 1" row "Grade" column of "user-grade" table should contain "5"
|
||||
Then "Quiz 1" row "Grade" column of "user-grade" table should contain "50"
|
||||
And "Quiz 1" row "Percentage" column of "user-grade" table should contain "50"
|
||||
|
||||
Examples:
|
||||
| include_user | case_explanation |
|
||||
| 1 | Checked |
|
||||
| 1 | Unchecked |
|
||||
|
||||
@@ -190,6 +190,11 @@ class category_bin_test extends \advanced_testcase {
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
|
||||
'restore/restore_general_users moodle' => [[
|
||||
(object)['plugin' => 'restore', 'name' => 'restore_general_users', 'value' => 0],
|
||||
(object)['plugin' => 'restore', 'name' => 'restore_general_groups', 'value' => 0],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,11 @@ class course_bin_test extends \advanced_testcase {
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
|
||||
'restore/restore_general_users moodle' => [[
|
||||
(object)['plugin' => 'restore', 'name' => 'restore_general_users', 'value' => 0],
|
||||
(object)['plugin' => 'restore', 'name' => 'restore_general_groups', 'value' => 0],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -66,14 +66,10 @@ Feature: Upload users
|
||||
@javascript
|
||||
Scenario: Upload users with custom profile fields
|
||||
# Create user profile field.
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Users > Accounts > User profile fields" in site administration
|
||||
And I click on "Create a new profile field" "link"
|
||||
And I click on "Text area" "link"
|
||||
And I set the following fields to these values:
|
||||
| Short name | superfield |
|
||||
| Name | Super field |
|
||||
And I click on "Save changes" "button"
|
||||
Given the following "custom profile fields" exist:
|
||||
| datatype | shortname | name |
|
||||
| text | superfield | Super field |
|
||||
And I log in as "admin"
|
||||
# Upload users.
|
||||
When I navigate to "Users > Accounts > Upload users" in site administration
|
||||
And I upload "lib/tests/fixtures/upload_users_profile.csv" file to "File" filemanager
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -44,7 +44,7 @@ import {prefetchStrings} from 'core/prefetch';
|
||||
* @constant
|
||||
* @type {number}
|
||||
*/
|
||||
const MINSPACING = 50;
|
||||
const MINSPACING = 10;
|
||||
|
||||
/**
|
||||
* A user tour.
|
||||
@@ -83,6 +83,12 @@ const Tour = class {
|
||||
// Apply configuration.
|
||||
this.configure.apply(this, arguments);
|
||||
|
||||
// Unset recalculate state.
|
||||
this.possitionNeedToBeRecalculated = false;
|
||||
|
||||
// Unset recalculate count.
|
||||
this.recalculatedNo = 0;
|
||||
|
||||
try {
|
||||
this.storage = window.sessionStorage;
|
||||
this.storageKey = 'tourstate_' + this.tourName;
|
||||
@@ -396,6 +402,11 @@ const Tour = class {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the CSS styles are allowed on the browser or not.
|
||||
if (!this.isCSSAllowed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let target = this.getStepTarget(stepConfig);
|
||||
if (target && target.length && target.is(':visible')) {
|
||||
// Without a target, there can be no step.
|
||||
@@ -405,6 +416,22 @@ const Tour = class {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the browser actually allow CSS styles?
|
||||
*
|
||||
* @returns {boolean} True if the browser is allowing CSS styles
|
||||
*/
|
||||
isCSSAllowed() {
|
||||
const testCSSElement = document.createElement('div');
|
||||
testCSSElement.classList.add('hide');
|
||||
document.body.appendChild(testCSSElement);
|
||||
const styles = window.getComputedStyle(testCSSElement);
|
||||
const isAllowed = styles.display === 'none';
|
||||
testCSSElement.remove();
|
||||
|
||||
return isAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next step in the tour.
|
||||
*
|
||||
@@ -924,6 +951,7 @@ const Tour = class {
|
||||
// Configure ARIA attributes on the target.
|
||||
let target = this.getStepTarget(stepConfig);
|
||||
if (target) {
|
||||
target.data('original-tabindex', target.attr('tabindex'));
|
||||
if (!target.attr('tabindex')) {
|
||||
target.attr('tabindex', 0);
|
||||
}
|
||||
@@ -1150,6 +1178,12 @@ const Tour = class {
|
||||
|
||||
if (target.data('original-tabindex')) {
|
||||
target.attr('tabindex', target.data('tabindex'));
|
||||
} else {
|
||||
// If the target does not have the tabindex attribute at the beginning. We need to remove it.
|
||||
// We should wait a little here before removing the attribute to prevent the browser from adding it again.
|
||||
window.setTimeout(() => {
|
||||
target.removeAttr('tabindex');
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1296,6 +1330,7 @@ const Tour = class {
|
||||
*/
|
||||
positionStep(stepConfig) {
|
||||
let content = this.currentStepNode;
|
||||
let thisT = this;
|
||||
if (!content || !content.length) {
|
||||
// Unable to find the step node.
|
||||
return this;
|
||||
@@ -1335,9 +1370,15 @@ const Tour = class {
|
||||
},
|
||||
onCreate: function(data) {
|
||||
recalculateArrowPosition(data);
|
||||
recalculateStepPosition(data);
|
||||
},
|
||||
onUpdate: function(data) {
|
||||
recalculateArrowPosition(data);
|
||||
if (thisT.possitionNeedToBeRecalculated) {
|
||||
thisT.recalculatedNo++;
|
||||
thisT.possitionNeedToBeRecalculated = false;
|
||||
recalculateStepPosition(data);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1387,6 +1428,90 @@ const Tour = class {
|
||||
}
|
||||
};
|
||||
|
||||
const recalculateStepPosition = function(data) {
|
||||
const placement = data.placement.split('-')[0];
|
||||
const isVertical = ['left', 'right'].indexOf(placement) !== -1;
|
||||
const popperElement = $(data.instance.popper);
|
||||
const targetElement = $(data.instance.reference);
|
||||
const arrowElement = popperElement.find('[data-role="arrow"]');
|
||||
const stepElement = popperElement.find('[data-role="flexitour-step"]');
|
||||
const viewportHeight = $(window).height();
|
||||
const viewportWidth = $(window).width();
|
||||
const arrowHeight = parseFloat(arrowElement.outerHeight(true));
|
||||
const popperHeight = parseFloat(popperElement.outerHeight(true));
|
||||
const targetHeight = parseFloat(targetElement.outerHeight(true));
|
||||
const arrowWidth = parseFloat(arrowElement.outerWidth(true));
|
||||
const popperWidth = parseFloat(popperElement.outerWidth(true));
|
||||
const targetWidth = parseFloat(targetElement.outerWidth(true));
|
||||
let maxHeight;
|
||||
|
||||
if (thisT.recalculatedNo > 1) {
|
||||
// The current screen is too small, and cannot fit with the original placement.
|
||||
// We should set the placement to auto so the PopperJS can calculate the perfect placement.
|
||||
thisT.currentStepPopper.options.placement = isVertical ? 'auto-left' : 'auto-bottom';
|
||||
}
|
||||
if (thisT.recalculatedNo > 2) {
|
||||
// Return here to prevent recursive calling.
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVertical) {
|
||||
// Find the best place to put the tour: Left of right.
|
||||
const leftSpace = targetElement.offset().left > 0 ? targetElement.offset().left : 0;
|
||||
const rightSpace = viewportWidth - leftSpace - targetWidth;
|
||||
const remainingSpace = leftSpace >= rightSpace ? leftSpace : rightSpace;
|
||||
maxHeight = viewportHeight - MINSPACING * 2;
|
||||
if (remainingSpace < (popperWidth + arrowWidth)) {
|
||||
const maxWidth = remainingSpace - MINSPACING - arrowWidth;
|
||||
if (maxWidth > 0) {
|
||||
popperElement.css({
|
||||
'max-width': maxWidth + 'px',
|
||||
});
|
||||
// Not enough space, flag true to make Popper to recalculate the position.
|
||||
thisT.possitionNeedToBeRecalculated = true;
|
||||
}
|
||||
} else if (maxHeight < popperHeight) {
|
||||
// Check if the Popper's height can fit the viewport height or not.
|
||||
// If not, set the correct max-height value for the Popper element.
|
||||
popperElement.css({
|
||||
'max-height': maxHeight + 'px',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Find the best place to put the tour: Top of bottom.
|
||||
const topSpace = targetElement.offset().top > 0 ? targetElement.offset().top : 0;
|
||||
const bottomSpace = viewportHeight - topSpace - targetHeight;
|
||||
const remainingSpace = topSpace >= bottomSpace ? topSpace : bottomSpace;
|
||||
maxHeight = remainingSpace - MINSPACING - arrowHeight;
|
||||
if (remainingSpace < (popperHeight + arrowHeight)) {
|
||||
// Not enough space, flag true to make Popper to recalculate the position.
|
||||
thisT.possitionNeedToBeRecalculated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the Popper's height can fit the viewport height or not.
|
||||
// If not, set the correct max-height value for the body.
|
||||
const currentStepBody = stepElement.find('[data-placeholder="body"]').first();
|
||||
const headerEle = stepElement.find('.modal-header').first();
|
||||
const footerEle = stepElement.find('.modal-footer').first();
|
||||
const headerHeight = headerEle.outerHeight(true) ?? 0;
|
||||
const footerHeight = footerEle.outerHeight(true) ?? 0;
|
||||
maxHeight = maxHeight - headerHeight - footerHeight;
|
||||
if (maxHeight > 0) {
|
||||
headerEle.removeClass('minimal');
|
||||
footerEle.removeClass('minimal');
|
||||
currentStepBody.css({
|
||||
'max-height': maxHeight + 'px',
|
||||
'overflow': 'auto',
|
||||
});
|
||||
} else {
|
||||
headerEle.addClass('minimal');
|
||||
footerEle.addClass('minimal');
|
||||
}
|
||||
// Call the Popper update method to update the position.
|
||||
thisT.currentStepPopper.update();
|
||||
};
|
||||
|
||||
let background = $('[data-flexitour="step-background"]');
|
||||
if (background.length) {
|
||||
target = background;
|
||||
@@ -1503,9 +1628,7 @@ const Tour = class {
|
||||
}
|
||||
|
||||
let targetPosition = this.calculatePosition(targetNode);
|
||||
if (targetPosition === 'fixed') {
|
||||
background.css('top', 0);
|
||||
} else if (targetPosition === 'absolute') {
|
||||
if (targetPosition === 'absolute') {
|
||||
background.css('position', 'fixed');
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@ span[data-flexitour="container"] .modal-dialog .modal-content .modal-header {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
span[data-flexitour="container"] .modal-dialog .modal-content .modal-header.minimal,
|
||||
span[data-flexitour="container"] .modal-dialog .modal-content .modal-footer.minimal {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
span[data-flexitour="container"] .modal-dialog .modal-content .modal-footer {
|
||||
justify-content: flex-start;
|
||||
border-top: 0;
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
* @package core
|
||||
*/
|
||||
|
||||
use core_user\fields;
|
||||
|
||||
define('NO_OUTPUT_BUFFERING', true);
|
||||
require_once('../../config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
@@ -37,7 +39,7 @@ if (empty($SESSION->bulk_users)) {
|
||||
}
|
||||
|
||||
if ($dataformat) {
|
||||
$fields = array('id' => 'id',
|
||||
$originfields = array('id' => 'id',
|
||||
'username' => 'username',
|
||||
'email' => 'email',
|
||||
'firstname' => 'firstname',
|
||||
@@ -51,8 +53,10 @@ if ($dataformat) {
|
||||
'country' => 'country');
|
||||
|
||||
$extrafields = profile_get_user_fields_with_data(0);
|
||||
$profilefields = [];
|
||||
foreach ($extrafields as $formfield) {
|
||||
$fields['profile_field_'.$formfield->get_shortname()] = 'profile_field_'.$formfield->get_shortname();
|
||||
$profilefields[fields::PROFILE_FIELD_PREFIX . $formfield->get_shortname()] = fields::PROFILE_FIELD_PREFIX .
|
||||
$formfield->get_shortname();
|
||||
}
|
||||
|
||||
$filename = clean_filename(get_string('users'));
|
||||
@@ -60,17 +64,17 @@ if ($dataformat) {
|
||||
$downloadusers = new ArrayObject($SESSION->bulk_users);
|
||||
$iterator = $downloadusers->getIterator();
|
||||
|
||||
\core\dataformat::download_data($filename, $dataformat, $fields, $iterator, function($userid, $supportshtml)
|
||||
use ($extrafields, $fields) {
|
||||
\core\dataformat::download_data($filename, $dataformat, array_merge($originfields, $profilefields), $iterator,
|
||||
function($userid, $supportshtml) use ($originfields) {
|
||||
|
||||
global $DB;
|
||||
|
||||
if (!$user = $DB->get_record('user', array('id' => $userid))) {
|
||||
return null;
|
||||
}
|
||||
profile_load_data($user);
|
||||
|
||||
$userprofiledata = array();
|
||||
foreach ($fields as $field => $unused) {
|
||||
foreach ($originfields as $field) {
|
||||
// Custom user profile textarea fields come in an array
|
||||
// The first element is the text and the second is the format.
|
||||
// We only take the text.
|
||||
@@ -82,6 +86,19 @@ if ($dataformat) {
|
||||
$userprofiledata[$field] = $user->$field;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Formatting extra field if transform is true.
|
||||
$extrafields = profile_get_user_fields_with_data($userid);
|
||||
foreach ($extrafields as $field) {
|
||||
$fieldkey = fields::PROFILE_FIELD_PREFIX . $field->get_shortname();
|
||||
if ($field->is_transform_supported()) {
|
||||
$userprofiledata[$fieldkey] = $field->display_data();
|
||||
} else {
|
||||
$userprofiledata[$fieldkey] = $field->data;
|
||||
}
|
||||
}
|
||||
|
||||
return $userprofiledata;
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Feature: User must accept policy when logging in and signing up
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
And I should see "An email should have been sent to your address at user1@address.invalid"
|
||||
@@ -48,7 +48,7 @@ Feature: User must accept policy when logging in and signing up
|
||||
| Email address | user1@address.invalid |
|
||||
| Email (again) | user1@address.invalid |
|
||||
| First name | User1 |
|
||||
| Surname | L1 |
|
||||
| Last name | L1 |
|
||||
| I understand and agree | 1 |
|
||||
And I press "Create my new account"
|
||||
And I should see "Confirm your account"
|
||||
@@ -81,7 +81,7 @@ Feature: User must accept policy when logging in and signing up
|
||||
| Email address | <email1> |
|
||||
| Email (again) | <email2> |
|
||||
| First name | Jane |
|
||||
| Surname | Doe |
|
||||
| Last name | Doe |
|
||||
And I press "Create my new account"
|
||||
Then I should <expect> "This email address is already registered. Perhaps you created an account in the past?"
|
||||
And I should <expect2> "Invalid email address"
|
||||
|
||||
@@ -59,8 +59,8 @@ $string['privacy:metadata:mnet_external:institution'] = 'The institution that th
|
||||
$string['privacy:metadata:mnet_external:lang'] = 'A user preference for the language shown.';
|
||||
$string['privacy:metadata:mnet_external:lastaccess'] = 'The time that the user last accessed the site.';
|
||||
$string['privacy:metadata:mnet_external:lastlogin'] = 'The last login of this user.';
|
||||
$string['privacy:metadata:mnet_external:lastname'] = 'The surname of the user.';
|
||||
$string['privacy:metadata:mnet_external:lastnamephonetic'] = 'The phonetic details about the user\'s surname.';
|
||||
$string['privacy:metadata:mnet_external:lastname'] = 'The last name of the user.';
|
||||
$string['privacy:metadata:mnet_external:lastnamephonetic'] = 'The phonetic details of the user\'s last name.';
|
||||
$string['privacy:metadata:mnet_external:maildigest'] = 'A setting for the mail digest for this user.';
|
||||
$string['privacy:metadata:mnet_external:maildisplay'] = 'A preference for the user about displaying their email address to other users.';
|
||||
$string['privacy:metadata:mnet_external:middlename'] = 'The middle name of the user';
|
||||
|
||||
@@ -86,7 +86,7 @@ Please log in as before, using the link on the login page.
|
||||
{$a->admin}';
|
||||
$string['emailpasswordchangeinfosubject'] = '{$a}: Change password information';
|
||||
$string['info'] = 'External account';
|
||||
$string['issuer'] = 'OAuth 2 Service';
|
||||
$string['issuer'] = 'OAuth 2 service';
|
||||
$string['issuernologin'] = 'This issuer can not be used to login';
|
||||
$string['linkedlogins'] = 'Linked logins';
|
||||
$string['linkedloginshelp'] = 'Help with linked logins';
|
||||
@@ -95,7 +95,7 @@ $string['loginerror_nouserinfo'] = 'No user information was returned. The OAuth
|
||||
$string['loginerror_invaliddomain'] = 'The email address is not allowed at this site.';
|
||||
$string['loginerror_authenticationfailed'] = 'The authentication process failed.';
|
||||
$string['loginerror_cannotcreateaccounts'] = 'An account with your email address could not be found.';
|
||||
$string['noissuersavailable'] = 'None of the configured OAuth2 services allow you to link login accounts';
|
||||
$string['noissuersavailable'] = 'None of the configured OAuth 2 services allow you to link login accounts.';
|
||||
$string['notloggedindebug'] = 'The login attempt failed. Reason: {$a}';
|
||||
$string['notwhileloggedinas'] = 'Linked logins cannot be managed while logged in as another user.';
|
||||
$string['oauth2:managelinkedlogins'] = 'Manage own linked login accounts';
|
||||
|
||||
+8
-8
@@ -32,7 +32,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
And I set the field "Activity or resource" to "Previous activity with completion"
|
||||
And I press "Save and return to course"
|
||||
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
|
||||
@@ -58,7 +58,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
And I set the field "Activity or resource" to "Previous activity with completion"
|
||||
And I press "Save and return to course"
|
||||
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
|
||||
@@ -83,7 +83,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
And I set the field "Activity or resource" to "Previous activity with completion"
|
||||
And I press "Save and return to course"
|
||||
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
|
||||
@@ -112,7 +112,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
And I set the field "Activity or resource" to "Previous activity with completion"
|
||||
And I press "Save changes"
|
||||
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
|
||||
@@ -138,7 +138,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
Then the "Activity or resource" select box should not contain "Previous activity with completion"
|
||||
|
||||
# Set Page2 restriction to Previous Activity with completion and delete Page1.
|
||||
@@ -148,7 +148,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
And I set the field "Activity or resource" to "Previous activity with completion"
|
||||
And I press "Save and return to course"
|
||||
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
|
||||
@@ -170,7 +170,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
And I set the field "Activity or resource" to "Previous activity with completion"
|
||||
And I press "Save changes"
|
||||
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
|
||||
@@ -184,7 +184,7 @@ Feature: Confirm that availability_completion works with previous activity setti
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Displayed greyed-out if user does not meet this condition • Click to hide" "link"
|
||||
And I click on "Displayed if student doesn't meet this condition • Click to hide" "link"
|
||||
And I set the field "Activity or resource" to "Previous activity with completion"
|
||||
And I press "Save changes"
|
||||
Then I should see "Not available unless: The previous activity with completion" in the "region-main" "region"
|
||||
|
||||
@@ -34,11 +34,11 @@ $string['option_min'] = 'must be ≥';
|
||||
$string['option_max'] = 'must be <';
|
||||
$string['pluginname'] = 'Restriction by grades';
|
||||
$string['requires_any'] = 'You have a grade in <strong>{$a}</strong>';
|
||||
$string['requires_max'] = 'You get an appropriate score in <strong>{$a}</strong>';
|
||||
$string['requires_min'] = 'You achieve a required score in <strong>{$a}</strong>';
|
||||
$string['requires_max'] = 'You achieve lower than a certain score in <strong>{$a}</strong>';
|
||||
$string['requires_min'] = 'You achieve higher than a certain score in <strong>{$a}</strong>';
|
||||
$string['requires_notany'] = 'You do not have a grade in <strong>{$a}</strong>';
|
||||
$string['requires_notgeneral'] = 'You do not get certain scores in <strong>{$a}</strong>';
|
||||
$string['requires_range'] = 'You get a particular score in <strong>{$a}</strong>';
|
||||
$string['requires_range'] = 'You achieve a score within a certain range in <strong>{$a}</strong>';
|
||||
$string['missing'] = '(missing activity)';
|
||||
$string['title'] = 'Grade';
|
||||
$string['privacy:metadata'] = 'The Restriction by grades plugin does not store any personal data.';
|
||||
|
||||
@@ -159,5 +159,5 @@ Feature: availability_grade
|
||||
|
||||
# Student sees information about no access to group, with group name in correct language.
|
||||
When I am on the "C1" "Course" page logged in as "student1"
|
||||
Then I should see "Not available unless: You achieve a required score in A-One"
|
||||
Then I should see "Not available unless: You achieve higher than a certain score in A-One"
|
||||
And I should not see "A-Un"
|
||||
|
||||
@@ -77,7 +77,7 @@ class condition_test extends \advanced_testcase {
|
||||
$this->assertFalse($cond->is_available(false, $info, true, $user->id));
|
||||
$information = $cond->get_description(false, false, $info);
|
||||
$information = \core_availability\info::format_info($information, $course);
|
||||
$this->assertMatchesRegularExpression('~achieve a required score.*Test!~', $information);
|
||||
$this->assertMatchesRegularExpression('~achieve higher than.*Test!~', $information);
|
||||
$this->assertTrue($cond->is_available(true, $info, true, $user->id));
|
||||
|
||||
// Min grade (success).
|
||||
@@ -95,7 +95,7 @@ class condition_test extends \advanced_testcase {
|
||||
$this->assertFalse($cond->is_available(false, $info, true, $user->id));
|
||||
$information = $cond->get_description(false, false, $info);
|
||||
$information = \core_availability\info::format_info($information, $course);
|
||||
$this->assertMatchesRegularExpression('~get an appropriate score.*Test!~', $information);
|
||||
$this->assertMatchesRegularExpression('~achieve lower than a certain score in.*Test!~', $information);
|
||||
$this->assertTrue($cond->is_available(true, $info, true, $user->id));
|
||||
|
||||
// Max grade (success).
|
||||
@@ -113,7 +113,7 @@ class condition_test extends \advanced_testcase {
|
||||
$this->assertFalse($cond->is_available(false, $info, true, $user->id));
|
||||
$information = $cond->get_description(false, false, $info);
|
||||
$information = \core_availability\info::format_info($information, $course);
|
||||
$this->assertMatchesRegularExpression('~get a particular score.*Test!~', $information);
|
||||
$this->assertMatchesRegularExpression('~achieve a score within a certain range.*Test!~', $information);
|
||||
$this->assertTrue($cond->is_available(true, $info, true, $user->id));
|
||||
|
||||
// Still fail (other end).
|
||||
|
||||
@@ -64,7 +64,7 @@ class frontend extends \core_availability\frontend {
|
||||
global $DB;
|
||||
if ($courseid != $this->allgroupingscourseid) {
|
||||
$this->allgroupings = $DB->get_records('groupings',
|
||||
array('courseid' => $courseid), 'id, name');
|
||||
['courseid' => $courseid], 'name');
|
||||
$this->allgroupingscourseid = $courseid;
|
||||
}
|
||||
return $this->allgroupings;
|
||||
|
||||
@@ -62,15 +62,10 @@ Feature: availability_profile
|
||||
|
||||
@javascript
|
||||
Scenario: Test with custom user profile field
|
||||
# Add custom field.
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Users > Accounts > User profile fields" in site administration
|
||||
And I click on "Create a new profile field" "link"
|
||||
And I click on "Text input" "link"
|
||||
And I set the following fields to these values:
|
||||
| Short name | superfield |
|
||||
| Name | Super field |
|
||||
And I click on "Save changes" "button"
|
||||
Given the following "custom profile fields" exist:
|
||||
| datatype | shortname | name |
|
||||
| text | superfield | Super field |
|
||||
And I log in as "admin"
|
||||
|
||||
# Set field value for user.
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
|
||||
@@ -86,13 +86,13 @@ Feature: edit_availability
|
||||
And I should see "Date" in the "Restrict access" "fieldset"
|
||||
And ".availability-item .availability-eye img" "css_element" should be visible
|
||||
And ".availability-item .availability-delete img" "css_element" should be visible
|
||||
And the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Displayed greyed-out"
|
||||
And the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Displayed if student"
|
||||
|
||||
# Toggle the eye icon.
|
||||
When I click on ".availability-item .availability-eye img" "css_element"
|
||||
Then the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Hidden entirely"
|
||||
When I click on ".availability-item .availability-eye img" "css_element"
|
||||
Then the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Displayed greyed-out"
|
||||
Then the "alt" attribute of ".availability-item .availability-eye img" "css_element" should contain "Displayed if student"
|
||||
|
||||
# Click the delete button.
|
||||
When I click on ".availability-item .availability-delete img" "css_element"
|
||||
|
||||
@@ -48,7 +48,7 @@ class backup_files_edit_form extends moodleform {
|
||||
$mform->setType('component', PARAM_COMPONENT);
|
||||
|
||||
$mform->addElement('hidden', 'returnurl', $this->_customdata['returnurl']);
|
||||
$mform->setType('returnurl', PARAM_URL);
|
||||
$mform->setType('returnurl', PARAM_LOCALURL);
|
||||
|
||||
$this->add_action_buttons(true, get_string('savechanges'));
|
||||
$this->set_data($this->_customdata['data']);
|
||||
|
||||
@@ -5261,6 +5261,7 @@ class restore_move_module_questions_categories extends restore_execution_step {
|
||||
AND parentitemid = ?", array($this->get_restoreid(), $contextid));
|
||||
$top = question_get_top_category($newcontext->newitemid, true);
|
||||
$oldtopid = 0;
|
||||
$categoryids = [];
|
||||
foreach ($modulecats as $modulecat) {
|
||||
// Before 3.5, question categories could be created at top level.
|
||||
// From 3.5 onwards, all question categories should be a child of a special category called the "top" category.
|
||||
@@ -5276,6 +5277,7 @@ class restore_move_module_questions_categories extends restore_execution_step {
|
||||
$cat->parent = $top->id;
|
||||
}
|
||||
$DB->update_record('question_categories', $cat);
|
||||
$categoryids[] = (int)$cat->id;
|
||||
}
|
||||
|
||||
// And set new contextid (and maybe update newitemid) also in question_category mapping (will be
|
||||
@@ -5284,6 +5286,30 @@ class restore_move_module_questions_categories extends restore_execution_step {
|
||||
$modulecat->newitemid, $newcontext->newitemid);
|
||||
}
|
||||
|
||||
// Update the context id of any tags applied to any questions in these categories.
|
||||
if ($categoryids) {
|
||||
[$categorysql, $categoryidparams] = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED);
|
||||
$sqlupdate = "UPDATE {tag_instance}
|
||||
SET contextid = :newcontext
|
||||
WHERE component = :component
|
||||
AND itemtype = :itemtype
|
||||
AND itemid IN (SELECT DISTINCT bi.newitemid as questionid
|
||||
FROM {backup_ids_temp} bi
|
||||
JOIN {question} q ON q.id = bi.newitemid
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
WHERE bi.backupid = :backupid AND bi.itemname = 'question_created'
|
||||
AND qbe.questioncategoryid {$categorysql}) ";
|
||||
$params = [
|
||||
'newcontext' => $newcontext->newitemid,
|
||||
'component' => 'core_question',
|
||||
'itemtype' => 'question',
|
||||
'backupid' => $this->get_restoreid(),
|
||||
];
|
||||
$params += $categoryidparams;
|
||||
$DB->execute($sqlupdate, $params);
|
||||
}
|
||||
|
||||
// Now set the parent id for the question categories that were in the top category in the course context
|
||||
// and have been moved now.
|
||||
if ($oldtopid) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
@core @core_backup
|
||||
Feature: Backup and restore of the question that was tagged
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
|
||||
@javascript @_file_upload
|
||||
Scenario: Restore the quiz containing the question that was tagged
|
||||
Given I am on the "Course 1" "restore" page logged in as "admin"
|
||||
And I press "Manage backup files"
|
||||
And I upload "backup/moodle2/tests/fixtures/test_tags_backup.mbz" file to "Files" filemanager
|
||||
And I press "Save changes"
|
||||
And I restore "test_tags_backup.mbz" backup into a new course using this options:
|
||||
| Schema | Course name | Course 2 |
|
||||
| Schema | Course short name | C2 |
|
||||
When I am on the "TF1" "core_question > edit" page logged in as admin
|
||||
And I expand all fieldsets
|
||||
Then I should see "Tag1-TF1"
|
||||
And I should see "Tag2-TF1"
|
||||
And I am on the "TF2" "core_question > edit" page logged in as admin
|
||||
And I expand all fieldsets
|
||||
And I should see "Tag1-TF2"
|
||||
Binary file not shown.
@@ -1806,10 +1806,9 @@ abstract class restore_dbops {
|
||||
public static function calculate_course_names($courseid, $fullname, $shortname) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$currentfullname = '';
|
||||
$currentshortname = '';
|
||||
$counter = 0;
|
||||
// Iteratere while the name exists
|
||||
|
||||
// Iterate while fullname or shortname exist.
|
||||
do {
|
||||
if ($counter) {
|
||||
$suffixfull = ' ' . get_string('copyasnoun') . ' ' . $counter;
|
||||
@@ -1818,8 +1817,11 @@ abstract class restore_dbops {
|
||||
$suffixfull = '';
|
||||
$suffixshort = '';
|
||||
}
|
||||
$currentfullname = $fullname.$suffixfull;
|
||||
$currentshortname = substr($shortname, 0, 100 - strlen($suffixshort)).$suffixshort; // < 100cc
|
||||
|
||||
// Ensure we don't overflow maximum length of name fields, in multi-byte safe manner.
|
||||
$currentfullname = core_text::substr($fullname, 0, 254 - strlen($suffixfull)) . $suffixfull;
|
||||
$currentshortname = core_text::substr($shortname, 0, 100 - strlen($suffixshort)) . $suffixshort;
|
||||
|
||||
$coursefull = $DB->get_record_select('course', 'fullname = ? AND id != ?',
|
||||
array($currentfullname, $courseid), '*', IGNORE_MULTIPLE);
|
||||
$courseshort = $DB->get_record_select('course', 'shortname = ? AND id != ?', array($currentshortname, $courseid));
|
||||
|
||||
@@ -96,6 +96,21 @@ class async_helper {
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return appropriate description for current async operation {@see async_helper::type}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_operation_description(): string {
|
||||
$operations = [
|
||||
'backup' => new lang_string('backup'),
|
||||
'copy' => new lang_string('copycourse'),
|
||||
'restore' => new lang_string('restore'),
|
||||
];
|
||||
|
||||
return (string) ($operations[$this->type] ?? $this->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for preg_replace_callback.
|
||||
* Replaces message placeholders with real values.
|
||||
@@ -105,7 +120,7 @@ class async_helper {
|
||||
*/
|
||||
private function lookup_message_variables($matches) {
|
||||
$options = array(
|
||||
'operation' => $this->type,
|
||||
'operation' => $this->get_operation_description(),
|
||||
'backupid' => $this->backupid,
|
||||
'user_username' => $this->user->username,
|
||||
'user_email' => $this->user->email,
|
||||
|
||||
@@ -30,6 +30,7 @@ require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
||||
* Asyncronhous helper tests.
|
||||
*
|
||||
* @package core_backup
|
||||
* @covers \async_helper
|
||||
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
@@ -47,7 +48,7 @@ class async_helper_test extends \advanced_testcase {
|
||||
set_config('backup_async_message_users', '1', 'backup');
|
||||
set_config('backup_async_message_subject', 'Moodle {operation} completed sucessfully', 'backup');
|
||||
set_config('backup_async_message',
|
||||
'Dear {user_firstname} {user_lastname}, <br/> Your {operation} (ID: {backupid}) has completed successfully!',
|
||||
'Dear {user_firstname} {user_lastname}, your {operation} (ID: {backupid}) has completed successfully!',
|
||||
'backup');
|
||||
set_config('allowedemaildomains', 'example.com');
|
||||
|
||||
@@ -76,15 +77,17 @@ class async_helper_test extends \advanced_testcase {
|
||||
$this->assertCount(1, $emails);
|
||||
$email = reset($emails);
|
||||
|
||||
$this->assertSame($USER->email, $email->from);
|
||||
$this->assertSame($user2->email, $email->to);
|
||||
$this->assertSame('Moodle backup completed sucessfully', $email->subject);
|
||||
$this->assertNotEmpty($email->header);
|
||||
$this->assertNotEmpty($email->body);
|
||||
$this->assertMatchesRegularExpression("/$backupid/", $email->body);
|
||||
$this->assertThat($email->body, $this->logicalNot($this->stringContains('{')));
|
||||
$this->assertGreaterThan(0, $messageid);
|
||||
$sink->clear();
|
||||
|
||||
$this->assertSame($USER->email, $email->from);
|
||||
$this->assertSame($user2->email, $email->to);
|
||||
$this->assertSame('Moodle Backup completed sucessfully', $email->subject);
|
||||
|
||||
// Assert body placeholders have all been replaced.
|
||||
$this->assertStringContainsString('Dear test human, your Backup', $email->body);
|
||||
$this->assertStringContainsString("(ID: {$backupid})", $email->body);
|
||||
$this->assertStringNotContainsString('{', $email->body);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -130,7 +130,6 @@ Feature: The my overview block allows users to easily access their courses
|
||||
|
||||
Scenario: View favourite courses - w/ persistence
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
@@ -201,7 +200,6 @@ Feature: The my overview block allows users to easily access their courses
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "In progress" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I reload the page
|
||||
@@ -215,7 +213,6 @@ Feature: The my overview block allows users to easily access their courses
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "Past" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 1')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 1')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 1')]" "xpath_element"
|
||||
And I reload the page
|
||||
@@ -229,7 +226,6 @@ Feature: The my overview block allows users to easily access their courses
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "Future" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I reload the page
|
||||
@@ -243,7 +239,6 @@ Feature: The my overview block allows users to easily access their courses
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "All" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I reload the page
|
||||
@@ -261,7 +256,6 @@ Feature: The my overview block allows users to easily access their courses
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
# We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (including removed from view)" element instead
|
||||
When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I reload the page
|
||||
|
||||
@@ -25,7 +25,6 @@ Feature: The my overview block allows users to favourite their courses
|
||||
|
||||
Scenario: Favourite a course on a course card
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I reload the page
|
||||
@@ -36,7 +35,6 @@ Feature: The my overview block allows users to favourite their courses
|
||||
|
||||
Scenario: Star a course and switch display to list
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Display drop-down menu" "button" in the "Course overview" "block"
|
||||
@@ -48,7 +46,6 @@ Feature: The my overview block allows users to favourite their courses
|
||||
|
||||
Scenario: Star a course and switch display to summary
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Display drop-down menu" "button" in the "Course overview" "block"
|
||||
|
||||
@@ -27,7 +27,6 @@ Feature: The my overview block allows users to hide their courses
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "All" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I reload the page
|
||||
@@ -36,33 +35,29 @@ Feature: The my overview block allows users to hide their courses
|
||||
Scenario: Test hide toggle functionality w/ favorites
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "All" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "All" "link" in the "Course overview" "block"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
When I reload the page
|
||||
Then I should not see "Course 2" in the "Course overview" "block"
|
||||
And I should not see "Course 2" in the "Course overview" "block"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
And I click on "Starred" "link" in the "Course overview" "block"
|
||||
Then I should not see "Course 2" in the "Course overview" "block"
|
||||
And I click on "Starred" "button" in the "Course overview" "block"
|
||||
And I click on "Removed from view" "link" in the "Course overview" "block"
|
||||
Then I should see "Course 2" in the "Course overview" "block"
|
||||
And I should see "Course 2" in the "Course overview" "block"
|
||||
|
||||
Scenario: Test show toggle functionality
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "All" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "All" "link" in the "Course overview" "block"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
When I click on "All" "button" in the "Course overview" "block"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
And I click on "Removed from view" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Restore to view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I reload the page
|
||||
And I click on "Removed from view" "button" in the "Course overview" "block"
|
||||
@@ -72,33 +67,29 @@ Feature: The my overview block allows users to hide their courses
|
||||
Scenario: Test show toggle functionality w/ favorites
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "All" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "All" "link" in the "Course overview" "block"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
And I click on "Removed from view" "link" in the "Course overview" "block"
|
||||
And I should see "Course 2" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Restore to view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
When I reload the page
|
||||
Then I should not see "Course 2" in the "Course overview" "block"
|
||||
And I click on "Removed from view" "button" in the "Course overview" "block"
|
||||
And I click on "All" "link" in the "Course overview" "block"
|
||||
Then I should see "Course 2" in the "Course overview" "block"
|
||||
And I should see "Course 2" in the "Course overview" "block"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
And I click on "Starred" "link" in the "Course overview" "block"
|
||||
Then I should see "Course 2" in the "Course overview" "block"
|
||||
And I should see "Course 2" in the "Course overview" "block"
|
||||
|
||||
Scenario: Test a course is hidden directly with "All" courses
|
||||
Given I am on the "My courses" page logged in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "All" "link" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
Then I should not see "Course 2" in the "Course overview" "block"
|
||||
@@ -111,17 +102,14 @@ Feature: The my overview block allows users to hide their courses
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
# We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except hidden)" element instead
|
||||
When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Remove from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
Then I should see "Course 2" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I should not see "Remove from view" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I should see "Restore to view" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Restore to view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
Then I should see "Course 2" in the "Course overview" "block"
|
||||
And I hover "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I should see "Course 2" in the "Course overview" "block"
|
||||
And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I should see "Remove from view" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I should not see "Restore to view" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
|
||||
@@ -22,9 +22,12 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
define('FLICKR_DEV_KEY', '4fddbdd7ff2376beec54d7f6afad425e');
|
||||
define('DEFAULT_NUMBER_OF_PHOTOS', 6);
|
||||
|
||||
require_once("{$CFG->libdir}/flickrclient.php");
|
||||
|
||||
class block_tag_flickr extends block_base {
|
||||
|
||||
function init() {
|
||||
@@ -102,7 +105,11 @@ class block_tag_flickr extends block_base {
|
||||
|
||||
$response = $this->fetch_request($request);
|
||||
|
||||
$search = unserialize($response);
|
||||
$search = @unserialize($response);
|
||||
if ($search === false && $search != serialize(false)) {
|
||||
// The response didn't appear to be anything serialized, exit...
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($search['photoset']['photo'] as $p){
|
||||
$p['owner'] = $search['photoset']['owner'];
|
||||
@@ -123,7 +130,11 @@ class block_tag_flickr extends block_base {
|
||||
|
||||
$response = $this->fetch_request($request);
|
||||
|
||||
$search = unserialize($response);
|
||||
$search = @unserialize($response);
|
||||
if ($search === false && $search != serialize(false)) {
|
||||
// The response didn't appear to be anything serialized, exit...
|
||||
return;
|
||||
}
|
||||
$photos = array_values($search['photos']['photo']);
|
||||
}
|
||||
|
||||
@@ -147,6 +158,10 @@ class block_tag_flickr extends block_base {
|
||||
|
||||
function fetch_request($request){
|
||||
$c = new curl(array('cache' => true, 'module_cache'=> 'tag_flickr'));
|
||||
// Set custom user agent as Flickr blocks our "MoodleBot" agent string.
|
||||
$c->setopt([
|
||||
'CURLOPT_USERAGENT' => flickr_client::user_agent(),
|
||||
]);
|
||||
|
||||
$response = $c->get($request);
|
||||
|
||||
|
||||
@@ -4,20 +4,36 @@ Feature: Adding and configuring Flickr block
|
||||
As a admin
|
||||
I need to add the Flickr block to the tags site page
|
||||
|
||||
@javascript
|
||||
Scenario: Adding Flickr block to the tags site page
|
||||
Given I log in as "admin"
|
||||
Background:
|
||||
# We need to create a user to use tag here because the tags site page only shows the tag that currently being used.
|
||||
Given the following "users" exist:
|
||||
| username | interests |
|
||||
| student1 | Cats |
|
||||
And I log in as "admin"
|
||||
And I turn editing mode on
|
||||
And the following config values are set as admin:
|
||||
| unaddableblocks | | theme_boost|
|
||||
And the following "blocks" exist:
|
||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
|
||||
| tag_flickr | System | 1 | tag-search | side-post |
|
||||
| tag_flickr | System | 1 | tag-index | side-post |
|
||||
# TODO MDL-57120 site "Tags" link not accessible without navigation block.
|
||||
And I add the "Navigation" block if not present
|
||||
And I click on "Site pages" "list_item" in the "Navigation" "block"
|
||||
And I click on "Tags" "link" in the "Navigation" "block"
|
||||
And I add the "Flickr" block
|
||||
And I configure the "Flickr" block
|
||||
|
||||
@javascript
|
||||
Scenario: Adding Flickr block to the tags site page
|
||||
Given "block_tag_flickr" "block" should exist
|
||||
When I configure the "Flickr" block
|
||||
Then I should see "Flickr block title"
|
||||
And I set the field "Flickr block title" to "The Flickr block header"
|
||||
And I press "Save changes"
|
||||
And "block_tag_flickr" "block" should exist
|
||||
Then "The Flickr block header" "block" should exist
|
||||
And "The Flickr block header" "block" should exist
|
||||
|
||||
@javascript
|
||||
Scenario: Adding Flickr block to a specific tag page
|
||||
Given I click on "Cats" "link"
|
||||
Then "block_tag_flickr" "block" should exist
|
||||
And ".flickr-photos" "css_element" should exist in the "block_tag_flickr" "block"
|
||||
|
||||
@@ -15,25 +15,18 @@ Feature: Block appearances
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | name | description | course | idnumber | section |
|
||||
| survey | Test survey name | Test survey description | C1 | survey1 | 1 |
|
||||
| book | Test book name | Test book description | C1 | book1 | 1 |
|
||||
| activity | name | description | course | idnumber | section | template |
|
||||
| survey | Test survey name | | C1 | survey1 | 1 | 4 |
|
||||
| book | Test book name | Test book description | C1 | book1 | 1 | |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage
|
||||
And I click on "Test survey name" "link" in the "region-main" "region"
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Survey type | ATTLS (20 item version) |
|
||||
And I press "Save and return to course"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I click on "Test book name" "link" in the "region-main" "region"
|
||||
And I set the following fields to these values:
|
||||
| Chapter title | Book title |
|
||||
| Content | Book content test test |
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
And I turn editing mode on
|
||||
And I add the "Comments" block
|
||||
And I configure the "Comments" block
|
||||
And I set the following fields to these values:
|
||||
|
||||
@@ -14,12 +14,11 @@ Feature: Block region moving
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | course | name | template |
|
||||
| survey | C1 | Test survey name | 4 |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Survey" to section "1" and I fill the form with:
|
||||
| Name | Test survey name |
|
||||
| Survey type | ATTLS (20 item version) |
|
||||
| Description | Test survey description |
|
||||
And I add a "Book" to section "1" and I fill the form with:
|
||||
| Name | Test book name |
|
||||
| Description | Test book description |
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
"icon": {
|
||||
"key": "icon",
|
||||
"component": "mod_assign",
|
||||
"alttext": "Assignment icon"
|
||||
"alttext": "Assignment icon",
|
||||
"iconurl": "#"
|
||||
},
|
||||
"overdue": false,
|
||||
"purpose": "assessment"
|
||||
@@ -54,7 +55,14 @@
|
||||
{{#userdate}} {{timesort}}, {{#str}} strftimetime24, core_langconfig {{/str}} {{/userdate}}
|
||||
</small>
|
||||
<div class="activityiconcontainer small {{purpose}} courseicon align-self-top align-self-center mx-3 mb-1 mb-sm-0 text-nowrap">
|
||||
{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}
|
||||
{{#icon}}
|
||||
{{#iconurl}}
|
||||
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon ">
|
||||
{{/iconurl}}
|
||||
{{^iconurl}}
|
||||
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}
|
||||
{{/iconurl}}
|
||||
{{/icon}}
|
||||
</div>
|
||||
<div class="event-name-container flex-grow-1 line-height-3 nowrap text-truncate">
|
||||
<div class="d-flex">
|
||||
@@ -71,7 +79,7 @@
|
||||
{{activitystr}}
|
||||
{{/courseview}}
|
||||
{{^courseview}}
|
||||
{{#course.fullnamedisplay}}{{{course.fullnamedisplay}}} · {{/course.fullnamedisplay}}{{activitystr}}
|
||||
{{activitystr}}{{#course.fullnamedisplay}} · {{{course.fullnamedisplay}}}{{/course.fullnamedisplay}}
|
||||
{{/courseview}}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -39,23 +39,23 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
When I click on "Next 7 days" "link" in the "Timeline" "block"
|
||||
Then "Test choice 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Choice closes" in the "Timeline" "block"
|
||||
And I should see "Choice closes · Course 2" in the "Timeline" "block"
|
||||
And "Test feedback 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Feedback closes" in the "Timeline" "block"
|
||||
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
|
||||
And "Test assign 2" "link" should exist in the "Timeline" "block"
|
||||
And "Test feedback 4" "link" should exist in the "Timeline" "block"
|
||||
And "Test choice 2" "link" should not exist in the "Timeline" "block"
|
||||
And "Test choice 3" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
|
||||
And "Test assign 1" "link" should not exist in the "Timeline" "block"
|
||||
And I should not see "Course 1 · Assignment is due" in the "Timeline" "block"
|
||||
And I should not see "Assignment is due · Course 1" in the "Timeline" "block"
|
||||
|
||||
Scenario: Overdue in date view
|
||||
Given I log in as "student1"
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
When I click on "Overdue" "link" in the "Timeline" "block"
|
||||
Then "Test assign 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 1 · Assignment is due" in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
|
||||
And "Test assign 2" "link" should exist in the "Timeline" "block"
|
||||
And "Test choice 2" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 1" "link" should not exist in the "Timeline" "block"
|
||||
@@ -69,20 +69,20 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
When I click on "All" "link" in the "Timeline" "block"
|
||||
Then "Test assign 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 1 · Assignment is due" in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
|
||||
And "Test assign 2" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Assignment is due" in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 2" in the "Timeline" "block"
|
||||
And "Test feedback 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Feedback closes" in the "Timeline" "block"
|
||||
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
|
||||
And "Test choice 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Choice closes" in the "Timeline" "block"
|
||||
And I should see "Choice closes · Course 2" in the "Timeline" "block"
|
||||
And "Test feedback 4" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Feedback closes" in the "Timeline" "block"
|
||||
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
|
||||
And "Test choice 2" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
|
||||
And I click on "Show more activities" "button"
|
||||
And "Test feedback 2" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 1 · Feedback closes" in the "Timeline" "block"
|
||||
And I should see "Feedback closes · Course 1" in the "Timeline" "block"
|
||||
And "Test choice 3" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Test assign 1" in the "Timeline" "block"
|
||||
And I should see "Test feedback 1" in the "Timeline" "block"
|
||||
@@ -97,20 +97,20 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
When I click on "All" "link" in the "Timeline" "block"
|
||||
And I reload the page
|
||||
Then "Test assign 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 1 · Assignment is due" in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
|
||||
And "Test assign 2" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Assignment is due" in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 2" in the "Timeline" "block"
|
||||
And "Test feedback 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Feedback closes" in the "Timeline" "block"
|
||||
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
|
||||
And "Test choice 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Choice closes" in the "Timeline" "block"
|
||||
And I should see "Choice closes · Course 2" in the "Timeline" "block"
|
||||
And "Test feedback 4" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Feedback closes" in the "Timeline" "block"
|
||||
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
|
||||
And I should not see "Test choice 2" in the "Timeline" "block"
|
||||
And I should not see "Test feedback 2" in the "Timeline" "block"
|
||||
And I click on "Show more activities" "button"
|
||||
And "Test feedback 2" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 1 · Feedback closes" in the "Timeline" "block"
|
||||
And I should see "Feedback closes · Course 1" in the "Timeline" "block"
|
||||
And I should see "Test assign 1" in the "Timeline" "block"
|
||||
And I should see "Test feedback 1" in the "Timeline" "block"
|
||||
And I should see "Test feedback 3" in the "Timeline" "block"
|
||||
@@ -124,9 +124,9 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
When I click on "Overdue" "link" in the "Timeline" "block"
|
||||
And I reload the page
|
||||
Then "Test assign 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 1 · Assignment is due" in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
|
||||
And "Test assign 2" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Course 2 · Assignment is due" in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 2" in the "Timeline" "block"
|
||||
And "Test feedback 1" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 4" "link" should not exist in the "Timeline" "block"
|
||||
|
||||
@@ -64,7 +64,7 @@ class blog_edit_external_form extends moodleform {
|
||||
$mform->setDefault('id', 0);
|
||||
|
||||
$mform->addElement('hidden', 'returnurl');
|
||||
$mform->setType('returnurl', PARAM_URL);
|
||||
$mform->setType('returnurl', PARAM_LOCALURL);
|
||||
$mform->setDefault('returnurl', 0);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -903,7 +903,7 @@ function blog_get_headers($courseid=null, $groupid=null, $userid=null, $tagid=nu
|
||||
if (!empty($search) && has_capability('moodle/blog:search', $sitecontext)) {
|
||||
$headers['filters']['search'] = $search;
|
||||
$blogurl->param('search', $search);
|
||||
$PAGE->navbar->add(get_string('searchterm', 'blog', $search), $blogurl->out());
|
||||
$PAGE->navbar->add(get_string('searchterm', 'blog', s($search)), $blogurl->out());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+4
@@ -440,6 +440,10 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
* @return int The number of keys successfully deleted.
|
||||
*/
|
||||
public function delete_many(array $keys) {
|
||||
// If there are no keys to delete, do nothing.
|
||||
if (!$keys) {
|
||||
return 0;
|
||||
}
|
||||
$count = $this->redis->hDel($this->hash, ...$keys);
|
||||
if ($this->definition->get_ttl()) {
|
||||
// When TTL is enabled, also remove the keys from the TTL list.
|
||||
|
||||
+59
-4
@@ -34,6 +34,7 @@ require_once(__DIR__.'/../lib.php');
|
||||
* define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');
|
||||
*
|
||||
* @package cachestore_redis
|
||||
* @covers \cachestore_redis
|
||||
* @copyright Copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
@@ -70,13 +71,28 @@ class store_test extends \cachestore_tests {
|
||||
* Creates the required cachestore for the tests to run against Redis.
|
||||
*
|
||||
* @param array $extraconfig Extra configuration options for Redis instance, if any
|
||||
* @param bool $ttl True to use a cache definition with TTL enabled
|
||||
* @return cachestore_redis
|
||||
*/
|
||||
protected function create_cachestore_redis(array $extraconfig = []): cachestore_redis {
|
||||
/** @var cache_definition $definition */
|
||||
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_redis', 'phpunit_test');
|
||||
protected function create_cachestore_redis(array $extraconfig = [], bool $ttl = false): cachestore_redis {
|
||||
if ($ttl) {
|
||||
/** @var cache_definition $definition */
|
||||
$definition = cache_definition::load('core/wibble', [
|
||||
'mode' => 1,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => true,
|
||||
'ttl' => 10,
|
||||
'component' => 'core',
|
||||
'area' => 'wibble',
|
||||
'selectedsharingoption' => 2,
|
||||
'userinputsharingkey' => '',
|
||||
'sharingoptions' => 15,
|
||||
]);
|
||||
} else {
|
||||
/** @var cache_definition $definition */
|
||||
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_redis', 'phpunit_test');
|
||||
}
|
||||
$configuration = array_merge(cachestore_redis::unit_test_configuration(), $extraconfig);
|
||||
|
||||
$store = new cachestore_redis('Test', $configuration);
|
||||
$store->initialise($definition);
|
||||
|
||||
@@ -165,4 +181,43 @@ class store_test extends \cachestore_tests {
|
||||
]);
|
||||
$this->assertEquals(111, $store->get_last_io_bytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for whether cache uses TTL or not.
|
||||
*
|
||||
* @return array Array with true and false options
|
||||
*/
|
||||
public static function ttl_or_not(): array {
|
||||
return [
|
||||
[false],
|
||||
[true]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the delete_many function.
|
||||
*
|
||||
* The behaviour is different with TTL enabled so we need to test with that kind of definition
|
||||
* as well as a 'normal' one.
|
||||
*
|
||||
* @param bool $ttl True to test using a TTL definition
|
||||
* @dataProvider ttl_or_not
|
||||
*/
|
||||
public function test_delete_many(bool $ttl): void {
|
||||
$store = $this->create_cachestore_redis([], $ttl);
|
||||
|
||||
// Check it works to delete selected items.
|
||||
$store->set('foo', 'frog');
|
||||
$store->set('bar', 'amphibian');
|
||||
$store->set('hmm', 'undead');
|
||||
$this->store->delete_many(['foo', 'bar']);
|
||||
$this->assertFalse($store->get('foo'));
|
||||
$this->assertFalse($store->get('bar'));
|
||||
$this->assertEquals('undead', $store->get('hmm'));
|
||||
|
||||
// If called with no keys it should do nothing.
|
||||
$store->delete_many([]);
|
||||
$this->assertEquals('undead', $store->get('hmm'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -5,6 +5,6 @@
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("core_calendar/crud",["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending"],(function($,Str,Notification,CustomEvents,Modal,ModalRegistry,ModalFactory,ModalEvents,ModalEventForm,CalendarRepository,CalendarEvents,ModalDelete,CalendarSelectors,Pending){return{registerRemove:function(root){root.on("click",CalendarSelectors.actions.remove,(function(e){var eventSource=$(this).closest(CalendarSelectors.eventItem);!function(eventId,eventTitle,eventCount){var deletePromise,pendingPromise=new Pending("core_calendar/crud:confirmDeletion"),deleteStrings=[{key:"deleteevent",component:"calendar"}],isRepeatedEvent=(eventCount=parseInt(eventCount,10))>1;isRepeatedEvent?(deleteStrings.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:eventTitle,count:eventCount}}),deletePromise=ModalFactory.create({type:ModalDelete.TYPE})):(deleteStrings.push({key:"confirmeventdelete",component:"calendar",param:eventTitle}),deletePromise=ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL}));var stringsPromise=Str.get_strings(deleteStrings);$.when(stringsPromise,deletePromise).then((function(strings,deleteModal){return deleteModal.setRemoveOnClose(!0),deleteModal.setTitle(strings[0]),deleteModal.setBody(strings[1]),isRepeatedEvent||deleteModal.setSaveButtonText(strings[0]),deleteModal.show(),deleteModal.getRoot().on(ModalEvents.save,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedevent");CalendarRepository.deleteEvent(eventId,!1).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!1])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal.getRoot().on(CalendarEvents.deleteAll,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedallevent");CalendarRepository.deleteEvent(eventId,!0).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!0])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)}(eventSource.data("eventId"),eventSource.data("eventTitle"),eventSource.data("eventCount")),e.preventDefault()}))},registerEditListeners:function(root,eventFormModalPromise){var pendingPromise=new Pending("core_calendar/crud:registerEditListeners");return eventFormModalPromise.then((function(modal){return $("body").on(CalendarEvents.editEvent,(function(e,eventId){var calendarWrapper=root.find(CalendarSelectors.wrapper);modal.setEventId(eventId),modal.setContextId(calendarWrapper.data("contextId")),modal.show(),e.stopImmediatePropagation()})),modal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)},registerEventFormModal:function(root){var eventFormPromise=ModalFactory.create({type:ModalEventForm.TYPE,large:!0});return root.on("click",CalendarSelectors.actions.create,(function(e){eventFormPromise.then((function(modal){var wrapper=root.find(CalendarSelectors.wrapper),categoryId=wrapper.data("categoryid");void 0!==categoryId&&modal.setCategoryId(categoryId);var today=root.find(CalendarSelectors.today),firstDay=root.find(CalendarSelectors.day);!today.length&&firstDay.length&&modal.setStartTime(firstDay.data("newEventTimestamp")),modal.setContextId(wrapper.data("contextId")),modal.setCourseId(wrapper.data("courseid")),modal.show()})).fail(Notification.exception),e.preventDefault()})),root.on("click",CalendarSelectors.actions.edit,(function(e){e.preventDefault();var target=$(e.currentTarget),calendarWrapper=target.closest(CalendarSelectors.wrapper),eventWrapper=target.closest(CalendarSelectors.eventItem);eventFormPromise.then((function(modal){modal.setEventId(eventWrapper.data("eventId")),modal.setContextId(calendarWrapper.data("contextId")),modal.setCourseId(eventWrapper.data("courseId")),modal.show(),e.stopImmediatePropagation()})).fail(Notification.exception)})),eventFormPromise}}}));
|
||||
define("core_calendar/crud",["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending"],(function($,Str,Notification,CustomEvents,Modal,ModalRegistry,ModalFactory,ModalEvents,ModalEventForm,CalendarRepository,CalendarEvents,ModalDelete,CalendarSelectors,Pending){return{registerRemove:function(root){root.on("click",CalendarSelectors.actions.remove,(function(e){var eventSource=$(this).closest(CalendarSelectors.eventItem);!function(eventId,eventTitle,eventCount){var deletePromise,pendingPromise=new Pending("core_calendar/crud:confirmDeletion"),deleteStrings=[{key:"deleteevent",component:"calendar"}],isRepeatedEvent=(eventCount=parseInt(eventCount,10))>1;isRepeatedEvent?(deleteStrings.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:eventTitle,count:eventCount}}),deletePromise=ModalFactory.create({type:ModalDelete.TYPE})):(deleteStrings.push({key:"confirmeventdelete",component:"calendar",param:eventTitle}),deletePromise=ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL}));var stringsPromise=Str.get_strings(deleteStrings);$.when(stringsPromise,deletePromise).then((function(strings,deleteModal){return deleteModal.setRemoveOnClose(!0),deleteModal.setTitle(strings[0]),deleteModal.setBody(strings[1]),isRepeatedEvent||deleteModal.setSaveButtonText(strings[0]),deleteModal.show(),deleteModal.getRoot().on(ModalEvents.save,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedevent");CalendarRepository.deleteEvent(eventId,!1).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!1])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal.getRoot().on(CalendarEvents.deleteAll,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedallevent");CalendarRepository.deleteEvent(eventId,!0).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!0])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)}(eventSource.data("eventId"),eventSource.data("eventTitle"),eventSource.data("eventCount")),e.preventDefault()}))},registerEditListeners:function(root,eventFormModalPromise){var pendingPromise=new Pending("core_calendar/crud:registerEditListeners");return eventFormModalPromise.then((function(modal){return $("body").on(CalendarEvents.editEvent,(function(e,eventId){var target=root.find("[data-event-id=".concat(eventId,"]")),calendarWrapper=root.find(CalendarSelectors.wrapper);modal.setEventId(eventId),modal.setContextId(calendarWrapper.data("contextId")),modal.setReturnElement(target),modal.show(),e.stopImmediatePropagation()})),modal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)},registerEventFormModal:function(root){var eventFormPromise=ModalFactory.create({type:ModalEventForm.TYPE,large:!0});return root.on("click",CalendarSelectors.actions.create,(function(e){eventFormPromise.then((function(modal){var wrapper=root.find(CalendarSelectors.wrapper),categoryId=wrapper.data("categoryid");void 0!==categoryId&&modal.setCategoryId(categoryId);var today=root.find(CalendarSelectors.today),firstDay=root.find(CalendarSelectors.day);!today.length&&firstDay.length&&modal.setStartTime(firstDay.data("newEventTimestamp")),modal.setContextId(wrapper.data("contextId")),modal.setCourseId(wrapper.data("courseid")),modal.show()})).fail(Notification.exception),e.preventDefault()})),root.on("click",CalendarSelectors.actions.edit,(function(e){e.preventDefault();var target=$(e.currentTarget),calendarWrapper=target.closest(CalendarSelectors.wrapper),eventWrapper=target.closest(CalendarSelectors.eventItem);eventFormPromise.then((function(modal){modal.setEventId(eventWrapper.data("eventId")),modal.setContextId(calendarWrapper.data("contextId")),modal.setCourseId(eventWrapper.data("courseId")),modal.show(),e.stopImmediatePropagation()})).fail(Notification.exception)})),eventFormPromise}}}));
|
||||
|
||||
//# sourceMappingURL=crud.min.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -5,6 +5,6 @@
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("core_calendar/event_form",["jquery","core_calendar/repository"],(function($,CalendarRepository){var SELECTORS_EVENT_GROUP_COURSE_ID='[name="groupcourseid"]',SELECTORS_EVENT_GROUP_ID='[name="groupid"]',SELECTORS_SELECT_OPTION="option",addCourseGroupSelectListeners=function(formElement){var courseGroupSelect=formElement.find(SELECTORS_EVENT_GROUP_COURSE_ID);courseGroupSelect.on("change",(function(){var courseId=formElement.find(SELECTORS_EVENT_GROUP_COURSE_ID).val();CalendarRepository.getCourseGroupsData(courseId).then((function(groups){return function(groups){var groupSelect=formElement.find(SELECTORS_EVENT_GROUP_ID),groupSelectOptions=groupSelect.find(SELECTORS_SELECT_OPTION),courseGroups=$(groups);groupSelectOptions.remove(),groupSelect.prop("disabled",!1),courseGroups.each((function(id,group){$(groupSelect).append($("<option></option>").attr("value",group.id).text(group.name))}))}(groups)})).catch(Notification.exception)}))};return{init:function(formId){var formElement=$("#"+formId);addCourseGroupSelectListeners(formElement)}}}));
|
||||
define("core_calendar/event_form",["jquery","core_calendar/repository","core/notification"],(function($,CalendarRepository,Notification){var SELECTORS_EVENT_GROUP_COURSE_ID='[name="groupcourseid"]',SELECTORS_EVENT_GROUP_ID='[name="groupid"]',SELECTORS_SELECT_OPTION="option",addCourseGroupSelectListeners=function(formElement){var courseGroupSelect=formElement.find(SELECTORS_EVENT_GROUP_COURSE_ID);courseGroupSelect.on("change",(function(){var courseId=formElement.find(SELECTORS_EVENT_GROUP_COURSE_ID).val();CalendarRepository.getCourseGroupsData(courseId).then((function(groups){return function(groups){var groupSelect=formElement.find(SELECTORS_EVENT_GROUP_ID),groupSelectOptions=groupSelect.find(SELECTORS_SELECT_OPTION),courseGroups=$(groups);groupSelectOptions.remove(),groupSelect.prop("disabled",!1),courseGroups.each((function(id,group){$(groupSelect).append($("<option></option>").attr("value",group.id).text(group.name))}))}(groups)})).catch(Notification.exception)}))};return{init:function(formId){var formElement=$("#"+formId);addCourseGroupSelectListeners(formElement)}}}));
|
||||
|
||||
//# sourceMappingURL=event_form.min.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"event_form.min.js","sources":["../src/event_form.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to enhance the event form.\n *\n * @module core_calendar/event_form\n * @copyright 2017 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core_calendar/repository'], function($, CalendarRepository) {\n\n var SELECTORS = {\n EVENT_GROUP_COURSE_ID: '[name=\"groupcourseid\"]',\n EVENT_GROUP_ID: '[name=\"groupid\"]',\n SELECT_OPTION: 'option'\n };\n\n /**\n * Listen for when the user changes the group course when configuring\n * a group event and filter the options in the group select to only\n * show the groups available within the course the user has selected.\n *\n * @method addCourseGroupSelectListeners\n * @param {object} formElement The root form element\n */\n var addCourseGroupSelectListeners = function(formElement) {\n var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID);\n\n var loadGroupSelectOptions = function(groups) {\n var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID),\n groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION),\n courseGroups = $(groups);\n\n // Let's clear all options first.\n groupSelectOptions.remove();\n groupSelect.prop(\"disabled\", false);\n courseGroups.each(function(id, group) {\n $(groupSelect).append($(\"<option></option>\").attr(\"value\", group.id).text(group.name));\n });\n };\n\n // If the user choose a course in the selector do a WS request to get groups.\n courseGroupSelect.on('change', function() {\n var courseId = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID).val();\n CalendarRepository.getCourseGroupsData(courseId)\n .then(function(groups) {\n return loadGroupSelectOptions(groups);\n })\n .catch(Notification.exception);\n });\n };\n\n /**\n * Initialise all of the form enhancements.\n *\n * @method init\n * @param {string} formId The value of the form's id attribute\n */\n var init = function(formId) {\n var formElement = $('#' + formId);\n addCourseGroupSelectListeners(formElement);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CalendarRepository","SELECTORS","addCourseGroupSelectListeners","formElement","courseGroupSelect","find","on","courseId","val","getCourseGroupsData","then","groups","groupSelect","groupSelectOptions","courseGroups","remove","prop","each","id","group","append","attr","text","name","loadGroupSelectOptions","catch","Notification","exception","init","formId"],"mappings":";;;;;;;AAsBAA,kCAAO,CAAC,SAAU,6BAA6B,SAASC,EAAGC,wBAEnDC,gCACuB,yBADvBA,yBAEgB,mBAFhBA,wBAGe,SAWfC,8BAAgC,SAASC,iBACrCC,kBAAoBD,YAAYE,KAAKJ,iCAgBzCG,kBAAkBE,GAAG,UAAU,eACvBC,SAAWJ,YAAYE,KAAKJ,iCAAiCO,MACjER,mBAAmBS,oBAAoBF,UAClCG,MAAK,SAASC,eAjBM,SAASA,YAC9BC,YAAcT,YAAYE,KAAKJ,0BAC/BY,mBAAqBD,YAAYP,KAAKJ,yBACtCa,aAAef,EAAEY,QAGrBE,mBAAmBE,SACnBH,YAAYI,KAAK,YAAY,GAC7BF,aAAaG,MAAK,SAASC,GAAIC,OAC3BpB,EAAEa,aAAaQ,OAAOrB,EAAE,qBAAqBsB,KAAK,QAASF,MAAMD,IAAII,KAAKH,MAAMI,UASrEC,CAAuBb,WAEjCc,MAAMC,aAAaC,qBAezB,CACHC,KANO,SAASC,YACZ1B,YAAcJ,EAAE,IAAM8B,QAC1B3B,8BAA8BC"}
|
||||
{"version":3,"file":"event_form.min.js","sources":["../src/event_form.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to enhance the event form.\n *\n * @module core_calendar/event_form\n * @copyright 2017 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core_calendar/repository', 'core/notification'], function($, CalendarRepository, Notification) {\n\n var SELECTORS = {\n EVENT_GROUP_COURSE_ID: '[name=\"groupcourseid\"]',\n EVENT_GROUP_ID: '[name=\"groupid\"]',\n SELECT_OPTION: 'option'\n };\n\n /**\n * Listen for when the user changes the group course when configuring\n * a group event and filter the options in the group select to only\n * show the groups available within the course the user has selected.\n *\n * @method addCourseGroupSelectListeners\n * @param {object} formElement The root form element\n */\n var addCourseGroupSelectListeners = function(formElement) {\n var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID);\n\n var loadGroupSelectOptions = function(groups) {\n var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID),\n groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION),\n courseGroups = $(groups);\n\n // Let's clear all options first.\n groupSelectOptions.remove();\n groupSelect.prop(\"disabled\", false);\n courseGroups.each(function(id, group) {\n $(groupSelect).append($(\"<option></option>\").attr(\"value\", group.id).text(group.name));\n });\n };\n\n // If the user choose a course in the selector do a WS request to get groups.\n courseGroupSelect.on('change', function() {\n var courseId = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID).val();\n CalendarRepository.getCourseGroupsData(courseId)\n .then(function(groups) {\n return loadGroupSelectOptions(groups);\n })\n .catch(Notification.exception);\n });\n };\n\n /**\n * Initialise all of the form enhancements.\n *\n * @method init\n * @param {string} formId The value of the form's id attribute\n */\n var init = function(formId) {\n var formElement = $('#' + formId);\n addCourseGroupSelectListeners(formElement);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CalendarRepository","Notification","SELECTORS","addCourseGroupSelectListeners","formElement","courseGroupSelect","find","on","courseId","val","getCourseGroupsData","then","groups","groupSelect","groupSelectOptions","courseGroups","remove","prop","each","id","group","append","attr","text","name","loadGroupSelectOptions","catch","exception","init","formId"],"mappings":";;;;;;;AAsBAA,kCAAO,CAAC,SAAU,2BAA4B,sBAAsB,SAASC,EAAGC,mBAAoBC,kBAE5FC,gCACuB,yBADvBA,yBAEgB,mBAFhBA,wBAGe,SAWfC,8BAAgC,SAASC,iBACrCC,kBAAoBD,YAAYE,KAAKJ,iCAgBzCG,kBAAkBE,GAAG,UAAU,eACvBC,SAAWJ,YAAYE,KAAKJ,iCAAiCO,MACjET,mBAAmBU,oBAAoBF,UAClCG,MAAK,SAASC,eAjBM,SAASA,YAC9BC,YAAcT,YAAYE,KAAKJ,0BAC/BY,mBAAqBD,YAAYP,KAAKJ,yBACtCa,aAAehB,EAAEa,QAGrBE,mBAAmBE,SACnBH,YAAYI,KAAK,YAAY,GAC7BF,aAAaG,MAAK,SAASC,GAAIC,OAC3BrB,EAAEc,aAAaQ,OAAOtB,EAAE,qBAAqBuB,KAAK,QAASF,MAAMD,IAAII,KAAKH,MAAMI,UASrEC,CAAuBb,WAEjCc,MAAMzB,aAAa0B,qBAezB,CACHC,KANO,SAASC,YACZzB,YAAcL,EAAE,IAAM8B,QAC1B1B,8BAA8BC"}
|
||||
@@ -246,9 +246,12 @@ function(
|
||||
// When something within the calendar tells us the user wants
|
||||
// to edit an event then show the event form modal.
|
||||
$('body').on(CalendarEvents.editEvent, function(e, eventId) {
|
||||
var calendarWrapper = root.find(CalendarSelectors.wrapper);
|
||||
var target = root.find(`[data-event-id=${eventId}]`),
|
||||
calendarWrapper = root.find(CalendarSelectors.wrapper);
|
||||
|
||||
modal.setEventId(eventId);
|
||||
modal.setContextId(calendarWrapper.data('contextId'));
|
||||
modal.setReturnElement(target);
|
||||
modal.show();
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core_calendar/repository'], function($, CalendarRepository) {
|
||||
define(['jquery', 'core_calendar/repository', 'core/notification'], function($, CalendarRepository, Notification) {
|
||||
|
||||
var SELECTORS = {
|
||||
EVENT_GROUP_COURSE_ID: '[name="groupcourseid"]',
|
||||
|
||||
@@ -61,11 +61,13 @@ class event_icon_exporter extends exporter {
|
||||
$iscourseevent = ($course && !empty($courseid) && $courseid != SITEID && empty($groupid));
|
||||
$isgroupevent = ($group && !empty($groupid));
|
||||
$isuserevent = ($user && !empty($userid));
|
||||
$iconurl = '';
|
||||
|
||||
if ($isactivityevent) {
|
||||
$key = 'monologo';
|
||||
$component = $coursemodule->get('modname');
|
||||
|
||||
$iconurl = get_fast_modinfo($courseid)->get_cm($coursemodule->get('id'))->get_icon_url()->out(false);
|
||||
if (get_string_manager()->string_exists($event->get_type(), $component)) {
|
||||
$alttext = get_string($event->get_type(), $component);
|
||||
} else {
|
||||
@@ -118,6 +120,7 @@ class event_icon_exporter extends exporter {
|
||||
$data->key = $key;
|
||||
$data->component = $component;
|
||||
$data->alttext = $alttext;
|
||||
$data->iconurl = $iconurl;
|
||||
|
||||
parent::__construct($data, $related);
|
||||
}
|
||||
@@ -132,6 +135,7 @@ class event_icon_exporter extends exporter {
|
||||
'key' => ['type' => PARAM_TEXT],
|
||||
'component' => ['type' => PARAM_TEXT],
|
||||
'alttext' => ['type' => PARAM_TEXT],
|
||||
'iconurl' => ['type' => PARAM_TEXT],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Calendar export
|
||||
*
|
||||
* @package core_calendar
|
||||
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('NO_MOODLE_COOKIES', true);
|
||||
|
||||
require_once('../config.php');
|
||||
//require_once($CFG->dirroot.'/course/lib.php');
|
||||
require_once($CFG->dirroot.'/calendar/lib.php');
|
||||
require_once($CFG->libdir.'/bennu/bennu.inc.php');
|
||||
|
||||
@@ -16,23 +39,28 @@ if (empty($CFG->enablecalendarexport)) {
|
||||
die('no export');
|
||||
}
|
||||
|
||||
//Fetch user information
|
||||
$checkuserid = !empty($userid) && $user = $DB->get_record('user', array('id' => $userid), 'id,password');
|
||||
//allowing for fallback check of old url - MDL-27542
|
||||
$checkusername = !empty($username) && $user = $DB->get_record('user', array('username' => $username), 'id,password');
|
||||
// Fetch basic user information to correctly log the user.
|
||||
$fields = 'id,username,password,firstname,lastname';
|
||||
|
||||
$checkuserid = !empty($userid) && $user = $DB->get_record('user', array('id' => $userid), $fields);
|
||||
// Allowing for fallback check of old url - MDL-27542.
|
||||
$checkusername = !empty($username) && $user = $DB->get_record('user', array('username' => $username), $fields);
|
||||
if ((!$checkuserid && !$checkusername) || !$user) {
|
||||
//No such user
|
||||
die('Invalid authentication');
|
||||
}
|
||||
|
||||
//Check authentication token
|
||||
// Check authentication token.
|
||||
$authuserid = !empty($userid) && $authtoken == calendar_get_export_token($user);
|
||||
//allowing for fallback check of old url - MDL-27542
|
||||
// Allowing for fallback check of old url - MDL-27542.
|
||||
$authusername = !empty($username) && $authtoken == sha1($username . $user->password . $CFG->calendar_exportsalt);
|
||||
if (!$authuserid && !$authusername) {
|
||||
die('Invalid authentication');
|
||||
}
|
||||
|
||||
// Setup up the user including web access logging.
|
||||
\core\session\manager::set_user($user);
|
||||
|
||||
$PAGE->set_context(context_system::instance());
|
||||
|
||||
// Get the calendar type we are using.
|
||||
|
||||
+14
-3
@@ -2360,7 +2360,8 @@ function calendar_edit_event_allowed($event, $manualedit = false) {
|
||||
return has_capability('moodle/calendar:manageentries', $event->context);
|
||||
} else if (!empty($event->userid) && $event->userid == $USER->id) {
|
||||
// If course is not set, but userid id set, it's a user event.
|
||||
return (has_capability('moodle/calendar:manageownentries', $event->context));
|
||||
return (has_capability('moodle/calendar:manageownentries',
|
||||
context_user::instance($event->userid)));
|
||||
} else if (!empty($event->userid)) {
|
||||
return calendar_can_manage_user_event($event);
|
||||
}
|
||||
@@ -3072,11 +3073,21 @@ function calendar_import_events_from_ical(iCalendar $ical, int $subscriptionid =
|
||||
\core_php_time_limit::raise(300);
|
||||
}
|
||||
|
||||
// Start with a safe default timezone.
|
||||
$timezone = 'UTC';
|
||||
|
||||
// Grab the timezone from the iCalendar file to be used later.
|
||||
if (isset($ical->properties['X-WR-TIMEZONE'][0]->value)) {
|
||||
$timezone = $ical->properties['X-WR-TIMEZONE'][0]->value;
|
||||
} else {
|
||||
$timezone = 'UTC';
|
||||
|
||||
} else if (isset($ical->properties['PRODID'][0]->value)) {
|
||||
// If the timezone was not found, check to se if this is MS exchange / Office 365 which uses Windows timezones.
|
||||
if (strncmp($ical->properties['PRODID'][0]->value, 'Microsoft', 9) == 0) {
|
||||
if (isset($ical->components['VTIMEZONE'][0]->properties['TZID'][0]->value)) {
|
||||
$tzname = $ical->components['VTIMEZONE'][0]->properties['TZID'][0]->value;
|
||||
$timezone = IntlTimeZone::getIDForWindowsID($tzname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$icaluuids = [];
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
"icon": {
|
||||
"key": "i/courseevent",
|
||||
"component": "core",
|
||||
"alttext": "Some course event"
|
||||
"alttext": "Some course event",
|
||||
"iconurl": "#"
|
||||
},
|
||||
"editurl": "#",
|
||||
"url": "#"
|
||||
|
||||
@@ -45,7 +45,14 @@
|
||||
}} data-region="event-item"{{!
|
||||
}}>
|
||||
<div class="activityiconcontainer small {{{purpose}}} courseicon mr-3">
|
||||
{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}
|
||||
{{#icon}}
|
||||
{{#iconurl}}
|
||||
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon ">
|
||||
{{/iconurl}}
|
||||
{{^iconurl}}
|
||||
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}
|
||||
{{/iconurl}}
|
||||
{{/icon}}
|
||||
</div>
|
||||
<div class="overflow-auto">
|
||||
<h6 class="d-flex mb-1">
|
||||
|
||||
@@ -334,3 +334,96 @@ Feature: Perform basic calendar functionality
|
||||
And I press "Save"
|
||||
And I click on "Category event" "link"
|
||||
And I should see "Category event"
|
||||
|
||||
@javascript
|
||||
Scenario: Changing the event type from user to anything else should work
|
||||
Given I log in as "teacher1"
|
||||
# We need this so we can see the groups.
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
# We need this so we can make a category event.
|
||||
And the following "categories" exist:
|
||||
| name | category | idnumber |
|
||||
| CatA | 0 | cata |
|
||||
And the following "role assigns" exist:
|
||||
| user | role | contextlevel | reference |
|
||||
| teacher1 | manager | Category | cata |
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Full calendar"
|
||||
And I set the field "course" to "C1"
|
||||
And I press "New event"
|
||||
And I set the following fields to these values:
|
||||
| Event title | type change test event |
|
||||
| Type of event | User |
|
||||
And I press "Save"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Full calendar"
|
||||
And I click on "type change test event" "link"
|
||||
And I should see "User event"
|
||||
When I click on "Edit" "button"
|
||||
And I set the following fields to these values:
|
||||
| Event title | type change test event |
|
||||
| Type of event | Course |
|
||||
And I expand the "Course" autocomplete
|
||||
And I click on "Course 1" item in the autocomplete list
|
||||
And I press "Save"
|
||||
And I click on "type change test event" "link"
|
||||
Then I should see "Course event"
|
||||
# Reset to user event
|
||||
And I click on "Edit" "button"
|
||||
And I set the following fields to these values:
|
||||
| Event title | type change test event |
|
||||
| Type of event | User |
|
||||
And I press "Save"
|
||||
And I click on "type change test event" "link"
|
||||
And I should see "User event"
|
||||
# Now test changing from user to group event.
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Full calendar"
|
||||
And I click on "type change test event" "link"
|
||||
And I click on "Edit" "button"
|
||||
And I set the following fields to these values:
|
||||
| Event title | type change test event |
|
||||
| Type of event | Group |
|
||||
And I click on "#fitem_id_groupcourseid .form-autocomplete-downarrow" "css_element"
|
||||
And I click on "Course 1" item in the autocomplete list
|
||||
And I set the following fields to these values:
|
||||
| Group | Group 1 |
|
||||
And I press "Save"
|
||||
And I click on "type change test event" "link"
|
||||
And I should see "Group event"
|
||||
# Reset to user event
|
||||
And I click on "Edit" "button"
|
||||
And I set the following fields to these values:
|
||||
| Event title | type change test event |
|
||||
| Type of event | User |
|
||||
And I press "Save"
|
||||
And I click on "type change test event" "link"
|
||||
And I should see "User event"
|
||||
# Now test changing from user to course event.
|
||||
And I click on "Edit" "button"
|
||||
And I set the following fields to these values:
|
||||
| Event title | Course 1 event |
|
||||
| Type of event | Course |
|
||||
And I expand the "Course" autocomplete
|
||||
And I click on "Course 1" item in the autocomplete list
|
||||
And I press "Save"
|
||||
And I click on "Course 1 event" "link"
|
||||
And I should see "Course event"
|
||||
# Reset to user event
|
||||
And I click on "Edit" "button"
|
||||
And I set the following fields to these values:
|
||||
| Event title | type change test event |
|
||||
| Type of event | User |
|
||||
And I press "Save"
|
||||
And I click on "type change test event" "link"
|
||||
And I should see "User event"
|
||||
# Now test changing from user to category event.
|
||||
And I click on "Edit" "button"
|
||||
And I set the following fields to these values:
|
||||
| Event title | type change test event |
|
||||
| Type of event | Category |
|
||||
And I press "Save"
|
||||
And I click on "type change test event" "link"
|
||||
And I should see "Category event"
|
||||
|
||||
@@ -83,15 +83,15 @@ Feature: Manage custom reports for cohorts
|
||||
| My report | user:lastname |
|
||||
And I change window size to "large"
|
||||
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
|
||||
And I set the field "Rename column 'Surname'" to "Member count"
|
||||
And I set the "Surname" column aggregation to "Count"
|
||||
And I set the field "Rename column 'Last name'" to "Member count"
|
||||
And I set the "Last name" column aggregation to "Count"
|
||||
And I click on "Show/hide 'Sorting'" "button"
|
||||
And I click on "Move sorting for column 'Surname'" "button"
|
||||
And I click on "To the top of the list" "link" in the "Move sorting for column 'Surname'" "dialogue"
|
||||
And I click on "Enable sorting for column 'Surname'" "checkbox"
|
||||
And I click on "Move sorting for column 'Last name'" "button"
|
||||
And I click on "To the top of the list" "link" in the "Move sorting for column 'Last name'" "dialogue"
|
||||
And I click on "Enable initial sorting for column Last name" "checkbox"
|
||||
# "New system cohort" has fewer members than "Another one" cohort.
|
||||
And "New system cohort" "table_row" should appear before "Another one" "table_row"
|
||||
When I click on "Sort column 'Surname' descending" "button"
|
||||
Then I should see "Updated sorting for column 'Surname'"
|
||||
When I click on "Sort column 'Last name' descending" "button"
|
||||
Then I should see "Updated sorting for column 'Last name'"
|
||||
# Switching sort direction should now show "Another one" cohort ahead of "New system cohort".
|
||||
And "Another one" "table_row" should appear before "New system cohort" "table_row"
|
||||
|
||||
@@ -135,7 +135,7 @@ Feature: Allow to mark course as completed without cron for activity completion
|
||||
And I am on "Completion course" course homepage
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I follow "Single view"
|
||||
And I select "Student First" from the "Select user..." singleselect
|
||||
And I select "Student First" from the "Select a user above to view all their grades" singleselect
|
||||
And I set the field "Override for Test assignment name" to "1"
|
||||
When I set the following fields to these values:
|
||||
| Grade for Test assignment name | 10.00 |
|
||||
|
||||
@@ -43,7 +43,7 @@ Feature: Restrict activity availability through grade conditions
|
||||
And I log out
|
||||
|
||||
When I am on the "Course 1" course page logged in as student1
|
||||
Then I should see "Not available unless: You achieve a required score in Grade assignment"
|
||||
Then I should see "Not available unless: You achieve higher than a certain score in Grade assignment"
|
||||
And I should see "Test page name"
|
||||
And "Test page name" "link" should not exist in the "region-main" "region"
|
||||
And I am on the "Grade assignment" "assign activity" page
|
||||
@@ -65,4 +65,4 @@ Feature: Restrict activity availability through grade conditions
|
||||
|
||||
And I am on the "Course 1" course page logged in as student1
|
||||
And "Test page name" activity should be visible
|
||||
And I should not see "Not available unless: You achieve a required score in Grade assignment"
|
||||
And I should not see "Not available unless: You achieve higher than a certain score in Grade assignment"
|
||||
|
||||
@@ -78,7 +78,7 @@ Feature: Restrict sections availability through completion or grade conditions
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
Then I should see "Not available unless: You achieve a required score in Grade assignment"
|
||||
Then I should see "Not available unless: You achieve higher than a certain score in Grade assignment"
|
||||
And "Test page name" activity should be hidden
|
||||
And I am on the "Grade assignment" "assign activity" page
|
||||
And I press "Add submission"
|
||||
@@ -97,4 +97,4 @@ Feature: Restrict sections availability through completion or grade conditions
|
||||
And I log out
|
||||
And I am on the "Course 1" Course page logged in as student1
|
||||
And "Test page name" activity should be visible
|
||||
And I should not see "Not available unless: You achieve a required score in Grade assignment"
|
||||
And I should not see "Not available unless: You achieve higher than a certain score in Grade assignment"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
"behat/mink-goutte-driver": "^1.3",
|
||||
"symfony/process": "^4.0 || ^5.0",
|
||||
"behat/behat": "3.11.*",
|
||||
"oleg-andreyev/mink-phpwebdriver": "^1.0.1"
|
||||
"oleg-andreyev/mink-phpwebdriver": "^1.2.1"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-0": {
|
||||
|
||||
Generated
+139
-134
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "981107a64c8c10d5afa79fd05b49af17",
|
||||
"content-hash": "7897c25160d1ab3b2ad956387eabccf6",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
@@ -1005,16 +1005,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.15.1",
|
||||
"version": "v4.15.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900"
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
|
||||
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1055,22 +1055,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
|
||||
},
|
||||
"time": "2022-09-04T07:30:47+00:00"
|
||||
"time": "2022-11-12T15:38:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "oleg-andreyev/mink-phpwebdriver",
|
||||
"version": "v1.0.1",
|
||||
"version": "v1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/oleg-andreyev/MinkPhpWebDriver.git",
|
||||
"reference": "66207ad04b45ad4348dd68ab2a918da2875ae46f"
|
||||
"reference": "e265917faf79b649f4e5d4419325e02c096caec2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/oleg-andreyev/MinkPhpWebDriver/zipball/66207ad04b45ad4348dd68ab2a918da2875ae46f",
|
||||
"reference": "66207ad04b45ad4348dd68ab2a918da2875ae46f",
|
||||
"url": "https://api.github.com/repos/oleg-andreyev/MinkPhpWebDriver/zipball/e265917faf79b649f4e5d4419325e02c096caec2",
|
||||
"reference": "e265917faf79b649f4e5d4419325e02c096caec2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1079,11 +1079,12 @@
|
||||
"php-webdriver/webdriver": "^1.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8",
|
||||
"behat/mink-extension": "^2.3",
|
||||
"ext-json": "*",
|
||||
"mink/driver-testsuite": "dev-integration-branch",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"symfony/phpunit-bridge": "^5.2"
|
||||
"symfony/phpunit-bridge": "^4.4 || ^5.4 || ^6.0"
|
||||
},
|
||||
"type": "mink-driver",
|
||||
"extra": {
|
||||
@@ -1119,9 +1120,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/oleg-andreyev/MinkPhpWebDriver/issues",
|
||||
"source": "https://github.com/oleg-andreyev/MinkPhpWebDriver/tree/v1.0.1"
|
||||
"source": "https://github.com/oleg-andreyev/MinkPhpWebDriver/tree/v1.2.1"
|
||||
},
|
||||
"time": "2021-01-30T23:07:29+00:00"
|
||||
"time": "2022-11-21T15:35:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@@ -1236,16 +1237,16 @@
|
||||
},
|
||||
{
|
||||
"name": "php-webdriver/webdriver",
|
||||
"version": "1.12.1",
|
||||
"version": "1.13.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-webdriver/php-webdriver.git",
|
||||
"reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a"
|
||||
"reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/b27ddf458d273c7d4602106fcaf978aa0b7fe15a",
|
||||
"reference": "b27ddf458d273c7d4602106fcaf978aa0b7fe15a",
|
||||
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/6dfe5f814b796c1b5748850aa19f781b9274c36c",
|
||||
"reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1295,22 +1296,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
|
||||
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.12.1"
|
||||
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.13.1"
|
||||
},
|
||||
"time": "2022-05-03T12:16:34+00:00"
|
||||
"time": "2022-10-11T11:49:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.17",
|
||||
"version": "9.2.23",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8"
|
||||
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8",
|
||||
"reference": "aa94dc41e8661fe90c7316849907cba3007b10d8",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
|
||||
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1366,7 +1367,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1374,7 +1375,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-30T12:24:04+00:00"
|
||||
"time": "2022-12-28T12:41:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
@@ -1619,16 +1620,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.5.24",
|
||||
"version": "9.5.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5"
|
||||
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
|
||||
"reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2",
|
||||
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1650,14 +1651,14 @@
|
||||
"phpunit/php-timer": "^5.0.2",
|
||||
"sebastian/cli-parser": "^1.0.1",
|
||||
"sebastian/code-unit": "^1.0.6",
|
||||
"sebastian/comparator": "^4.0.5",
|
||||
"sebastian/comparator": "^4.0.8",
|
||||
"sebastian/diff": "^4.0.3",
|
||||
"sebastian/environment": "^5.1.3",
|
||||
"sebastian/exporter": "^4.0.3",
|
||||
"sebastian/exporter": "^4.0.5",
|
||||
"sebastian/global-state": "^5.0.1",
|
||||
"sebastian/object-enumerator": "^4.0.3",
|
||||
"sebastian/resource-operations": "^3.0.3",
|
||||
"sebastian/type": "^3.1",
|
||||
"sebastian/type": "^3.2",
|
||||
"sebastian/version": "^3.0.2"
|
||||
},
|
||||
"suggest": {
|
||||
@@ -1701,7 +1702,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1711,9 +1712,13 @@
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-30T07:42:16+00:00"
|
||||
"time": "2022-10-28T06:00:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -3027,16 +3032,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v5.4.12",
|
||||
"version": "v5.4.15",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1"
|
||||
"reference": "ea59bb0edfaf9f28d18d8791410ee0355f317669"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1",
|
||||
"reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/ea59bb0edfaf9f28d18d8791410ee0355f317669",
|
||||
"reference": "ea59bb0edfaf9f28d18d8791410ee0355f317669",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3106,7 +3111,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v5.4.12"
|
||||
"source": "https://github.com/symfony/console/tree/v5.4.15"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3122,7 +3127,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-17T13:18:05+00:00"
|
||||
"time": "2022-10-26T21:41:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
@@ -3192,16 +3197,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/dependency-injection",
|
||||
"version": "v5.4.11",
|
||||
"version": "v5.4.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dependency-injection.git",
|
||||
"reference": "a8b9251016e9476db73e25fa836904bc0bf74c62"
|
||||
"reference": "24cf522668845391c0542bc1de496366072a6d0e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a8b9251016e9476db73e25fa836904bc0bf74c62",
|
||||
"reference": "a8b9251016e9476db73e25fa836904bc0bf74c62",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/24cf522668845391c0542bc1de496366072a6d0e",
|
||||
"reference": "24cf522668845391c0542bc1de496366072a6d0e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3261,7 +3266,7 @@
|
||||
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dependency-injection/tree/v5.4.11"
|
||||
"source": "https://github.com/symfony/dependency-injection/tree/v5.4.13"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3277,7 +3282,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-07-20T13:00:38+00:00"
|
||||
"time": "2022-08-30T19:10:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -3586,16 +3591,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v5.4.12",
|
||||
"version": "v5.4.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "2d67c1f9a1937406a9be3171b4b22250c0a11447"
|
||||
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/2d67c1f9a1937406a9be3171b4b22250c0a11447",
|
||||
"reference": "2d67c1f9a1937406a9be3171b4b22250c0a11447",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51",
|
||||
"reference": "ac09569844a9109a5966b9438fc29113ce77cf51",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3630,7 +3635,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v5.4.12"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v5.4.13"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3646,20 +3651,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-02T13:48:16+00:00"
|
||||
"time": "2022-09-21T19:53:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
||||
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3674,7 +3679,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -3712,7 +3717,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3728,20 +3733,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
"reference": "433d05519ce6990bf3530fba6957499d327395c2"
|
||||
"reference": "511a08c03c1960e08a883f4cffcacd219b758354"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
|
||||
"reference": "433d05519ce6990bf3530fba6957499d327395c2",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354",
|
||||
"reference": "511a08c03c1960e08a883f4cffcacd219b758354",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3753,7 +3758,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -3793,7 +3798,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3809,20 +3814,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
"reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8"
|
||||
"reference": "639084e360537a19f9ee352433b84ce831f3d2da"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
|
||||
"reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da",
|
||||
"reference": "639084e360537a19f9ee352433b84ce831f3d2da",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3836,7 +3841,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -3880,7 +3885,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3896,20 +3901,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
"reference": "219aa369ceff116e673852dce47c3a41794c14bd"
|
||||
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
|
||||
"reference": "219aa369ceff116e673852dce47c3a41794c14bd",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
|
||||
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3921,7 +3926,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -3964,7 +3969,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3980,20 +3985,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
||||
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4008,7 +4013,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -4047,7 +4052,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4063,20 +4068,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php72",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php72.git",
|
||||
"reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2"
|
||||
"reference": "869329b1e9894268a8a61dabb69153029b7a8c97"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2",
|
||||
"reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97",
|
||||
"reference": "869329b1e9894268a8a61dabb69153029b7a8c97",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4085,7 +4090,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -4123,7 +4128,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4139,20 +4144,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php73",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php73.git",
|
||||
"reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85"
|
||||
"reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85",
|
||||
"reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
|
||||
"reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4161,7 +4166,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -4202,7 +4207,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4218,20 +4223,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4240,7 +4245,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -4285,7 +4290,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4301,20 +4306,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-10T07:21:04+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||
"reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1"
|
||||
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1",
|
||||
"reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a",
|
||||
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4323,7 +4328,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
@@ -4364,7 +4369,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4380,7 +4385,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-24T11:49:31+00:00"
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
@@ -4529,16 +4534,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v5.4.12",
|
||||
"version": "v5.4.15",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "2fc515e512d721bf31ea76bd02fe23ada4640058"
|
||||
"reference": "571334ce9f687e3e6af72db4d3b2a9431e4fd9ed"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/2fc515e512d721bf31ea76bd02fe23ada4640058",
|
||||
"reference": "2fc515e512d721bf31ea76bd02fe23ada4640058",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/571334ce9f687e3e6af72db4d3b2a9431e4fd9ed",
|
||||
"reference": "571334ce9f687e3e6af72db4d3b2a9431e4fd9ed",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4595,7 +4600,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v5.4.12"
|
||||
"source": "https://github.com/symfony/string/tree/v5.4.15"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4611,20 +4616,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-12T17:03:11+00:00"
|
||||
"time": "2022-10-05T15:16:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v5.4.12",
|
||||
"version": "v5.4.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "42ecc77eb4f229ce2df702a648ec93b8478d76ae"
|
||||
"reference": "f0ed07675863aa6e3939df8b1bc879450b585cab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/42ecc77eb4f229ce2df702a648ec93b8478d76ae",
|
||||
"reference": "42ecc77eb4f229ce2df702a648ec93b8478d76ae",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/f0ed07675863aa6e3939df8b1bc879450b585cab",
|
||||
"reference": "f0ed07675863aa6e3939df8b1bc879450b585cab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4692,7 +4697,7 @@
|
||||
"description": "Provides tools to internationalize your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/translation/tree/v5.4.12"
|
||||
"source": "https://github.com/symfony/translation/tree/v5.4.14"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4708,7 +4713,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-02T15:52:22+00:00"
|
||||
"time": "2022-10-07T08:01:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation-contracts",
|
||||
@@ -4790,16 +4795,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v5.4.12",
|
||||
"version": "v5.4.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c"
|
||||
"reference": "e83fe9a72011f07c662da46a05603d66deeeb487"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c",
|
||||
"reference": "7a3aa21ac8ab1a96cc6de5bbcab4bc9fc943b18c",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/e83fe9a72011f07c662da46a05603d66deeeb487",
|
||||
"reference": "e83fe9a72011f07c662da46a05603d66deeeb487",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4845,7 +4850,7 @@
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v5.4.12"
|
||||
"source": "https://github.com/symfony/yaml/tree/v5.4.14"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4861,7 +4866,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-02T15:52:22+00:00"
|
||||
"time": "2022-10-03T15:15:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -582,11 +582,11 @@ define(
|
||||
if (action === 'hide' || action === 'show') {
|
||||
if (action === 'hide') {
|
||||
sectionElement.addClass('hidden');
|
||||
setSectionBadge(sectionElement[0], 'hiddenfromstudents', true);
|
||||
setSectionBadge(sectionElement[0], 'hiddenfromstudents', true, false);
|
||||
replaceActionItem(actionItem, 'i/show',
|
||||
'showfromothers', 'format_' + courseformat, 'show');
|
||||
} else {
|
||||
setSectionBadge(sectionElement[0], 'hiddenfromstudents', false);
|
||||
setSectionBadge(sectionElement[0], 'hiddenfromstudents', false, false);
|
||||
sectionElement.removeClass('hidden');
|
||||
replaceActionItem(actionItem, 'i/hide',
|
||||
'hidefromothers', 'format_' + courseformat, 'hide');
|
||||
@@ -616,13 +616,13 @@ define(
|
||||
replaceActionItem(actionItem, 'i/marked',
|
||||
'highlightoff', 'core', 'removemarker');
|
||||
courseeditor.dispatch('legacySectionAction', action, sectionid);
|
||||
setSectionBadge(sectionElement[0], 'iscurrent', true);
|
||||
setSectionBadge(sectionElement[0], 'iscurrent', true, true);
|
||||
} else if (action === 'removemarker') {
|
||||
sectionElement.removeClass('current');
|
||||
replaceActionItem(actionItem, 'i/marker',
|
||||
'highlight', 'core', 'setmarker');
|
||||
courseeditor.dispatch('legacySectionAction', action, sectionid);
|
||||
setSectionBadge(sectionElement[0], 'iscurrent', false);
|
||||
setSectionBadge(sectionElement[0], 'iscurrent', false, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -733,8 +733,9 @@ define(
|
||||
* @param {JQuery} sectionElement section element we perform action on
|
||||
* @param {String} badgetype the type of badge this is for
|
||||
* @param {bool} add true to add, false to remove
|
||||
* @param {boolean} removeOther in case of adding a badge, whether to remove all other.
|
||||
*/
|
||||
var setSectionBadge = function(sectionElement, badgetype, add) {
|
||||
var setSectionBadge = function(sectionElement, badgetype, add, removeOther) {
|
||||
const sectionbadges = sectionElement.querySelector(SELECTOR.SECTIONBADGES);
|
||||
if (!sectionbadges) {
|
||||
return;
|
||||
@@ -744,9 +745,11 @@ define(
|
||||
return;
|
||||
}
|
||||
if (add) {
|
||||
document.querySelectorAll('[data-type="' + badgetype + '"]').forEach((b) => {
|
||||
b.classList.add('d-none');
|
||||
});
|
||||
if (removeOther) {
|
||||
document.querySelectorAll('[data-type="' + badgetype + '"]').forEach((b) => {
|
||||
b.classList.add('d-none');
|
||||
});
|
||||
}
|
||||
badge.classList.remove('d-none');
|
||||
} else {
|
||||
badge.classList.add('d-none');
|
||||
|
||||
@@ -2640,10 +2640,11 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
|
||||
$thislist = array();
|
||||
foreach ($rs as $record) {
|
||||
context_helper::preload_from_record($record);
|
||||
$context = context_coursecat::instance($record->id);
|
||||
$canview = self::can_view_category($record);
|
||||
$context = context_coursecat::instance($record->id);
|
||||
$filtercontext = \context_helper::get_navigation_filter_context($context);
|
||||
$baselist[$record->id] = array(
|
||||
'name' => $canview ? format_string($record->name, true, array('context' => $context)) : false,
|
||||
'name' => $canview ? format_string($record->name, true, array('context' => $filtercontext)) : false,
|
||||
'path' => $record->path
|
||||
);
|
||||
if (!$canview || (!empty($requiredcapability) && !has_all_capabilities($requiredcapability, $context))) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user