Compare commits
271 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb02f4fa9f | |||
| 85a6851a8e | |||
| 2db7c80f80 | |||
| ec3ab5528a | |||
| 256080fb2c | |||
| 8c2110c80c | |||
| 9a45e5c064 | |||
| db896863cb | |||
| 5f0d1b806c | |||
| 789f112c86 | |||
| f35a5c1047 | |||
| 43aa89c255 | |||
| 99dc7feb31 | |||
| eb0dd85d88 | |||
| 13d7e5dc6b | |||
| 1646ce747e | |||
| 0082009561 | |||
| ec3f9ed5e4 | |||
| ca7501babb | |||
| 6035d07677 | |||
| f71bd594d1 | |||
| 19e31c4169 | |||
| 17c8757314 | |||
| 0427e1bf7b | |||
| 8c84d14195 | |||
| e375efca79 | |||
| d870292683 | |||
| 2e9c76095c | |||
| 0a3e64de5d | |||
| 5008146b77 | |||
| 03430a4060 | |||
| 7616aa1eef | |||
| 6978147a03 | |||
| a0f12b787b | |||
| 890d43e1c3 | |||
| 4068e8ee2b | |||
| 4d12c8fac3 | |||
| f56e272ff7 | |||
| 98f8fdee6a | |||
| a08193f094 | |||
| 1159f0763b | |||
| 33b7ccdd88 | |||
| eb97c10d89 | |||
| 49e3a29b00 | |||
| b784f20565 | |||
| 375e56bb9b | |||
| 76f0a88d89 | |||
| c4b864606f | |||
| 86d14f5932 | |||
| d1575b2739 | |||
| 9a419d4378 | |||
| dc869f0c05 | |||
| 9e7c8cd9c0 | |||
| 43b5b809e6 | |||
| b56614fce6 | |||
| 8143ad5cb5 | |||
| 580a46bf12 | |||
| b479c7a056 | |||
| 353f4ed7cc | |||
| 599b74c1a2 | |||
| 6498193e6d | |||
| eea759e64c | |||
| b9bea60fa8 | |||
| afd0e41b69 | |||
| 5bf57f83c9 | |||
| b00188cd82 | |||
| 5ea0f0f35e | |||
| 3e43057840 | |||
| 8730b522e6 | |||
| 3bcbaf2ec8 | |||
| d205f1bcb4 | |||
| 116dc12ed1 | |||
| de57e4a1cf | |||
| 86479d5871 | |||
| 63a21837db | |||
| 2181327bf2 | |||
| f12170bb56 | |||
| 4985662a5b | |||
| 763bff0289 | |||
| af455ccfe2 | |||
| f83d35c631 | |||
| bc100f6efa | |||
| fde36bb49c | |||
| 38fe5cc384 | |||
| 67249bea47 | |||
| 20dfe6475b | |||
| b08eaaa028 | |||
| b3e2e81b0d | |||
| eb0634d489 | |||
| 59808c6870 | |||
| 4a02a9975d | |||
| 51662cb8ef | |||
| c3e13a4ca6 | |||
| 35eec5b9aa | |||
| 3b68cecb15 | |||
| 4c3be75ef2 | |||
| dc91c2144c | |||
| 319113f872 | |||
| e894f95f6c | |||
| 977eacdfc0 | |||
| d43a89d86f | |||
| 39c57e2f02 | |||
| c68104c12f | |||
| b177a0cee6 | |||
| 3d41378956 | |||
| 09f9052355 | |||
| 1ef0196b0d | |||
| a5e3d3f018 | |||
| 7c4210b6ea | |||
| 5aec7f1992 | |||
| c97987e9ed | |||
| 266d5f8857 | |||
| 6a1d35dfa0 | |||
| d7d824e872 | |||
| 396f77e552 | |||
| a736250f33 | |||
| b0c912d6f0 | |||
| 72640fbc5a | |||
| 29fab26069 | |||
| 114917dc65 | |||
| c9c1d71c32 | |||
| a61e0d92ed | |||
| 4e822cccfd | |||
| 07af6ccc73 | |||
| cada977938 | |||
| cd409009e4 | |||
| ef0ba8a988 | |||
| 4de8318ca9 | |||
| 9bd33ef7e6 | |||
| 4baac8c6ce | |||
| b4092640b8 | |||
| 75ac9fc3ae | |||
| b7d3d4f191 | |||
| ccb3a73f98 | |||
| d78eccdb13 | |||
| 12e3626a6d | |||
| 86fe6f4ae7 | |||
| 1708db93e6 | |||
| 9f953080d1 | |||
| 0a01a0d119 | |||
| ce34e8ff08 | |||
| 34a0d939dd | |||
| 14d442c52c | |||
| c4c11a559f | |||
| 3e8f439d17 | |||
| ab271c4c24 | |||
| ed44561319 | |||
| b85edb5f5a | |||
| 0a7c65c43e | |||
| ba71a95d34 | |||
| 09bbcdaf32 | |||
| 0de18a98cf | |||
| 0f5f7c8dbe | |||
| 4c406b2d81 | |||
| 19edba7020 | |||
| aee5428591 | |||
| 2d4b15f317 | |||
| 6462abc6df | |||
| 422a6f94a9 | |||
| 6f961cacc5 | |||
| eed6310a08 | |||
| ddcaa5f8a6 | |||
| 6b9cbd5a19 | |||
| 0b86e7f574 | |||
| 547cd0de94 | |||
| 323d7ba996 | |||
| 22c2f2455e | |||
| 4e9c6b3f37 | |||
| a442938279 | |||
| 067138aab4 | |||
| e41473d9ff | |||
| 97aa3dd1f9 | |||
| 2fce831627 | |||
| 6fe2fb1f0f | |||
| 396a02396e | |||
| cb9bebdc8e | |||
| 9a28c68655 | |||
| 98329e1d71 | |||
| 046a9c4905 | |||
| b37969c4f9 | |||
| cc2d6b276d | |||
| 6a4b730dfd | |||
| 9fa2846df4 | |||
| e721309453 | |||
| 3101c07a97 | |||
| 8c9d16e605 | |||
| d3d2d67c8e | |||
| 7c0600ae16 | |||
| b6f3972fad | |||
| 76275dadf5 | |||
| c36274a15a | |||
| c080b4bf58 | |||
| be23488e54 | |||
| c8116a0dbe | |||
| 2ed1dd557c | |||
| fc34abe738 | |||
| c477dcd640 | |||
| c4e40860e4 | |||
| 91530e92ee | |||
| f7b694d15f | |||
| 471a73e98c | |||
| 651af33600 | |||
| 002398450f | |||
| 7f708151eb | |||
| 1263027e0f | |||
| 90ce0974bd | |||
| 23dc4c7ca4 | |||
| 97ed6b7083 | |||
| 499f6f3688 | |||
| 5a22bfc175 | |||
| 66bf9156b3 | |||
| a14b77ec1c | |||
| 8d6e21135c | |||
| 9d661d516e | |||
| 8369f19a64 | |||
| 1338ddaa3f | |||
| 67827bf0fa | |||
| 7479f88d22 | |||
| 0e70433873 | |||
| d54a04197a | |||
| f6497cbe1f | |||
| 693d89cbdb | |||
| ccc6b2ee89 | |||
| 5d7c70c21e | |||
| d2e1d0e76e | |||
| fc21f64863 | |||
| b5fd491b73 | |||
| e8ac6e2fec | |||
| 10aece9d65 | |||
| b419fd08fc | |||
| 5c3e474160 | |||
| 1bf62c2e46 | |||
| 036f61da49 | |||
| e6894fcf11 | |||
| 4fceaed909 | |||
| 95518023c6 | |||
| fe0d7c9b0a | |||
| 56801cdf3b | |||
| 9691e4d4c6 | |||
| d2711e792f | |||
| 102621f53d | |||
| 44fc7bd927 | |||
| 18b20273fd | |||
| 49729e14a4 | |||
| 97ccd815f9 | |||
| abdf765a28 | |||
| bd54d58d8a | |||
| 172277eae4 | |||
| 110e4f7aed | |||
| 8061a3cdc8 | |||
| 60c73f7ff7 | |||
| d83b08f92c | |||
| 5dd77e0022 | |||
| cc7efbf977 | |||
| cd11b9533f | |||
| 436c8070e2 | |||
| 4e1feb2c4e | |||
| 8c96b52f08 | |||
| dd639a9516 | |||
| a9b8b52a16 | |||
| 3f16708b55 | |||
| 586ead512c | |||
| 1e5ad34897 | |||
| 9a23fc37fb | |||
| bf3c210d82 | |||
| 14c33bf2a8 | |||
| cbda104d14 | |||
| 0b719c3951 | |||
| 5d640c4b5c | |||
| b9144ae56a | |||
| 1c4b98c2d2 |
@@ -19,7 +19,7 @@ env:
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
@@ -61,12 +61,12 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
# MySQL builds always run with the lowest PHP supported version.
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
php: 8.1
|
||||
extensions:
|
||||
db: mysqli
|
||||
# PostgreSQL builds always run with the highest PHP supported version.
|
||||
- os: ubuntu-22.04
|
||||
- os: ubuntu-24.04
|
||||
php: 8.3
|
||||
db: pgsql
|
||||
|
||||
|
||||
@@ -6,6 +6,38 @@ More detailed information on key changes can be found in the [Developer update n
|
||||
|
||||
The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com).
|
||||
|
||||
## 4.5.6
|
||||
|
||||
### core
|
||||
|
||||
#### Added
|
||||
|
||||
- Add a new method has_valid_group in \core\report_helper that will return true or false depending if the user has a valid group. This is mainly false in case the user is not in any group in SEPARATEGROUPS. Used in report_log and report_loglive
|
||||
|
||||
For more information see [MDL-84464](https://tracker.moodle.org/browse/MDL-84464)
|
||||
|
||||
#### Changed
|
||||
|
||||
- The `\core\attribute\deprecated` attribute constructor `$replacement` parameter now defaults to null, and can be omitted
|
||||
|
||||
For more information see [MDL-84531](https://tracker.moodle.org/browse/MDL-84531)
|
||||
- Added a new `\core\deprecation::emit_deprecation()` method which should be used in places where a deprecation is known to occur. This method will throw debugging if no deprecation notice was found, for example:
|
||||
```php
|
||||
public function deprecated_method(): void {
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
```
|
||||
|
||||
For more information see [MDL-85897](https://tracker.moodle.org/browse/MDL-85897)
|
||||
|
||||
### core_message
|
||||
|
||||
#### Added
|
||||
|
||||
- The web service `core_message_get_member_info` additionally returns `cancreatecontact` which is a boolean value for a user's permission to add a contact.
|
||||
|
||||
For more information see [MDL-72123](https://tracker.moodle.org/browse/MDL-72123)
|
||||
|
||||
## 4.5.5
|
||||
|
||||
### core
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
define('IGNORE_COMPONENT_CACHE', true);
|
||||
|
||||
require(__DIR__.'/../../config.php');
|
||||
require_once($CFG->libdir.'/clilib.php');
|
||||
|
||||
@@ -4716,6 +4716,7 @@
|
||||
<VENDOR name="mysql" version="8.4" />
|
||||
<VENDOR name="postgres" version="14" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="auroramysql" version="8.0" />
|
||||
</DATABASE>
|
||||
<PHP version="8.2.0" level="required">
|
||||
</PHP>
|
||||
@@ -4895,6 +4896,8 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_aurora_version" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="5.1" requires="4.2.3">
|
||||
@@ -4906,8 +4909,9 @@
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.11.0" />
|
||||
<VENDOR name="mysql" version="8.4" />
|
||||
<VENDOR name="postgres" version="14" />
|
||||
<VENDOR name="postgres" version="15" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="auroramysql" version="8.0" />
|
||||
</DATABASE>
|
||||
<PHP version="8.2.0" level="required">
|
||||
</PHP>
|
||||
@@ -5092,6 +5096,8 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_aurora_version" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('IGNORE_COMPONENT_CACHE', true);
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
|
||||
|
||||
@@ -24,5 +24,14 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
// Remove the 'enableaccessibilitytools' configuration setting in case of reinstall.
|
||||
unset_config('enableaccessibilitytools');
|
||||
/**
|
||||
* Plugin uninstall code.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
function xmldb_tool_brickfield_uninstall(): bool {
|
||||
// Remove the 'enableaccessibilitytools' configuration setting in case of reinstall.
|
||||
unset_config('enableaccessibilitytools');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_capability;
|
||||
|
||||
// phpcs:disable moodle.PHPUnit.TestCaseProvider.dataProviderSyntaxMethodNotFound
|
||||
|
||||
/**
|
||||
* Detect common problems in capability definitions of plugins.
|
||||
*
|
||||
* @group plugin_checks
|
||||
* @package tool_capability
|
||||
* @copyright 2025 Petr Skoda
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class plugin_checks_test extends \core\tests\plugin_checks_testcase {
|
||||
/**
|
||||
* Verify contents of plugin db/access.php file.
|
||||
*
|
||||
* @dataProvider all_plugins_provider
|
||||
* @coversNothing
|
||||
*
|
||||
* @param string $component
|
||||
* @param string $plugintype
|
||||
* @param string $pluginname
|
||||
* @param string $dir
|
||||
*/
|
||||
public function test_db_access_file(string $component, string $plugintype, string $pluginname, string $dir): void {
|
||||
global $CFG;
|
||||
|
||||
$stringmanager = get_string_manager();
|
||||
$corerolefile = "$CFG->dirroot/lang/en/role.php";
|
||||
$langfile = "$dir/lang/en/$component.php";
|
||||
|
||||
$file = "$dir/db/access.php";
|
||||
$capabilities = $this->fetch_array_from_file($file, 'capabilities');
|
||||
if (!$capabilities) {
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($capabilities as $capname => $capability) {
|
||||
if ($plugintype === 'qbank' && str_starts_with($capname, 'moodle/question:')) {
|
||||
// Question bank capabilities are irregular.
|
||||
$strname = explode('/', $capname, 2)[1];
|
||||
$this->assertTrue($stringmanager->string_exists($strname, 'core_role'),
|
||||
"Missing capability name string '$strname' in $corerolefile");
|
||||
continue;
|
||||
}
|
||||
$this->assertMatchesRegularExpression("|^$plugintype/$pluginname:[a-z0-9_]+$|", $capname);
|
||||
$strname = substr($capname, strlen($plugintype) + 1);
|
||||
$this->assertTrue($stringmanager->string_exists($strname, $component),
|
||||
"Missing capability name string '$strname' in $langfile");
|
||||
$this->assertSame($capname, clean_param($capname, PARAM_CAPABILITY));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -379,7 +379,7 @@ class renderer extends \plugin_renderer_base {
|
||||
WHERE deleted = 0
|
||||
AND suspended = 0
|
||||
GROUP BY auth";
|
||||
$allusersinfo = $DB->get_records_sql($alluserssql, []);
|
||||
$allusersinfo = $DB->get_records_sql_menu($alluserssql);
|
||||
|
||||
$noncompletesql = "SELECT u.auth, COUNT(u.id)
|
||||
FROM {user} u
|
||||
@@ -388,7 +388,7 @@ class renderer extends \plugin_renderer_base {
|
||||
AND (mfaa.lastverified < ?
|
||||
OR mfaa.lastverified IS NULL)
|
||||
GROUP BY u.auth";
|
||||
$noncompleteinfo = $DB->get_records_sql($noncompletesql, [$lookback, $lookback]);
|
||||
$noncompleteinfo = $DB->get_records_sql_menu($noncompletesql, [$lookback, $lookback]);
|
||||
|
||||
$nologinsql = "SELECT auth, COUNT(id)
|
||||
FROM {user}
|
||||
@@ -396,7 +396,7 @@ class renderer extends \plugin_renderer_base {
|
||||
AND suspended = 0
|
||||
AND lastlogin < ?
|
||||
GROUP BY auth";
|
||||
$nologininfo = $DB->get_records_sql($nologinsql, [$lookback]);
|
||||
$nologininfo = $DB->get_records_sql_menu($nologinsql, [$lookback]);
|
||||
|
||||
$mfauserssql = "SELECT auth,
|
||||
COUNT(DISTINCT tm.userid)
|
||||
@@ -406,7 +406,7 @@ class renderer extends \plugin_renderer_base {
|
||||
AND u.deleted = 0
|
||||
AND u.suspended = 0
|
||||
GROUP BY u.auth";
|
||||
$mfausersinfo = $DB->get_records_sql($mfauserssql, [$lookback]);
|
||||
$mfausersinfo = $DB->get_records_sql_menu($mfauserssql, [$lookback]);
|
||||
|
||||
$factorsusedsql = "SELECT CONCAT(u.auth, '_', tm.factor) as id,
|
||||
COUNT(*)
|
||||
@@ -417,7 +417,7 @@ class renderer extends \plugin_renderer_base {
|
||||
AND u.suspended = 0
|
||||
AND (tm.revoked = 0 OR (tm.revoked = 1 AND tm.timemodified > ?))
|
||||
GROUP BY CONCAT(u.auth, '_', tm.factor)";
|
||||
$factorsusedinfo = $DB->get_records_sql($factorsusedsql, [$lookback, $lookback]);
|
||||
$factorsusedinfo = $DB->get_records_sql_menu($factorsusedsql, [$lookback, $lookback]);
|
||||
|
||||
// Auth rows.
|
||||
$authtypes = get_enabled_auth_plugins(true);
|
||||
@@ -426,10 +426,10 @@ class renderer extends \plugin_renderer_base {
|
||||
$row[] = \html_writer::tag('b', $authtype);
|
||||
|
||||
// Setup the overall totals columns.
|
||||
$row[] = $allusersinfo[$authtype]->count ?? '-';
|
||||
$row[] = $mfausersinfo[$authtype]->count ?? '-';
|
||||
$row[] = $noncompleteinfo[$authtype]->count ?? '-';
|
||||
$row[] = $nologininfo[$authtype]->count ?? '-';
|
||||
$row[] = $allusersinfo[$authtype] ?? '-';
|
||||
$row[] = $mfausersinfo[$authtype] ?? '-';
|
||||
$row[] = $noncompleteinfo[$authtype] ?? '-';
|
||||
$row[] = $nologininfo[$authtype] ?? '-';
|
||||
|
||||
// Create a running counter for the total.
|
||||
$authtotal = 0;
|
||||
@@ -439,7 +439,7 @@ class renderer extends \plugin_renderer_base {
|
||||
if (!empty($column)) {
|
||||
// Get the information from the data key.
|
||||
$key = $authtype . '_' . $column;
|
||||
$count = $factorsusedinfo[$key]->count ?? 0;
|
||||
$count = $factorsusedinfo[$key] ?? 0;
|
||||
$authtotal += $count;
|
||||
|
||||
$row[] = $count ? format_float($count, 0) : '-';
|
||||
|
||||
@@ -71,7 +71,7 @@ $string['setupfactor:instructionsverification'] = '3. Enter the verification cod
|
||||
$string['setupfactor:intro'] = 'To set up this method, you need to have a device with an authenticator app. If you don\'t have an app, you can download one. For example, <a href="https://2fas.com/" target="_blank">2FAS Auth</a>, <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a>, Google Authenticator, Microsoft Authenticator or Twilio Authy.';
|
||||
$string['setupfactor:key'] = 'Secret key: ';
|
||||
$string['setupfactor:link'] = 'Or enter details manually.';
|
||||
$string['setupfactor:link_help'] = 'If you are on a mobile device and already have an authenticator app installed this link may work. Note that using TOTP on the same device as you login on can weaken the benefits of MFA.';
|
||||
$string['setupfactor:link_help'] = 'If you are on a mobile device and already have an authenticator app installed this link may work. Note that using TOTP on the same device as you log in on can weaken the benefits of MFA.';
|
||||
$string['setupfactor:linklabel'] = 'Open app already installed on this device';
|
||||
$string['setupfactor:mode'] = 'Mode:';
|
||||
$string['setupfactor:mode:timebased'] = 'Time-based';
|
||||
|
||||
@@ -590,6 +590,7 @@ class api {
|
||||
'subdirs' => false,
|
||||
'maxfiles' => -1,
|
||||
'context' => context_system::instance(),
|
||||
'noclean' => true,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -606,6 +607,7 @@ class api {
|
||||
'subdirs' => false,
|
||||
'maxfiles' => -1,
|
||||
'context' => context_system::instance(),
|
||||
'noclean' => true,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ class course_bin extends base_bin {
|
||||
// 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.
|
||||
$forcedrestoresettings = $CFG->forced_plugin_settings['restore'] ?? null;
|
||||
$forcedrestoresettings = $CFG->forced_plugin_settings['restore'] ?? [];
|
||||
$CFG->forced_plugin_settings['restore']['restore_general_users'] = 1;
|
||||
$CFG->forced_plugin_settings['restore']['restore_general_groups'] = 1;
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||
$nextruntime = $task->get_next_run_time();
|
||||
$due = $nextruntime < $now;
|
||||
if ($task->get_attempts_available() > 0) {
|
||||
$nextrun = $due ? userdate($nextruntime) : get_string('asap', 'tool_task');
|
||||
$nextrun = $due ? get_string('asap', 'tool_task') : userdate($nextruntime);
|
||||
} else {
|
||||
$nextrun = get_string('never', 'admin');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
@tool @tool_task @javascript
|
||||
Feature: Manage adhoc task
|
||||
In order to manage adhoc tasks
|
||||
As an admin
|
||||
I need to be able to view adhoc tasks
|
||||
|
||||
Scenario Outline: View adhoc tasks next run time
|
||||
Given the following "tool_task > adhoc tasks" exist:
|
||||
| classname | seconds | hostname | pid | nextruntime |
|
||||
| \core\task\asynchronous_backup_task | 0 | c69335460f7f | 1915 | <nextruntime> |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Server > Tasks > Ad hoc tasks" in site administration
|
||||
Then the following should exist in the "Ad hoc tasks" table:
|
||||
| Component / Class name | Next run |
|
||||
| asynchronous_backup_task | <nextruntimestr> |
|
||||
And I click on "asynchronous_backup_task" "link" in the "Ad hoc tasks" "table"
|
||||
And the following should exist in the "\core\task\asynchronous_backup_task Ad hoc tasks" table:
|
||||
| Next run |
|
||||
| <nextruntimestr> |
|
||||
Examples:
|
||||
| nextruntime | nextruntimestr |
|
||||
| ##yesterday## | ASAP |
|
||||
| ##tomorrow noon## | ##tomorrow noon##%A, %d %B %Y, %I:%M## |
|
||||
@@ -43,7 +43,7 @@ class tool_task_generator extends testing_module_generator {
|
||||
global $DB;
|
||||
$conditions = ['classname' => $data['classname']];
|
||||
$record = $DB->get_record('task_scheduled', $conditions, '*', MUST_EXIST);
|
||||
$record->timestarted = time() - $data['seconds'];
|
||||
$record->timestarted = $data['seconds'] > 0 ? time() - $data['seconds'] : 0;
|
||||
$record->hostname = $data['hostname'];
|
||||
$record->pid = $data['pid'];
|
||||
$DB->update_record('task_scheduled', $record);
|
||||
@@ -59,8 +59,8 @@ class tool_task_generator extends testing_module_generator {
|
||||
global $DB;
|
||||
$adhoctask = (object)[
|
||||
'classname' => $data['classname'],
|
||||
'nextruntime' => 0,
|
||||
'timestarted' => time() - $data['seconds'],
|
||||
'nextruntime' => $data['nextruntime'] ?? 0,
|
||||
'timestarted' => $data['seconds'] > 0 ? time() - $data['seconds'] : 0,
|
||||
'hostname' => $data['hostname'],
|
||||
'pid' => $data['pid'],
|
||||
];
|
||||
|
||||
@@ -1146,7 +1146,7 @@ class tool_uploadcourse_course {
|
||||
|
||||
// Now update values.
|
||||
// Sort out plugin specific fields.
|
||||
$modifiedinstance = $plugin->update_enrol_plugin_data($course->id, $method, $instance);
|
||||
$modifiedinstance = $plugin->update_enrol_plugin_data($course->id, $method, clone $instance);
|
||||
$plugin->update_instance($instance, $modifiedinstance);
|
||||
} else {
|
||||
foreach ($errors as $key => $message) {
|
||||
|
||||
@@ -126,3 +126,19 @@ Feature: An admin can create courses with cohort enrolments using a CSV file
|
||||
And I press "Cancel"
|
||||
And I click on "Edit" "link" in the "Cohort 4" "table_row"
|
||||
And the field "Assign role" matches value "Non-editing teacher"
|
||||
|
||||
@javascript
|
||||
Scenario: Uploading a cohort enrolment method with a non-default role applies only the specified role
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| t1 | Teacher | 1 | s1@example.com |
|
||||
And the following "cohort members" exist:
|
||||
| user | cohort |
|
||||
| t1 | CV4 |
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_cohort_multiple.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I click on "Upload courses" "button"
|
||||
When I am on the "Course 1" "course" page
|
||||
And I navigate to course participants
|
||||
Then I should see "Non-editing teacher" in the "Teacher 1" "table_row"
|
||||
And I should not see "Student" in the "Teacher 1" "table_row"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
@tool @tool_xmldb
|
||||
Feature: The Reconcile XMLDB files report works and all the files are ok
|
||||
In order to ensure that all the XMLDB Editor xml files are generated properly
|
||||
As a developer
|
||||
I need to be able to run the Reconcile XMLDB files report and verify that everything is ok
|
||||
|
||||
Scenario: The Reconcile XMLDB files reports that all files are ok
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Development > XMLDB editor" in site administration
|
||||
When I follow "[Reconcile XMLDB files]"
|
||||
Then I should see "Look for XMLDB files needing reconciling"
|
||||
And I should see "All files are OK. No reconciling is needed."
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -31,6 +31,8 @@ import AIHelper from 'core_ai/helper';
|
||||
import DrawerEvents from 'core/drawer_events';
|
||||
import {subscribe} from 'core/pubsub';
|
||||
import * as MessageDrawerHelper from 'core_message/message_drawer_helper';
|
||||
import * as FocusLock from 'core/local/aria/focuslock';
|
||||
import {isSmall} from "core/pagehelpers";
|
||||
|
||||
const AICourseAssist = class {
|
||||
|
||||
@@ -60,6 +62,7 @@ const AICourseAssist = class {
|
||||
this.jumpToElement = document.querySelector(Selectors.ELEMENTS.JUMPTO);
|
||||
this.summaryActionElement = document.querySelector(Selectors.ACTIONS.SUMMARY);
|
||||
this.aiDrawerCloseElement = this.aiDrawerElement.querySelector(Selectors.ELEMENTS.AIDRAWER_CLOSE);
|
||||
this.isDrawerFocusLocked = false;
|
||||
|
||||
this.registerEventListeners();
|
||||
}
|
||||
@@ -85,6 +88,12 @@ const AICourseAssist = class {
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (this.isAIDrawerOpen() && e.key === 'Escape') {
|
||||
this.closeAIDrawer();
|
||||
}
|
||||
});
|
||||
|
||||
// Close AI drawer if message drawer is shown.
|
||||
subscribe(DrawerEvents.DRAWER_SHOWN, () => {
|
||||
if (this.isAIDrawerOpen()) {
|
||||
@@ -198,12 +207,27 @@ const AICourseAssist = class {
|
||||
}
|
||||
this.jumpToElement.setAttribute('tabindex', 0);
|
||||
this.jumpToElement.focus();
|
||||
|
||||
// If the AI drawer is opened on a small screen, we need to trap the focus tab within the AI drawer.
|
||||
if (isSmall()) {
|
||||
FocusLock.trapFocus(this.aiDrawerElement);
|
||||
this.aiDrawerElement.setAttribute('aria-modal', 'true');
|
||||
this.aiDrawerElement.setAttribute('role', 'dialog');
|
||||
this.isDrawerFocusLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the AI drawer.
|
||||
*/
|
||||
closeAIDrawer() {
|
||||
// Untrap focus if it was locked.
|
||||
if (this.isDrawerFocusLocked) {
|
||||
FocusLock.untrapFocus();
|
||||
this.aiDrawerElement.removeAttribute('aria-modal');
|
||||
this.aiDrawerElement.setAttribute('role', 'region');
|
||||
}
|
||||
|
||||
this.aiDrawerElement.classList.remove('show');
|
||||
this.aiDrawerElement.setAttribute('tabindex', '-1');
|
||||
this.aiDrawerBodyElement.removeAttribute('aria-live');
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['aidrawerlabel'] = 'AI-generated content drawer';
|
||||
$string['aisummary'] = 'AI summary';
|
||||
$string['courseassist:summarise_text'] = 'Summarise text';
|
||||
$string['copy'] = 'Copy';
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"content": "<p>Content to display</p>"
|
||||
}
|
||||
}}
|
||||
<div class="ai-drawer" id="ai-drawer" tabindex="-1">
|
||||
<div class="ai-drawer" id="ai-drawer" aria-label={{#quote}}{{#str}} aidrawerlabel, aiplacement_courseassist {{/str}}{{/quote}} tabindex="-1" role="region">
|
||||
<div class="ai-drawer-header">
|
||||
<button id="ai-drawer-close" class="btn ai-drawer-button" type="button" data-action="course-summarise">
|
||||
{{#pix}} e/cancel, core {{/pix}}
|
||||
|
||||
@@ -87,7 +87,7 @@ Please log in as before, using the link on the login page.
|
||||
$string['emailpasswordchangeinfosubject'] = '{$a}: Change password information';
|
||||
$string['info'] = 'External account';
|
||||
$string['issuer'] = 'OAuth 2 service';
|
||||
$string['issuernologin'] = 'This issuer can not be used to login';
|
||||
$string['issuernologin'] = 'This issuer can not be used to log in.';
|
||||
$string['key'] = 'Key';
|
||||
$string['linkedlogins'] = 'Linked logins';
|
||||
$string['linkedloginshelp'] = 'Help with linked logins';
|
||||
|
||||
@@ -31,7 +31,7 @@ $string['auth_shib_contact_administrator'] = 'In case you are not associated wit
|
||||
$string['auth_shibbolethdescription'] = 'Using this method users are created and authenticated using Shibboleth. For set-up details, see the <a href="{$a}">Shibboleth README</a>.';
|
||||
$string['auth_shibboleth_errormsg'] = 'Please select the organization you are member of!';
|
||||
$string['auth_shibboleth_login'] = 'Shibboleth login';
|
||||
$string['auth_shibboleth_login_long'] = 'Login to Moodle via Shibboleth';
|
||||
$string['auth_shibboleth_login_long'] = 'Log in via Shibboleth';
|
||||
$string['auth_shibboleth_manual_login'] = 'Manual login';
|
||||
$string['auth_shibboleth_select_member'] = 'I\'m a member of ...';
|
||||
$string['auth_shibboleth_select_organization'] = 'For authentication via Shibboleth, please select your organisation from the drop-down menu:';
|
||||
|
||||
@@ -31,6 +31,6 @@ class core_availability_renderer extends plugin_renderer_base {
|
||||
*/
|
||||
#[\core\attribute\deprecated(availability_info::class, since: '4.0', mdl: 'MDL-71691', final: true)]
|
||||
public function render_core_availability_multiple_messages(): void {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -764,7 +764,7 @@ M.core_availability.List.prototype.clickAdd = function() {
|
||||
dialogRef.dialog = new M.core.dialogue(config);
|
||||
dialogRef.dialog.show();
|
||||
cancel.on('click', function() {
|
||||
dialogRef.dialog.destroy();
|
||||
dialogRef.dialog.hide();
|
||||
// Focus the button they clicked originally.
|
||||
this.inner.one('> .availability-button').one('button').focus();
|
||||
}, this);
|
||||
@@ -801,7 +801,7 @@ M.core_availability.List.prototype.getAddHandler = function(type, dialogRef) {
|
||||
M.core_availability.form.rootList.renumber();
|
||||
this.updateHtml();
|
||||
// Hide dialog.
|
||||
dialogRef.dialog.destroy();
|
||||
dialogRef.dialog.hide();
|
||||
newItem.focusAfterAdd();
|
||||
};
|
||||
};
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -764,7 +764,7 @@ M.core_availability.List.prototype.clickAdd = function() {
|
||||
dialogRef.dialog = new M.core.dialogue(config);
|
||||
dialogRef.dialog.show();
|
||||
cancel.on('click', function() {
|
||||
dialogRef.dialog.destroy();
|
||||
dialogRef.dialog.hide();
|
||||
// Focus the button they clicked originally.
|
||||
this.inner.one('> .availability-button').one('button').focus();
|
||||
}, this);
|
||||
@@ -801,7 +801,7 @@ M.core_availability.List.prototype.getAddHandler = function(type, dialogRef) {
|
||||
M.core_availability.form.rootList.renumber();
|
||||
this.updateHtml();
|
||||
// Hide dialog.
|
||||
dialogRef.dialog.destroy();
|
||||
dialogRef.dialog.hide();
|
||||
newItem.focusAfterAdd();
|
||||
};
|
||||
};
|
||||
|
||||
Vendored
+2
-2
@@ -762,7 +762,7 @@ M.core_availability.List.prototype.clickAdd = function() {
|
||||
dialogRef.dialog = new M.core.dialogue(config);
|
||||
dialogRef.dialog.show();
|
||||
cancel.on('click', function() {
|
||||
dialogRef.dialog.destroy();
|
||||
dialogRef.dialog.hide();
|
||||
// Focus the button they clicked originally.
|
||||
this.inner.one('> .availability-button').one('button').focus();
|
||||
}, this);
|
||||
@@ -799,7 +799,7 @@ M.core_availability.List.prototype.getAddHandler = function(type, dialogRef) {
|
||||
M.core_availability.form.rootList.renumber();
|
||||
this.updateHtml();
|
||||
// Hide dialog.
|
||||
dialogRef.dialog.destroy();
|
||||
dialogRef.dialog.hide();
|
||||
newItem.focusAfterAdd();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -111,7 +111,7 @@ abstract class base_controller extends backup implements loggable {
|
||||
*/
|
||||
#[\core\attribute\deprecated(since: '4.1', mdl: 'MDL-74548', final: true)]
|
||||
public function set_copy(): void {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +119,6 @@ abstract class base_controller extends backup implements loggable {
|
||||
*/
|
||||
#[\core\attribute\deprecated('restore_controller::get_copy()', since: '4.1', mdl: 'MDL-74548', final: true)]
|
||||
public function get_copy() {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
}
|
||||
|
||||
+113
-59
@@ -60,6 +60,7 @@ if (!empty($issuedbadge->recipient->id)) {
|
||||
$assertionid = $assertion->get_assertion_hash();
|
||||
$assertionentityid = $assertiondata['id'];
|
||||
$badgeadded = false;
|
||||
$issuerexists = false;
|
||||
if (badges_open_badges_backpack_api() == OPEN_BADGES_V2) {
|
||||
$sitebackpack = badges_get_site_primary_backpack();
|
||||
$api = new \core_badges\backpack_api($sitebackpack);
|
||||
@@ -71,62 +72,75 @@ if (!empty($issuedbadge->recipient->id)) {
|
||||
$issuer = $assertion->get_issuer();
|
||||
if (!($issuerentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email']))) {
|
||||
$response = $api->put_issuer($issuer);
|
||||
if (!$response) {
|
||||
throw new moodle_exception('invalidrequest', 'error');
|
||||
if ($response) {
|
||||
$issuerexists = true;
|
||||
$issuerentityid = $response->id;
|
||||
badges_external_create_mapping(
|
||||
$sitebackpack->id,
|
||||
OPEN_BADGES_V2_TYPE_ISSUER,
|
||||
$issuer['email'],
|
||||
$issuerentityid,
|
||||
);
|
||||
}
|
||||
$issuerentityid = $response->id;
|
||||
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'],
|
||||
$issuerentityid);
|
||||
}
|
||||
// Create badge.
|
||||
$badge = $assertion->get_badge_class(false);
|
||||
$badgeid = $assertion->get_badge_id();
|
||||
if (!($badgeentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid))) {
|
||||
$response = $api->put_badgeclass($issuerentityid, $badge);
|
||||
if (!$response) {
|
||||
throw new moodle_exception('invalidrequest', 'error');
|
||||
if ($issuerexists) {
|
||||
// Create badge.
|
||||
$badge = $assertion->get_badge_class(false);
|
||||
$badgeid = $assertion->get_badge_id();
|
||||
if (!($badgeentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid))) {
|
||||
$response = $api->put_badgeclass($issuerentityid, $badge);
|
||||
if ($response) {
|
||||
$badgeentityid = $response->id;
|
||||
badges_external_create_mapping(
|
||||
$sitebackpack->id,
|
||||
OPEN_BADGES_V2_TYPE_BADGE,
|
||||
$badgeid,
|
||||
$badgeentityid,
|
||||
);
|
||||
}
|
||||
}
|
||||
$badgeentityid = $response->id;
|
||||
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid,
|
||||
$badgeentityid);
|
||||
}
|
||||
|
||||
// Create assertion (Award the badge!).
|
||||
$assertionentityid = badges_external_get_mapping(
|
||||
$sitebackpack->id,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$assertionid
|
||||
);
|
||||
|
||||
if ($assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) {
|
||||
$assertionentityid = badges_generate_badgr_open_url(
|
||||
$sitebackpack,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$assertionentityid
|
||||
);
|
||||
}
|
||||
|
||||
// Create an assertion for the recipient in the issuer's account.
|
||||
if (!$assertionentityid) {
|
||||
$response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata);
|
||||
if (!$response) {
|
||||
throw new moodle_exception('invalidrequest', 'error');
|
||||
}
|
||||
$assertionentityid = badges_generate_badgr_open_url($sitebackpack, OPEN_BADGES_V2_TYPE_ASSERTION, $response->id);
|
||||
$badgeadded = true;
|
||||
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
|
||||
$response->id);
|
||||
} else {
|
||||
// An assertion already exists. Make sure it's up to date.
|
||||
$internalid = badges_external_get_mapping(
|
||||
// Create assertion (Award the badge!).
|
||||
$assertionentityid = badges_external_get_mapping(
|
||||
$sitebackpack->id,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$assertionid,
|
||||
'externalid'
|
||||
$assertionid
|
||||
);
|
||||
$response = $api->update_assertion($internalid, $assertiondata);
|
||||
if (!$response) {
|
||||
throw new moodle_exception('invalidrequest', 'error');
|
||||
|
||||
if ($assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) {
|
||||
$assertionentityid = badges_generate_badgr_open_url(
|
||||
$sitebackpack,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$assertionentityid
|
||||
);
|
||||
}
|
||||
|
||||
// Create an assertion for the recipient in the issuer's account.
|
||||
if (!$assertionentityid) {
|
||||
$response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata);
|
||||
if ($response) {
|
||||
$assertionentityid = badges_generate_badgr_open_url(
|
||||
$sitebackpack,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$response->id,
|
||||
);
|
||||
$badgeadded = true;
|
||||
badges_external_create_mapping(
|
||||
$sitebackpack->id,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$assertionid,
|
||||
$response->id,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// An assertion already exists. Make sure it's up to date.
|
||||
$internalid = badges_external_get_mapping(
|
||||
$sitebackpack->id,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$assertionid,
|
||||
'externalid'
|
||||
);
|
||||
$response = $api->update_assertion($internalid, $assertiondata);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,21 +150,61 @@ if (!empty($issuedbadge->recipient->id)) {
|
||||
// - If a user and site backpack have the same provider we can skip this as Badgr automatically maps recipients
|
||||
// based on email address.
|
||||
// - This is only needed when the backpacks are from different regions.
|
||||
if ($assertionentityid && !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid)) {
|
||||
if (
|
||||
$assertionentityid
|
||||
&& (!$issuerexists || !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid))
|
||||
) {
|
||||
$userapi = new \core_badges\backpack_api($userbackpack, $backpack);
|
||||
$userapi->authenticate();
|
||||
$response = $userapi->import_badge_assertion($assertionentityid);
|
||||
if (!$response) {
|
||||
throw new moodle_exception('invalidrequest', 'error');
|
||||
if ($response) {
|
||||
$assertionentityid = $response->id;
|
||||
$badgeadded = true;
|
||||
badges_external_create_mapping(
|
||||
$userbackpack->id,
|
||||
OPEN_BADGES_V2_TYPE_ASSERTION,
|
||||
$assertionid,
|
||||
$assertionentityid,
|
||||
);
|
||||
}
|
||||
$assertionentityid = $response->id;
|
||||
$badgeadded = true;
|
||||
badges_external_create_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
|
||||
$assertionentityid);
|
||||
}
|
||||
|
||||
$response = $badgeadded ? ['success' => 'addedtobackpack'] : ['warning' => 'existsinbackpack'];
|
||||
redirect(new moodle_url('/badges/mybadges.php', $response));
|
||||
if ($badgeadded) {
|
||||
$message = get_string('addedtobackpack', 'badges');
|
||||
$messagetype = \core\output\notification::NOTIFY_SUCCESS;
|
||||
} else {
|
||||
if (isset($userapi) && !empty($userapi->get_errors())) {
|
||||
// If the api used to import the badge to the backpack has errors, show them to inform the user.
|
||||
if (array_filter($userapi->get_errors(), fn($element) => str_contains($element, "DUPLICATE_BADGE"))) {
|
||||
// Duplicated badges are displayed as a warning.
|
||||
$message = get_string('existsinbackpack', 'badges');
|
||||
$messagetype = \core\output\notification::NOTIFY_WARNING;
|
||||
} else {
|
||||
// If the userapi has any other errors, we will use those to inform the user.
|
||||
$message = get_string(
|
||||
'error:cannotsendtobackpack',
|
||||
'badges',
|
||||
implode($userapi->get_errors()),
|
||||
);
|
||||
$messagetype = \core\output\notification::NOTIFY_ERROR;
|
||||
}
|
||||
} else if (isset($api) && !empty($api->get_errors())) {
|
||||
// If the api used to create/update the issuer has errors, show them to inform the user.
|
||||
$errors = $api->get_errors() ?? [get_string('invalidrequest', 'error')];
|
||||
$message = get_string(
|
||||
'error:cannotsendtobackpack',
|
||||
'badges',
|
||||
implode($errors),
|
||||
);
|
||||
$messagetype = \core\output\notification::NOTIFY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
redirect(
|
||||
url: new \core\url('/badges/mybadges.php'),
|
||||
message: $message,
|
||||
messagetype: $messagetype,
|
||||
);
|
||||
} else {
|
||||
redirect(new moodle_url('/badges/mybadges.php'));
|
||||
redirect(new \core\url('/badges/mybadges.php'));
|
||||
}
|
||||
|
||||
@@ -512,6 +512,20 @@ class backpack_api {
|
||||
return backpack_api_mapping::get_authentication_error();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all errors occurred during the requests to the backpack.
|
||||
*
|
||||
* @return array The list of errors.
|
||||
*/
|
||||
public function get_errors(): array {
|
||||
$errors = [];
|
||||
foreach ($this->mappings as $mapping) {
|
||||
$errors = array_merge($errors, $mapping->get_errors());
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of badges in a collection.
|
||||
*
|
||||
|
||||
@@ -81,6 +81,9 @@ class backpack_api_mapping {
|
||||
/** @var int OpenBadges version. */
|
||||
protected $backpackapiversion;
|
||||
|
||||
/** @var array Errors encountered during the request. */
|
||||
protected $errors = [];
|
||||
|
||||
/**
|
||||
* Create a mapping.
|
||||
*
|
||||
@@ -146,6 +149,26 @@ class backpack_api_mapping {
|
||||
return self::$authenticationerror;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the errors encountered during the request.
|
||||
*
|
||||
* @return array The list of errors.
|
||||
*/
|
||||
public function get_errors() {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error to the list of errors.
|
||||
*
|
||||
* @param string $error The error message.
|
||||
* @return self This instance for method chaining.
|
||||
*/
|
||||
public function add_error(string $error): self {
|
||||
$this->errors[] = $error;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the action match this mapping?
|
||||
*
|
||||
@@ -342,6 +365,23 @@ class backpack_api_mapping {
|
||||
$response = $curl->put($url, $post, $options);
|
||||
}
|
||||
$response = json_decode($response);
|
||||
if ($response === null) {
|
||||
$this->add_error(get_string('invalidrequest', 'error'));
|
||||
return null;
|
||||
}
|
||||
if (isset($response->status) && isset($response->status->success) && $response->status->success != true) {
|
||||
// If the response wasn't successful, store the errors and return null.
|
||||
if (isset($response->validationErrors)) {
|
||||
$error = implode(', ', $response->validationErrors);
|
||||
} else if (isset($response->status->description)) {
|
||||
$error = $response->status->description;
|
||||
} else {
|
||||
$error = get_string('invalidrequest', 'error');
|
||||
}
|
||||
$this->add_error($error);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($response->result)) {
|
||||
$response = $response->result;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,38 @@ class badge extends base {
|
||||
return html_writer::link($url, $row->name);
|
||||
});
|
||||
|
||||
// Name with image and link.
|
||||
$columns[] = (new column(
|
||||
'namewithimagelink',
|
||||
new lang_string('namewithimagelink', 'core_badges'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_join("LEFT JOIN {context} {$contextalias}
|
||||
ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . "
|
||||
AND {$contextalias}.instanceid = {$badgealias}.courseid")
|
||||
->add_fields(
|
||||
"{$badgealias}.name, {$badgealias}.id, {$badgealias}.type, {$badgealias}.courseid"
|
||||
)
|
||||
->add_field($DB->sql_cast_to_char("{$badgealias}.imagecaption"), 'imagecaption')
|
||||
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
|
||||
->set_is_sortable(true)
|
||||
->add_callback(static function ($value, stdClass $badge): string {
|
||||
if ($badge->id === null) {
|
||||
return '';
|
||||
}
|
||||
if ($badge->type == BADGE_TYPE_SITE) {
|
||||
$context = context_system::instance();
|
||||
} else {
|
||||
context_helper::preload_from_record($badge);
|
||||
$context = context_course::instance($badge->courseid);
|
||||
}
|
||||
|
||||
$badgeimage = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', 'f2');
|
||||
$url = new moodle_url('/badges/overview.php', ['id' => $badge->id]);
|
||||
return html_writer::img($badgeimage, $badge->imagecaption) . ' ' . html_writer::link($url, $badge->name);
|
||||
});
|
||||
|
||||
// Description (note, this column contains plaintext so requires no post-processing).
|
||||
$descriptionfieldsql = "{$badgealias}.description";
|
||||
if ($DB->get_dbfamily() === 'oracle') {
|
||||
|
||||
@@ -94,8 +94,6 @@ class badges extends system_report {
|
||||
$this->add_filters();
|
||||
$this->add_actions();
|
||||
|
||||
// Set initial sorting by name.
|
||||
$this->set_initial_sort_column('badge:namewithlink', SORT_ASC);
|
||||
$this->set_default_no_results_notice(new lang_string('nomatchingbadges', 'core_badges'));
|
||||
|
||||
// Set if report can be downloaded.
|
||||
@@ -129,8 +127,7 @@ class badges extends system_report {
|
||||
*/
|
||||
public function add_columns(string $badgeissuedalias): void {
|
||||
$columns = [
|
||||
'badge:image',
|
||||
'badge:namewithlink',
|
||||
'badge:namewithimagelink',
|
||||
'badge:status',
|
||||
'badge:criteria',
|
||||
];
|
||||
@@ -138,15 +135,12 @@ class badges extends system_report {
|
||||
$canviewdraftbadges = $this->can_view_draft_badges();
|
||||
if (!$canviewdraftbadges) {
|
||||
// Remove status and recipients column.
|
||||
unset($columns[2]);
|
||||
unset($columns[1]);
|
||||
}
|
||||
$this->add_columns_from_entities($columns);
|
||||
|
||||
// Remove title from image column.
|
||||
$this->get_column('badge:image')->set_title(null);
|
||||
|
||||
// Change title from namewithlink column.
|
||||
$this->get_column('badge:namewithlink')->set_title(new lang_string('name'));
|
||||
// Change title of the `namewithimagelink` column to 'Name'.
|
||||
$this->get_column('badge:namewithimagelink')->set_title(new lang_string('name'));
|
||||
|
||||
// Recipients column.
|
||||
if ($canviewdraftbadges) {
|
||||
@@ -193,7 +187,7 @@ class badges extends system_report {
|
||||
return $OUTPUT->action_icon($badgeurl, $icon, null, null, true);
|
||||
});
|
||||
|
||||
$this->set_initial_sort_column('badge:namewithlink', SORT_ASC);
|
||||
$this->set_initial_sort_column('badge:namewithimagelink', SORT_ASC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,7 @@ class course_badges extends system_report {
|
||||
*/
|
||||
#[\core\attribute\deprecated('course_badges::initialise', since: '4.5', mdl: 'MDL-82503')]
|
||||
protected function initialise(): void {
|
||||
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
|
||||
global $USER;
|
||||
// Our main entity, it contains all of the column definitions that we need.
|
||||
$badgeentity = new badge();
|
||||
@@ -103,7 +103,7 @@ class course_badges extends system_report {
|
||||
*/
|
||||
#[\core\attribute\deprecated('course_badges::can_view', since: '4.5', mdl: 'MDL-82503')]
|
||||
protected function can_view(): bool {
|
||||
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
|
||||
return has_capability('moodle/badges:viewbadges', $this->get_context());
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class course_badges extends system_report {
|
||||
*/
|
||||
#[\core\attribute\deprecated('course_badges::add_columns', since: '4.5', mdl: 'MDL-82503')]
|
||||
protected function add_columns(): void {
|
||||
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
|
||||
$badgeissuedalias = $this->get_entity('badge_issued')->get_table_alias('badge_issued');
|
||||
|
||||
$this->add_columns_from_entities([
|
||||
@@ -156,7 +156,7 @@ class course_badges extends system_report {
|
||||
*/
|
||||
#[\core\attribute\deprecated('course_badges::add_filters', since: '4.5', mdl: 'MDL-82503')]
|
||||
protected function add_filters(): void {
|
||||
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
|
||||
$this->add_filters_from_entities([
|
||||
'badge:name',
|
||||
'badge_issued:issued',
|
||||
|
||||
@@ -118,6 +118,7 @@ Feature: Manage badges
|
||||
| Admin User |
|
||||
| User One |
|
||||
|
||||
@accessibility
|
||||
Scenario: View list of badges with recipients
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
@@ -139,6 +140,7 @@ Feature: Manage badges
|
||||
| Badge #1 | Not available | 2 |
|
||||
| Badge #2 | Available | 1 |
|
||||
| Badge #3 | Available | 0 |
|
||||
And the "Badges" "table" should meet accessibility standards with "best-practice" extra tests
|
||||
|
||||
@_file_upload
|
||||
Scenario: Badge names are not unique anymore
|
||||
|
||||
@@ -125,6 +125,7 @@ final class badges_test extends core_reportbuilder_testcase {
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname', 'sortenabled' => 1]);
|
||||
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:namewithlink']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:namewithimagelink']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:criteria']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:image']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'badge:language']);
|
||||
@@ -146,11 +147,12 @@ final class badges_test extends core_reportbuilder_testcase {
|
||||
['id' => $badgeone->id]), ($badgeone->name));
|
||||
|
||||
// First badge, issued to user one.
|
||||
[$badgename, $fullname, $namewithlink, $criteria, $image, $language, $version, $status, $expiry, $tag, $expires,
|
||||
$visible, $coursename] = array_values($content[0]);
|
||||
[$badgename, $fullname, $namewithlink, $namewithimagelink, $criteria, $image, $language, $version, $status, $expiry, $tag,
|
||||
$expires, $visible, $coursename] = array_values($content[0]);
|
||||
$this->assertEquals($badgeone->name, $badgename);
|
||||
$this->assertEquals(fullname($user1), $fullname);
|
||||
$this->assertEquals($expectedbadgeonelink, $namewithlink);
|
||||
$this->assertEquals($image . ' ' . $expectedbadgeonelink, $namewithimagelink);
|
||||
$this->assertStringContainsString('Awarded by: Manager', $criteria);
|
||||
$this->assertStringContainsString('Image caption', $image);
|
||||
$this->assertEquals('German', $language);
|
||||
@@ -163,11 +165,12 @@ final class badges_test extends core_reportbuilder_testcase {
|
||||
$this->assertEquals('PHPUnit test site', $coursename);
|
||||
|
||||
// First badge, issued to user two.
|
||||
[$badgename, $fullname, $namewithlink, $criteria, $image, $language, $version, $status, $expiry, $tag, $expires,
|
||||
$visible, $coursename] = array_values($content[1]);
|
||||
[$badgename, $fullname, $namewithlink, $namewithimagelink, $criteria, $image, $language, $version, $status, $expiry, $tag,
|
||||
$expires, $visible, $coursename] = array_values($content[1]);
|
||||
$this->assertEquals($badgeone->name, $badgename);
|
||||
$this->assertEquals(fullname($user2), $fullname);
|
||||
$this->assertEquals($expectedbadgeonelink, $namewithlink);
|
||||
$this->assertEquals($image . ' ' . $expectedbadgeonelink, $namewithimagelink);
|
||||
$this->assertStringContainsString('Awarded by: Manager', $criteria);
|
||||
$this->assertStringContainsString('Image caption', $image);
|
||||
$this->assertEquals('German', $language);
|
||||
@@ -183,11 +186,12 @@ final class badges_test extends core_reportbuilder_testcase {
|
||||
['id' => $badgetwo->id]), ($badgetwo->name));
|
||||
|
||||
// Course badge, not issues to any users.
|
||||
[$badgename, $fullname, $namewithlink, $criteria, $image, $language, $version, $status, $expiry, $tag, $expires,
|
||||
$visible, $coursename] = array_values($content[2]);
|
||||
[$badgename, $fullname, $namewithlink, $namewithimagelink, $criteria, $image, $language, $version, $status, $expiry, $tag,
|
||||
$expires, $visible, $coursename] = array_values($content[2]);
|
||||
$this->assertEquals($badgetwo->name, $badgename);
|
||||
$this->assertEmpty($fullname);
|
||||
$this->assertEquals($expectedbadgetwolink, $namewithlink);
|
||||
$this->assertEquals($image . ' ' . $expectedbadgetwolink, $namewithimagelink);
|
||||
$this->assertEquals('Criteria for this badge have not been set up yet.', $criteria);
|
||||
$this->assertStringContainsString('Image caption', $image);
|
||||
$this->assertEquals('English', $language);
|
||||
|
||||
+9
-4
@@ -32,7 +32,9 @@
|
||||
* @todo MDL-36050 improve capability check on stick blocks, so we can check user capability before sending images.
|
||||
*/
|
||||
function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
|
||||
global $DB, $CFG, $USER;
|
||||
global $CFG;
|
||||
|
||||
require_once("{$CFG->dirroot}/user/lib.php");
|
||||
|
||||
if ($context->contextlevel != CONTEXT_BLOCK) {
|
||||
send_file_not_found();
|
||||
@@ -51,9 +53,12 @@ function block_html_pluginfile($course, $birecord_or_cm, $context, $filearea, $a
|
||||
if (!core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) {
|
||||
send_file_not_found();
|
||||
}
|
||||
} else if ($parentcontext->contextlevel === CONTEXT_USER && $parentcontext->instanceid != $USER->id) {
|
||||
// The block is in the context of a user, it is only visible to the user who it belongs to.
|
||||
send_file_not_found();
|
||||
} else if ($parentcontext->contextlevel === CONTEXT_USER) {
|
||||
$user = core_user::get_user($parentcontext->instanceid, '*', MUST_EXIST);
|
||||
$extracaps = block_method_result('html', 'get_extra_capabilities');
|
||||
if (!user_can_view_profile($user, null, $parentcontext) || !has_any_capability($extracaps, $context)) {
|
||||
send_file_not_found();
|
||||
}
|
||||
}
|
||||
// At this point there is no way to check SYSTEM context, so ignoring it.
|
||||
}
|
||||
|
||||
@@ -297,6 +297,7 @@ final class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$this->setUser($user);
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher');
|
||||
|
||||
$block = $this->create_course_block($course, $title, $content, $format);
|
||||
$context = \context_block::instance($block->instance->id);
|
||||
@@ -324,6 +325,7 @@ final class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$this->setUser($user);
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher');
|
||||
|
||||
$block = $this->create_course_block($course, $title, $content, $format);
|
||||
$context = \context_block::instance($block->instance->id);
|
||||
@@ -369,6 +371,7 @@ final class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$this->setUser($user2);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
|
||||
$courseblock = $this->create_course_block($course, $title, $content, $blockformat);
|
||||
$coursecontext = \context_block::instance($courseblock->instance->id);
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
}}
|
||||
|
||||
<div id="block-myoverview-{{uniqid}}" class="block-myoverview block-cards" data-region="myoverview" role="navigation">
|
||||
<hr class="mt-0"/>
|
||||
|
||||
<div role="search" data-region="filter" class="d-flex align-items-center my-2" aria-label="{{#str}} aria:controls, block_myoverview {{/str}}">
|
||||
<div class="row g-0">
|
||||
{{> block_myoverview/nav-grouping-selector }}
|
||||
|
||||
@@ -44,6 +44,7 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
global $DB, $FULLME;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
@@ -77,6 +78,7 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
global $DB, $FULLME;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
@@ -141,6 +143,7 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
global $DB, $FULLME;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
@@ -237,6 +240,7 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
global $DB, $CFG;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
// Enable MathJax filter in content and headings.
|
||||
$this->configure_filters([
|
||||
@@ -362,6 +366,7 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
public function test_get_dashboard_blocks_default_dashboard_including_sticky_block(): void {
|
||||
global $PAGE, $DB;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set.
|
||||
@@ -410,6 +415,7 @@ final class externallib_test extends externallib_advanced_testcase {
|
||||
public function test_get_dashboard_blocks_custom_user_dashboard(): void {
|
||||
global $PAGE, $DB;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set.
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ class footer_options_exporter extends exporter {
|
||||
*/
|
||||
protected function get_manage_subscriptions_link(): ?string {
|
||||
if (calendar_user_can_add_event($this->calendar->course)) {
|
||||
$managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php');
|
||||
$managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php', ['course' => $this->calendar->courseid]);
|
||||
return $managesubscriptionurl->out(true);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -213,7 +213,8 @@ class create extends \moodleform {
|
||||
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
|
||||
$group[] = $mform->createElement('date_time_selector', 'timedurationuntil', '');
|
||||
$group[] = $mform->createElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
|
||||
$group[] = $mform->createElement('text', 'timedurationminutes', get_string('durationminutes', 'calendar'));
|
||||
$group[] = $mform->createElement('text', 'timedurationminutes',
|
||||
get_string('durationminutes', 'calendar'), 'maxlength="7" size="7"');
|
||||
|
||||
$mform->addGroup($group, 'durationgroup', get_string('eventduration', 'calendar'), '<br />', false);
|
||||
$mform->setAdvanced('durationgroup');
|
||||
@@ -227,6 +228,9 @@ class create extends \moodleform {
|
||||
|
||||
$mform->setType('timedurationminutes', PARAM_INT);
|
||||
$mform->disabledIf('timedurationminutes', 'duration', 'noteq', 2);
|
||||
$mform->addGroupRule('durationgroup', [
|
||||
'timedurationminutes' => [[get_string('maximumchars', '', 7), 'maxlength', 7, 'server']],
|
||||
]);
|
||||
|
||||
$mform->setDefault('duration', 0);
|
||||
}
|
||||
@@ -238,11 +242,12 @@ class create extends \moodleform {
|
||||
*/
|
||||
protected function add_event_repeat_elements($mform) {
|
||||
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null);
|
||||
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="10" size="10"');
|
||||
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="3" size="3"');
|
||||
$mform->setType('repeats', PARAM_INT);
|
||||
$mform->setDefault('repeats', 1);
|
||||
$mform->disabledIf('repeats', 'repeat', 'notchecked');
|
||||
$mform->setAdvanced('repeat');
|
||||
$mform->setAdvanced('repeats');
|
||||
$mform->addRule('repeats', get_string('maximumchars', '', 3), 'maxlength', 3, 'server');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@ class event_form extends moodleform {
|
||||
$group[] =& $mform->createElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
|
||||
$group[] =& $mform->createElement('date_time_selector', 'timedurationuntil', '');
|
||||
$group[] =& $mform->createElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
|
||||
$group[] =& $mform->createElement('text', 'timedurationminutes', get_string('durationminutes', 'calendar'));
|
||||
$group[] =& $mform->createElement('text', 'timedurationminutes',
|
||||
get_string('durationminutes', 'calendar'), 'maxlength="7" size="7"');
|
||||
|
||||
$mform->addGroup($group, 'durationgroup', '', '<br />', false);
|
||||
|
||||
@@ -134,6 +135,9 @@ class event_form extends moodleform {
|
||||
|
||||
$mform->setType('timedurationminutes', PARAM_INT);
|
||||
$mform->disabledIf('timedurationminutes','duration','noteq', 2);
|
||||
$mform->addGroupRule('durationgroup', [
|
||||
'timedurationminutes' => [[get_string('maximumchars', '', 7), 'maxlength', 7, 'server']],
|
||||
]);
|
||||
|
||||
$mform->setDefault('duration', ($hasduration)?1:0);
|
||||
|
||||
@@ -141,10 +145,11 @@ class event_form extends moodleform {
|
||||
|
||||
$mform->addElement('header', 'repeatevents', get_string('repeatedevents', 'calendar'));
|
||||
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null);
|
||||
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="10" size="10"');
|
||||
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="3" size="3"');
|
||||
$mform->setType('repeats', PARAM_INT);
|
||||
$mform->setDefault('repeats', 1);
|
||||
$mform->disabledIf('repeats','repeat','notchecked');
|
||||
$mform->addRule('repeats', get_string('maximumchars', '', 3), 'maxlength', 3, 'server');
|
||||
|
||||
} else if ($repeatedevents) {
|
||||
|
||||
|
||||
+2
-2
@@ -3642,7 +3642,7 @@ function calendar_output_fragment_event_form($args) {
|
||||
$groupcoursedata = groups_get_course_data($courseid);
|
||||
$formoptions['groups'] = [];
|
||||
foreach ($groupcoursedata->groups as $groupid => $groupdata) {
|
||||
$formoptions['groups'][$groupid] = $groupdata->name;
|
||||
$formoptions['groups'][$groupid] = format_string($groupdata->name, false, ['context' => $context]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3686,7 +3686,7 @@ function calendar_output_fragment_event_form($args) {
|
||||
$groupcoursedata = groups_get_course_data($event->courseid);
|
||||
$formoptions['groups'] = [];
|
||||
foreach ($groupcoursedata->groups as $groupid => $groupdata) {
|
||||
$formoptions['groups'][$groupid] = $groupdata->name;
|
||||
$formoptions['groups'][$groupid] = format_string($groupdata->name, false, ['context' => $context]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ Feature: Perform basic calendar functionality
|
||||
| teacher1 | C1 | teacher |
|
||||
| admin | C1 | editingteacher |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber |
|
||||
| Group 1 | C1 | G1 |
|
||||
| name | course | idnumber |
|
||||
| <span lang="en" class="multilang">Group</span><span lang="es" class="multilang">Grupo</span> 1 | C1 | G1 |
|
||||
And the following "group members" exist:
|
||||
| user | group |
|
||||
| student1 | G1 |
|
||||
@@ -75,7 +75,9 @@ Feature: Perform basic calendar functionality
|
||||
|
||||
@javascript
|
||||
Scenario: Create a group event
|
||||
Given I log in as "teacher1"
|
||||
Given the "multilang" filter is "on"
|
||||
And the "multilang" filter applies to "content and headings"
|
||||
And I log in as "teacher1"
|
||||
And I follow "Full calendar"
|
||||
And I set the field "course" to "Course 1"
|
||||
And I create a calendar event:
|
||||
@@ -322,18 +324,23 @@ Feature: Perform basic calendar functionality
|
||||
|
||||
@javascript
|
||||
Scenario: Changing the event type should clear previous data
|
||||
Given I am on the "Course 1" course page logged in as admin
|
||||
Given the "multilang" filter is "on"
|
||||
And the "multilang" filter applies to "content and headings"
|
||||
And I am on the "Course 1" course page logged in as admin
|
||||
And I follow "Course calendar"
|
||||
And I press "New event"
|
||||
And I set the following fields to these values:
|
||||
| Event title | Group 1 event |
|
||||
| Type of event | Group |
|
||||
And I should see "Group 1"
|
||||
And I should not see "GroupGrupo 1"
|
||||
And I press "Save"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Course calendar"
|
||||
And I click on "Group 1 event" "link"
|
||||
And I should see "Group event"
|
||||
And I should see "Group 1"
|
||||
And I should not see "GroupGrupo 1"
|
||||
When I click on "Edit" "button" in the "Group 1 event" "dialogue"
|
||||
And I set the following fields to these values:
|
||||
| Event title | My own user event |
|
||||
@@ -369,7 +376,9 @@ Feature: Perform basic calendar functionality
|
||||
|
||||
@javascript
|
||||
Scenario: Changing the event type from user to anything else should work
|
||||
Given I log in as "teacher1"
|
||||
Given the "multilang" filter is "on"
|
||||
And the "multilang" filter applies to "content and headings"
|
||||
And I log in as "teacher1"
|
||||
# We need this so we can see the groups.
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
|
||||
+4
-2
@@ -144,8 +144,10 @@ $headingstr = get_string('calendar', 'core_calendar');
|
||||
// If the user is on the course page,
|
||||
// then make the course name linkable to ease the user's navigation to the course page.
|
||||
if ($iscoursecalendar) {
|
||||
$url = new \moodle_url('/course/view.php', ['id' => $courseid]);
|
||||
$linkcourse = html_writer::link($url, $course->shortname);
|
||||
$linkcourse = html_writer::link(
|
||||
course_get_url($course),
|
||||
format_string($course->shortname, options: ['context' => $PAGE->context]),
|
||||
);
|
||||
$headingstr = "{$headingstr}: {$linkcourse}";
|
||||
}
|
||||
$PAGE->set_heading($headingstr, false);
|
||||
|
||||
@@ -71,7 +71,7 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
final: true,
|
||||
)]
|
||||
public function management_heading() {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +187,7 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
$a->parentcategory = $parent->get_formatted_name();
|
||||
$textlabel = get_string('categorysubcategoryof', 'moodle', $a);
|
||||
}
|
||||
$courseicon = $this->output->pix_icon('i/course', get_string('courses'));
|
||||
$courseicon = $this->output->pix_icon('i/course', get_string('courses'), 'core', ['class' => 'ps-1']);
|
||||
$bcatinput = array(
|
||||
'id' => 'categorylistitem' . $category->id,
|
||||
'type' => 'checkbox',
|
||||
@@ -1286,7 +1286,7 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
final: true,
|
||||
)]
|
||||
public function course_search_form() {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -71,7 +71,7 @@ class completion {
|
||||
*/
|
||||
#[\core\attribute\deprecated(null, mdl: 'MDL-82467', since: '4.5')]
|
||||
public static function get_days(?int $value, stdClass $row): ?int {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
|
||||
// Do not show anything if there is no userid.
|
||||
if (!$row->userid) {
|
||||
|
||||
@@ -10,6 +10,6 @@ define("core_courseformat/local/courseeditor/dndsectionitem",["exports","core/re
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&null===this.section.component&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;this.getDraggableData&&(null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value))}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section,_this$section2;if(null!==(_this$section=this.section)&&void 0!==_this$section&&_this$section.component&&!0===(null==dropdata?void 0:dropdata.hasdelegatedsection))return!1;const firstcmid=null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section3;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section3=this.section)||void 0===_this$section3?void 0:_this$section3.cmlist[0])}}}return _exports.default=_default,_exports.default}));
|
||||
class _default extends _reactive.BaseComponent{configDragDrop(sectionid,state,fullregion){let isDndAllowed=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];this.id=sectionid,void 0===this.section&&(this.section=state.section.get(this.id)),void 0===this.course&&(this.course=state.course),this.section.number>0&&null===this.section.component&&(this.getDraggableData=this._getDraggableData),this.fullregion=fullregion,this.reactive.isEditing&&this.reactive.supportComponents&&isDndAllowed&&(this.dragdrop=new _reactive.DragDrop(this),this.classes=this.dragdrop.getClasses())}destroy(){void 0!==this.dragdrop&&this.dragdrop.unregister()}setDraggable(value){var _this$dragdrop;this.getDraggableData&&(null===(_this$dragdrop=this.dragdrop)||void 0===_this$dragdrop||_this$dragdrop.setDraggable(value))}dragStart(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!0)}dragEnd(dropdata){this.reactive.dispatch("sectionDrag",[dropdata.id],!1)}_getDraggableData(){return this.reactive.getExporter().sectionDraggableData(this.reactive.state,this.id)}validateDropData(dropdata){if("cm"===(null==dropdata?void 0:dropdata.type)){var _this$section,_this$section2;if(null!==(_this$section=this.section)&&void 0!==_this$section&&_this$section.component&&!0===(null==dropdata?void 0:dropdata.hasdelegatedsection))return!1;const firstcmid=null===(_this$section2=this.section)||void 0===_this$section2?void 0:_this$section2.cmlist[0];return dropdata.id!==firstcmid}return!1}showDropZone(){this.element.classList.add(this.classes.DROPZONE)}hideDropZone(){this.element.classList.remove(this.classes.DROPZONE)}drop(dropdata,event){if("cm"==dropdata.type){var _this$section3;const mutation=event.altKey?"cmDuplicate":"cmMove";this.reactive.dispatch(mutation,[dropdata.id],this.id,null===(_this$section3=this.section)||void 0===_this$section3?void 0:_this$section3.cmlist[0])}}}return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=dndsectionitem.min.js.map
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/cm",["exports","core_courseformat/lo
|
||||
* @class core_courseformat/local/courseindex/cm
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");if((window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&this.element.scrollIntoView({block:"center"}),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),!cm.uservisible||!cm.url){const element=this.getElement(this.selectors.CM_NAME);this.addEventListener(element,"click",this._activityAnchor),document.getElementById(cm.anchor)||element.setAttribute("href",this._getActivitySectionURL(cm))}}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(!completionElement||completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element),{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor)){this.reactive.dispatch("sectionContentCollapsed",[cm.sectionid],!1);const pendingAnchor=new _pending.default("courseformat/activity:openAnchor");setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id),pendingAnchor.resolve()}),50)}else event.preventDefault(),window.location=this._getActivitySectionURL(cm)}_getActivitySectionURL(cm){let section=this.reactive.get("section",cm.sectionid);if(section.component&§ion.parentsectionid&&(section=this.reactive.get("section",section.parentsectionid)),!section)return"#";const sectionurl=section.sectionurl.split("#")[0];return"".concat(sectionurl,"#").concat(cm.anchor)}}return _exports.default=Component,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']",DND_ALLOWED:"[data-courseindexdndallowed='true']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){document.querySelector(this.selectors.DND_ALLOWED)&&this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");if((window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&this.element.scrollIntoView({block:"center"}),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),!cm.uservisible||!cm.url){const element=this.getElement(this.selectors.CM_NAME);this.addEventListener(element,"click",this._activityAnchor),document.getElementById(cm.anchor)||element.setAttribute("href",this._getActivitySectionURL(cm))}}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(!completionElement||completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element),{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor)){this.reactive.dispatch("sectionContentCollapsed",[cm.sectionid],!1);const pendingAnchor=new _pending.default("courseformat/activity:openAnchor");setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id),pendingAnchor.resolve()}),50)}else event.preventDefault(),window.location=this._getActivitySectionURL(cm)}_getActivitySectionURL(cm){let section=this.reactive.get("section",cm.sectionid);if(section.component&§ion.parentsectionid&&(section=this.reactive.get("section",section.parentsectionid)),!section)return"#";const sectionurl=section.sectionurl.split("#")[0];return"".concat(sectionurl,"#").concat(cm.anchor)}}return _exports.default=Component,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=cm.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/section",["exports","core_courseform
|
||||
* @class core_courseformat/local/courseindex/section
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_sectiontitle=_interopRequireDefault(_sectiontitle),_dndsection=_interopRequireDefault(_dndsection);class Component extends _dndsection.default{create(){this.name="courseindex_section",this.selectors={SECTION:"[data-for='section']",SECTION_ITEM:"[data-for='section_item']",SECTION_TITLE:"[data-for='section_title']",CM_LAST:'[data-for="cm"]:last-child'},this.classes={SECTIONHIDDEN:"dimmed",SECTIONCURRENT:"current",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",OVERLAYBORDERS:"overlay-preview-borders"},this.id=this.element.dataset.id,this.isPageItem=!1}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configState(state);const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(this.reactive.isEditing&&this.reactive.supportComponents){const titleitem=new _sectiontitle.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(titleitem)}const section=state.section.get(this.id);window.location.href==section.sectionurl.replace(/&/g,"&")&&(this.reactive.dispatch("setPageItem","section",this.id),sectionItem.scrollIntoView())}getWatchers(){return[{watch:"section[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}getLastCm(){return this.getElement(this.selectors.CM_LAST)}_refreshSection(_ref){var _element$hasrestricti,_element$dragging,_element$locked;let{element:element}=_ref;const sectionItem=this.getElement(this.selectors.SECTION_ITEM);sectionItem.classList.toggle(this.classes.SECTIONHIDDEN,!element.visible),sectionItem.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hasrestricti=element.hasrestrictions)&&void 0!==_element$hasrestricti&&_element$hasrestricti),this.element.classList.toggle(this.classes.SECTIONCURRENT,element.current),this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked,this.getElement(this.selectors.SECTION_TITLE).innerHTML=element.title}_refreshPageItem(_ref2){var _element$pageItem,_this$pageItem;let{element:element,state:state}=_ref2;if(!element.pageItem)return;const containsPageItem=this._isPageItemInThisSection(element.pageItem);if(!containsPageItem||this._isParentSectionIndexCollapsed(state))return this.pageItem=!1,void this.getElement(this.selectors.SECTION_ITEM).classList.remove(this.classes.PAGEITEM);!state.section.get(this.id).indexcollapsed||null!==(_element$pageItem=element.pageItem)&&void 0!==_element$pageItem&&_element$pageItem.isStatic?this.pageItem="section"==element.pageItem.type&&element.pageItem.id==this.id:this.pageItem=containsPageItem;this.getElement(this.selectors.SECTION_ITEM).classList.toggle(this.classes.PAGEITEM,null!==(_this$pageItem=this.pageItem)&&void 0!==_this$pageItem&&_this$pageItem),this.pageItem&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}_isPageItemInThisSection(pageItem){if(pageItem.sectionId==this.id)return!0;return!!this.element.querySelector("".concat(this.selectors.SECTION,'[data-id="').concat(pageItem.sectionId,'"]'))}_isParentSectionIndexCollapsed(state){const parentElement=this.element.parentElement.closest(this.selectors.SECTION);if(!parentElement||!parentElement.dataset.id)return null;return!!state.section.get(parentElement.dataset.id).indexcollapsed}async addOverlay(){this.element.classList.add(this.classes.OVERLAYBORDERS)}removeOverlay(){this.element.classList.remove(this.classes.OVERLAYBORDERS)}}return _exports.default=Component,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_sectiontitle=_interopRequireDefault(_sectiontitle),_dndsection=_interopRequireDefault(_dndsection);class Component extends _dndsection.default{create(){this.name="courseindex_section",this.selectors={SECTION:"[data-for='section']",SECTION_ITEM:"[data-for='section_item']",SECTION_TITLE:"[data-for='section_title']",CM_LAST:'[data-for="cm"]:last-child',DND_ALLOWED:"[data-courseindexdndallowed='true']"},this.classes={SECTIONHIDDEN:"dimmed",SECTIONCURRENT:"current",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",OVERLAYBORDERS:"overlay-preview-borders"},this.id=this.element.dataset.id,this.isPageItem=!1}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configState(state);const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(this.reactive.isEditing&&this.reactive.supportComponents&&document.querySelector(this.selectors.DND_ALLOWED)){const titleitem=new _sectiontitle.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(titleitem)}const section=state.section.get(this.id);window.location.href==section.sectionurl.replace(/&/g,"&")&&(this.reactive.dispatch("setPageItem","section",this.id),sectionItem.scrollIntoView())}getWatchers(){return[{watch:"section[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}getLastCm(){return this.getElement(this.selectors.CM_LAST)}_refreshSection(_ref){var _element$hasrestricti,_element$dragging,_element$locked;let{element:element}=_ref;const sectionItem=this.getElement(this.selectors.SECTION_ITEM);sectionItem.classList.toggle(this.classes.SECTIONHIDDEN,!element.visible),sectionItem.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hasrestricti=element.hasrestrictions)&&void 0!==_element$hasrestricti&&_element$hasrestricti),this.element.classList.toggle(this.classes.SECTIONCURRENT,element.current),this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked,this.getElement(this.selectors.SECTION_TITLE).innerHTML=element.title}_refreshPageItem(_ref2){var _element$pageItem,_this$pageItem;let{element:element,state:state}=_ref2;if(!element.pageItem)return;const containsPageItem=this._isPageItemInThisSection(element.pageItem);if(!containsPageItem||this._isParentSectionIndexCollapsed(state))return this.pageItem=!1,void this.getElement(this.selectors.SECTION_ITEM).classList.remove(this.classes.PAGEITEM);!state.section.get(this.id).indexcollapsed||null!==(_element$pageItem=element.pageItem)&&void 0!==_element$pageItem&&_element$pageItem.isStatic?this.pageItem="section"==element.pageItem.type&&element.pageItem.id==this.id:this.pageItem=containsPageItem;this.getElement(this.selectors.SECTION_ITEM).classList.toggle(this.classes.PAGEITEM,null!==(_this$pageItem=this.pageItem)&&void 0!==_this$pageItem&&_this$pageItem),this.pageItem&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}_isPageItemInThisSection(pageItem){if(pageItem.sectionId==this.id)return!0;return!!this.element.querySelector("".concat(this.selectors.SECTION,'[data-id="').concat(pageItem.sectionId,'"]'))}_isParentSectionIndexCollapsed(state){const parentElement=this.element.parentElement.closest(this.selectors.SECTION);if(!parentElement||!parentElement.dataset.id)return null;return!!state.section.get(parentElement.dataset.id).indexcollapsed}async addOverlay(){this.element.classList.add(this.classes.OVERLAYBORDERS)}removeOverlay(){this.element.classList.remove(this.classes.OVERLAYBORDERS)}}return _exports.default=Component,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=section.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/sectiontitle",["exports","core_cours
|
||||
* @class core_courseformat/local/courseindex/sectiontitle
|
||||
* @copyright 2021 Ferran Recio <ferran@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndsectionitem=(obj=_dndsectionitem)&&obj.__esModule?obj:{default:obj};class Component extends _dndsectionitem.default{create(descriptor){this.name="courseindex_sectiontitle",this.id=descriptor.id,this.section=descriptor.section,this.course=descriptor.course,this.fullregion=descriptor.fullregion,this.section.number>0&&null===this.section.component&&(this.getDraggableData=this._getDraggableData)}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id,state,this.fullregion)}}return _exports.default=Component,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndsectionitem=(obj=_dndsectionitem)&&obj.__esModule?obj:{default:obj};class Component extends _dndsectionitem.default{create(descriptor){this.name="courseindex_sectiontitle",this.id=descriptor.id,this.section=descriptor.section,this.course=descriptor.course,this.fullregion=descriptor.fullregion,this.section.number>0&&null===this.section.component&&(this.getDraggableData=this._getDraggableData)}static init(target,selectors){return new this({element:document.getElementById(target),selectors:selectors})}stateReady(state){this.configDragDrop(this.id,state,this.fullregion,!!document.querySelector('[data-courseindexdndallowed="true"]'))}}return _exports.default=Component,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=sectiontitle.min.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"sectiontitle.min.js","sources":["../../../src/local/courseindex/sectiontitle.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 * Course index section title component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/sectiontitle\n * @class core_courseformat/local/courseindex/sectiontitle\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';\n\nexport default class Component extends DndSectionItem {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'courseindex_sectiontitle';\n\n this.id = descriptor.id;\n this.section = descriptor.section;\n this.course = descriptor.course;\n this.fullregion = descriptor.fullregion;\n\n // Prevent topic zero and delegated sections from being draggable.\n if (this.section.number > 0 && this.section.component === null) {\n this.getDraggableData = this._getDraggableData;\n }\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configDragDrop(this.id, state, this.fullregion);\n }\n}\n"],"names":["Component","DndSectionItem","create","descriptor","name","id","section","course","fullregion","this","number","component","getDraggableData","_getDraggableData","target","selectors","element","document","getElementById","stateReady","state","configDragDrop"],"mappings":";;;;;;;;;;mKA4BqBA,kBAAkBC,wBAOnCC,OAAOC,iBAEEC,KAAO,gCAEPC,GAAKF,WAAWE,QAChBC,QAAUH,WAAWG,aACrBC,OAASJ,WAAWI,YACpBC,WAAaL,WAAWK,WAGzBC,KAAKH,QAAQI,OAAS,GAAgC,OAA3BD,KAAKH,QAAQK,iBACnCC,iBAAmBH,KAAKI,+BAWzBC,OAAQC,kBACT,IAAIN,KAAK,CACZO,QAASC,SAASC,eAAeJ,QACjCC,UAAAA,YASRI,WAAWC,YACFC,eAAeZ,KAAKJ,GAAIe,MAAOX,KAAKD"}
|
||||
{"version":3,"file":"sectiontitle.min.js","sources":["../../../src/local/courseindex/sectiontitle.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 * Course index section title component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/sectiontitle\n * @class core_courseformat/local/courseindex/sectiontitle\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';\n\nexport default class Component extends DndSectionItem {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor\n */\n create(descriptor) {\n // Optional component name for debugging.\n this.name = 'courseindex_sectiontitle';\n\n this.id = descriptor.id;\n this.section = descriptor.section;\n this.course = descriptor.course;\n this.fullregion = descriptor.fullregion;\n\n // Prevent topic zero and delegated sections from being draggable.\n if (this.section.number > 0 && this.section.component === null) {\n this.getDraggableData = this._getDraggableData;\n }\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configDragDrop(this.id, state, this.fullregion, !!document.querySelector(`[data-courseindexdndallowed=\"true\"]`));\n }\n}\n"],"names":["Component","DndSectionItem","create","descriptor","name","id","section","course","fullregion","this","number","component","getDraggableData","_getDraggableData","target","selectors","element","document","getElementById","stateReady","state","configDragDrop","querySelector"],"mappings":";;;;;;;;;;mKA4BqBA,kBAAkBC,wBAOnCC,OAAOC,iBAEEC,KAAO,gCAEPC,GAAKF,WAAWE,QAChBC,QAAUH,WAAWG,aACrBC,OAASJ,WAAWI,YACpBC,WAAaL,WAAWK,WAGzBC,KAAKH,QAAQI,OAAS,GAAgC,OAA3BD,KAAKH,QAAQK,iBACnCC,iBAAmBH,KAAKI,+BAWzBC,OAAQC,kBACT,IAAIN,KAAK,CACZO,QAASC,SAASC,eAAeJ,QACjCC,UAAAA,YASRI,WAAWC,YACFC,eAAeZ,KAAKJ,GAAIe,MAAOX,KAAKD,aAAcS,SAASK"}
|
||||
@@ -35,8 +35,9 @@ export default class extends BaseComponent {
|
||||
* @param {number} sectionid the section id
|
||||
* @param {Object} state the initial state
|
||||
* @param {Element} fullregion the complete section region to mark as dragged
|
||||
* @param {Boolean} isDndAllowed Whether drag and drop is allowed in this section.
|
||||
*/
|
||||
configDragDrop(sectionid, state, fullregion) {
|
||||
configDragDrop(sectionid, state, fullregion, isDndAllowed = true) {
|
||||
|
||||
this.id = sectionid;
|
||||
if (this.section === undefined) {
|
||||
@@ -54,7 +55,7 @@ export default class extends BaseComponent {
|
||||
this.fullregion = fullregion;
|
||||
|
||||
// Drag and drop is only available for components compatible course formats.
|
||||
if (this.reactive.isEditing && this.reactive.supportComponents) {
|
||||
if (this.reactive.isEditing && this.reactive.supportComponents && isDndAllowed) {
|
||||
// Init the dropzone.
|
||||
this.dragdrop = new DragDrop(this);
|
||||
// Save dropzone classes.
|
||||
|
||||
@@ -46,6 +46,7 @@ export default class Component extends DndCmItem {
|
||||
this.selectors = {
|
||||
CM_NAME: `[data-for='cm_name']`,
|
||||
CM_COMPLETION: `[data-for='cm_completion']`,
|
||||
DND_ALLOWED: `[data-courseindexdndallowed='true']`,
|
||||
};
|
||||
// Default classes to toggle on refresh.
|
||||
this.classes = {
|
||||
@@ -79,7 +80,9 @@ export default class Component extends DndCmItem {
|
||||
* @param {Object} state the course state.
|
||||
*/
|
||||
stateReady(state) {
|
||||
this.configDragDrop(this.id);
|
||||
if (document.querySelector(this.selectors.DND_ALLOWED)) {
|
||||
this.configDragDrop(this.id);
|
||||
}
|
||||
const cm = state.cm.get(this.id);
|
||||
const course = state.course;
|
||||
// Refresh completion icon.
|
||||
|
||||
@@ -41,6 +41,7 @@ export default class Component extends DndSection {
|
||||
SECTION_ITEM: `[data-for='section_item']`,
|
||||
SECTION_TITLE: `[data-for='section_title']`,
|
||||
CM_LAST: `[data-for="cm"]:last-child`,
|
||||
DND_ALLOWED: `[data-courseindexdndallowed='true']`,
|
||||
};
|
||||
// Default classes to toggle on refresh.
|
||||
this.classes = {
|
||||
@@ -80,7 +81,7 @@ export default class Component extends DndSection {
|
||||
this.configState(state);
|
||||
const sectionItem = this.getElement(this.selectors.SECTION_ITEM);
|
||||
// Drag and drop is only available for components compatible course formats.
|
||||
if (this.reactive.isEditing && this.reactive.supportComponents) {
|
||||
if (this.reactive.isEditing && this.reactive.supportComponents && document.querySelector(this.selectors.DND_ALLOWED)) {
|
||||
// Init the inner dragable element passing the full section as affected region.
|
||||
const titleitem = new SectionTitle({
|
||||
...this,
|
||||
|
||||
@@ -68,6 +68,6 @@ export default class Component extends DndSectionItem {
|
||||
* @param {Object} state the initial state
|
||||
*/
|
||||
stateReady(state) {
|
||||
this.configDragDrop(this.id, state, this.fullregion);
|
||||
this.configDragDrop(this.id, state, this.fullregion, !!document.querySelector(`[data-courseindexdndallowed="true"]`));
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -198,15 +198,21 @@ if (core_communication\api::is_available() && has_capability('moodle/course:upda
|
||||
$communication->show_communication_room_status_notification();
|
||||
}
|
||||
|
||||
// Display a warning if asynchronous backups are pending for this course.
|
||||
$containerattributes = [];
|
||||
if ($PAGE->user_is_editing()) {
|
||||
require_once($CFG->dirroot . '/backup/util/helper/async_helper.class.php');
|
||||
// Display a warning if asynchronous backups are pending for this course.
|
||||
if (async_helper::is_async_pending($course->id, 'course', 'backup')) {
|
||||
echo $OUTPUT->notification(get_string('pendingasyncedit', 'backup'), 'warning');
|
||||
}
|
||||
|
||||
// Allow drag and drop in the course index.
|
||||
$containerattributes = [
|
||||
'data-courseindexdndallowed' => 'true',
|
||||
];
|
||||
}
|
||||
|
||||
echo $renderer->container_start('course-content');
|
||||
echo $renderer->container_start('course-content', attributes: $containerattributes);
|
||||
|
||||
// Include course AJAX.
|
||||
include_course_ajax($course, $modinfo->get_used_module_names());
|
||||
|
||||
+6
-3
@@ -311,18 +311,21 @@ if (has_capability('moodle/course:update', $context)) {
|
||||
core_communication\helper::get_course_communication_status_notification($course);
|
||||
}
|
||||
|
||||
if ($USER->editing == 1) {
|
||||
|
||||
$containerattributes = ['class' => 'course-content'];
|
||||
if ($PAGE->user_is_editing()) {
|
||||
// MDL-65321 The backup libraries are quite heavy, only require the bare minimum.
|
||||
require_once($CFG->dirroot . '/backup/util/helper/async_helper.class.php');
|
||||
|
||||
if (async_helper::is_async_pending($id, 'course', 'backup')) {
|
||||
echo $OUTPUT->notification(get_string('pendingasyncedit', 'backup'), 'warning');
|
||||
}
|
||||
|
||||
// Allow drag and drop in the course index.
|
||||
$containerattributes['data-courseindexdndallowed'] = 'true';
|
||||
}
|
||||
|
||||
// Course wrapper start.
|
||||
echo html_writer::start_tag('div', ['class' => 'course-content']);
|
||||
echo html_writer::start_tag('div', $containerattributes);
|
||||
|
||||
// Make sure that section 0 exists (this function will create one if it is missing).
|
||||
course_create_sections_if_missing($course, 0);
|
||||
|
||||
@@ -110,3 +110,26 @@ Feature: Cohort enrolment management
|
||||
And I set the field "groups[]" to "Beta2 cohort (2)"
|
||||
Then the "members" select box should contain "Bianca McAfee (s3@example.com)"
|
||||
And the "members" select box should contain "Abigail Wyatt (s5@example.com)"
|
||||
|
||||
@javascript
|
||||
Scenario: Editing a cohort enrolment method's role should change participants' role
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | Sandra | Cole | s1@example.com |
|
||||
And the following "cohort members" exist:
|
||||
| user | cohort |
|
||||
| s1 | A1 |
|
||||
When I log in as "teacher001"
|
||||
And I add "Cohort sync" enrolment method in "Course 001" with:
|
||||
| Cohort | Alpha1 |
|
||||
And I should see "Cohort sync (Alpha1 - Student)"
|
||||
And I navigate to course participants
|
||||
And I should see "Student" in the "Sandra Cole" "table_row"
|
||||
And I am on the "Course 001" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Alpha1" "table_row"
|
||||
And I set the field "Assign role" to "Non-editing teacher"
|
||||
And I click on "Save" "button"
|
||||
Then I should see "Cohort sync (Alpha1 - Non-editing teacher)"
|
||||
And I navigate to course participants
|
||||
And I should see "Non-editing teacher" in the "Sandra Cole" "table_row"
|
||||
And I should not see "Student" in the "Sandra Cole" "table_row"
|
||||
|
||||
@@ -89,12 +89,6 @@ if ($mform->is_cancelled()) {
|
||||
$reset = ($instance->status != $data->status);
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$instance->$key = $value;
|
||||
}
|
||||
|
||||
$instance->timemodified = time();
|
||||
|
||||
$plugin->update_instance($instance, $data);
|
||||
|
||||
if ($reset) {
|
||||
|
||||
+2
-2
@@ -420,7 +420,7 @@ class enrol_self_plugin extends enrol_plugin {
|
||||
*/
|
||||
#[\core\attribute\deprecated('enrol_plugin::send_course_welcome_message_to_user', since: '4.4', mdl: 'MDL-4188')]
|
||||
protected function email_welcome_message($instance, $user) {
|
||||
\core\deprecation::emit_deprecation_if_present(__FUNCTION__);
|
||||
\core\deprecation::emit_deprecation(__FUNCTION__);
|
||||
$this->send_course_welcome_message_to_user(
|
||||
instance: $instance,
|
||||
userid: $user->id,
|
||||
@@ -1193,7 +1193,7 @@ class enrol_self_plugin extends enrol_plugin {
|
||||
*/
|
||||
#[\core\attribute\deprecated('enrol_plugin::get_welcome_message_contact', since: '4.4', mdl: 'MDL-4188')]
|
||||
public function get_welcome_email_contact($sendoption, $context) {
|
||||
\core\deprecation::emit_deprecation_if_present(__FUNCTION__);
|
||||
\core\deprecation::emit_deprecation(__FUNCTION__);
|
||||
return $this->get_welcome_message_contact(
|
||||
sendoption: $sendoption,
|
||||
context: $context,
|
||||
|
||||
@@ -204,15 +204,7 @@ abstract class component_gradeitem {
|
||||
* @return bool
|
||||
*/
|
||||
public function is_using_advanced_grading(): bool {
|
||||
if ($this->is_using_scale()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->get_advanced_grading_controller()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return (bool) $this->get_advanced_grading_controller();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ Feature: Converting rubric score to grades
|
||||
As a teacher
|
||||
I need to be able to use different grade settings
|
||||
|
||||
Scenario Outline: Convert rubric scores to grades.
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
@@ -19,9 +19,11 @@ Feature: Converting rubric score to grades
|
||||
And the following "scales" exist:
|
||||
| name | scale |
|
||||
| Test scale 1 | Disappointing, Good, Very good, Excellent |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
|
||||
| assign | Test assignment 1 | Test | C1 | assign1 | <grade> | rubric |
|
||||
|
||||
Scenario Outline: Convert assignment rubric scores to grades.
|
||||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
|
||||
| assign | Test assignment 1 | Test | C1 | assign1 | <grade> | rubric |
|
||||
When I log in as "teacher1"
|
||||
And I change window size to "large"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
@@ -55,3 +57,39 @@ Feature: Converting rubric score to grades
|
||||
| 100 | | 10.00 |
|
||||
| 70 | | 7.00 |
|
||||
| Test scale 1 | | Disappointing |
|
||||
|
||||
Scenario Outline: Convert forum rubric scores with a scale to grades.
|
||||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
|
||||
| forum | Test forum 1 | Test | C1 | forum1 | 100 | rubric |
|
||||
And I change window size to "large"
|
||||
And I am on the "Test forum 1" "forum activity editing" page logged in as teacher1
|
||||
And I expand all fieldsets
|
||||
And I set the following fields to these values:
|
||||
| Whole forum grading > Type | Scale |
|
||||
| Whole forum grading > Scale | Default competence scale |
|
||||
| Grading method | Rubric |
|
||||
And I press "Save and display"
|
||||
And I go to "Test forum 1" advanced grading definition page
|
||||
# Defining a rubric.
|
||||
And I set the following fields to these values:
|
||||
| Name | Forum 1 rubric |
|
||||
| Description | Rubric test description |
|
||||
And I define the following rubric:
|
||||
| Criterion 1 | Level 11 | 1 | Level 12 | 20 | Level 13 | 40 | Level 14 | 50 |
|
||||
| Criterion 2 | Level 21 | 10 | Level 22 | 20 | Level 23 | 30 | | |
|
||||
| Criterion 3 | Level 31 | 5 | Level 32 | 20 | | | | |
|
||||
And I press "Save rubric and make it ready"
|
||||
When I am on the "Test forum 1" "forum activity" page logged in as teacher1
|
||||
And I press "Grade users"
|
||||
And I click on "<criterion1> points" "radio"
|
||||
And I click on "<criterion2> points" "radio"
|
||||
And I click on "<criterion3> points" "radio"
|
||||
And I press "Save"
|
||||
And I am on the "Course 1" "grades > Grader report > View" page
|
||||
Then I should see "<studentgrade>" in the "student1@example.com" "table_row"
|
||||
|
||||
Examples:
|
||||
| studentgrade | criterion1 | criterion2 | criterion3 |
|
||||
| Competent | Level 14 50 | Level 23 30 | Level 32 20 |
|
||||
| Not yet competent | Level 11 1 | Level 21 10 | Level 31 5 |
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ class core_grades_renderer extends plugin_renderer_base {
|
||||
public function group_selector(object $course, ?string $groupactionbaseurl = null): ?string {
|
||||
global $USER;
|
||||
|
||||
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
|
||||
|
||||
if ($groupactionbaseurl !== null) {
|
||||
debugging(
|
||||
@@ -139,7 +139,7 @@ class core_grades_renderer extends plugin_renderer_base {
|
||||
): stdClass {
|
||||
global $SESSION, $COURSE;
|
||||
|
||||
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([$this, __FUNCTION__]);
|
||||
// User search.
|
||||
$searchvalue = optional_param('gpr_search', null, PARAM_NOTAGS);
|
||||
$userid = optional_param('grp_userid', null, PARAM_INT);
|
||||
|
||||
@@ -244,7 +244,7 @@ class behat_grade extends behat_base {
|
||||
*/
|
||||
#[\core\attribute\deprecated('behat_forms::i_set_the_field_to', since: '4.0', final: true)]
|
||||
protected function select_in_gradebook_tabs() {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,6 +307,6 @@ class behat_grade extends behat_base {
|
||||
*/
|
||||
#[\core\attribute\deprecated('behat_forms::i_set_the_field_to', since: '4.1', final: true)]
|
||||
protected function select_in_gradebook_navigation_selector() {
|
||||
\core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]);
|
||||
\core\deprecation::emit_deprecation([self::class, __FUNCTION__]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user