Compare commits
342 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68aa19ba73 | |||
| f9d08701fb | |||
| 324e1fbb78 | |||
| b58a4e8da4 | |||
| e7c0ddb942 | |||
| e9d63ac29c | |||
| fce440eeed | |||
| 5fae2fc7a1 | |||
| 913eb31830 | |||
| 68fbcaf303 | |||
| 20403e0e43 | |||
| 2740e06920 | |||
| a84b51046a | |||
| a19092e71c | |||
| b41a748a98 | |||
| cd96cb300e | |||
| e3c031023f | |||
| c5b4da7257 | |||
| 7edc33a8ce | |||
| 61e9318339 | |||
| f254b2b88f | |||
| 264fe69743 | |||
| 80e2f8473e | |||
| b9b5f4b489 | |||
| dc871f251e | |||
| f972485484 | |||
| 1f9488ccb5 | |||
| 1d8512bc0e | |||
| b751583087 | |||
| 94b897a176 | |||
| 4c6b771c2f | |||
| b8122e1de3 | |||
| a9eae7edf4 | |||
| a7a9150b62 | |||
| c283ed04e0 | |||
| f65e706662 | |||
| 49e1731fbe | |||
| cd320b32b5 | |||
| 8dea153c6b | |||
| ae9fea0b47 | |||
| dc9258574d | |||
| 5b94aaed06 | |||
| a099c8d921 | |||
| 36a3c41b8b | |||
| d7208f8dd0 | |||
| df9c7f755d | |||
| a76583296b | |||
| b56affcfe6 | |||
| 4799f8b7c4 | |||
| 62758b8224 | |||
| cf6aeae427 | |||
| 8ea5b30a5d | |||
| 270f52bf55 | |||
| 1affa30299 | |||
| f61b2edde0 | |||
| d1b2674eea | |||
| 849f392f3b | |||
| 2c73122c60 | |||
| 3b6104d6dc | |||
| be2eb84be9 | |||
| 711b1a5ae9 | |||
| df136f7535 | |||
| 6c996b1570 | |||
| f01179355c | |||
| 414102ed74 | |||
| 24cd545eb8 | |||
| bcf349ed45 | |||
| ae221af121 | |||
| 4f87b28436 | |||
| b3a66b1484 | |||
| 52b5aa5a18 | |||
| de6a44d7d0 | |||
| 21941c6c2a | |||
| 54da1506a1 | |||
| 7f3a1dff28 | |||
| 818182879a | |||
| e995073997 | |||
| bc47266a65 | |||
| 799e988f80 | |||
| 9bae0ce6a7 | |||
| d6a633d7a8 | |||
| 16dff14e56 | |||
| c0d26fff0d | |||
| 3698f34fc3 | |||
| e52ec9edd4 | |||
| 63b4929729 | |||
| 3afbffe6a0 | |||
| 1dd574efd8 | |||
| a426320c54 | |||
| 3dea860e64 | |||
| 69f3e0753e | |||
| 690bad040a | |||
| 22c30c160e | |||
| b28b6066b4 | |||
| c4e993d546 | |||
| 5b7d2a1992 | |||
| 23846c5450 | |||
| b6d65c77cf | |||
| f77ec5e7fd | |||
| 9031cf789d | |||
| 010aa5ee16 | |||
| 2014c08636 | |||
| f5531458aa | |||
| a6e2d950e7 | |||
| dddc4cc410 | |||
| 2e16fca0f1 | |||
| 30d1832f45 | |||
| 24c44b38be | |||
| d848c604dd | |||
| 9a4034f3bf | |||
| 4c1d448b5b | |||
| f92e83fe36 | |||
| d500b5c62d | |||
| 65fad5f93f | |||
| 7ea8dc7112 | |||
| 276476233d | |||
| a565c5d93d | |||
| a72e1f1e0d | |||
| 2d86d87012 | |||
| 27ea958dfe | |||
| 2410df1fd2 | |||
| ccb1a3b39f | |||
| ce2058c4c2 | |||
| b7fb146cca | |||
| 2695a394fe | |||
| 05e9ae07cd | |||
| c14603ecd1 | |||
| 5d2d5bc542 | |||
| 3d92acac8a | |||
| ac115326f9 | |||
| cb94a54412 | |||
| 0c2e30809a | |||
| a6c9132e73 | |||
| 323276969f | |||
| 18a99edcb9 | |||
| 9d6fff9671 | |||
| c4dad5b5e6 | |||
| 9c4204c96b | |||
| 15da5ba48e | |||
| c7e65064b1 | |||
| 32702452e7 | |||
| 2e1151bbb3 | |||
| 2e7cc5e17c | |||
| 513ac0c086 | |||
| 9f7752d6bc | |||
| 79071cdd87 | |||
| 69962f36d7 | |||
| 76ef9c5d1a | |||
| b67b742e40 | |||
| eece2df651 | |||
| 61ef211525 | |||
| 535fcef31e | |||
| 817757c088 | |||
| 054d5c0f04 | |||
| adcd50bea0 | |||
| 3018a35249 | |||
| 1d217301ad | |||
| 4dc6c7a79d | |||
| 6d6a39e34d | |||
| 1b169c82be | |||
| 91376cdb69 | |||
| 3f5477f8a0 | |||
| 2166e3e681 | |||
| 9e3b15f31e | |||
| dfe187a59d | |||
| 45725c2408 | |||
| 8aea2ef66b | |||
| f065061f08 | |||
| 92df285f49 | |||
| c72839d6d0 | |||
| f33b8bb576 | |||
| 7ccf8f2fe4 | |||
| 0997a89e39 | |||
| f63aed03b8 | |||
| cf3215cff4 | |||
| d26512cb63 | |||
| b4ccc75a03 | |||
| 49f994fb03 | |||
| ebd1eb3726 | |||
| 88d21b999c | |||
| 946cbedf54 | |||
| 6ec958e639 | |||
| 737682c750 | |||
| 138e5b9996 | |||
| f3f7f28015 | |||
| 2bada93f17 | |||
| a5d64155ad | |||
| e511a7fdc4 | |||
| 767c610be1 | |||
| c0f4d9e170 | |||
| e4c7028c20 | |||
| 375ec1c034 | |||
| 1c3b9af49a | |||
| d4d2ef9119 | |||
| fdcc9383b1 | |||
| d1d57dc12d | |||
| 27a6bf394d | |||
| 5e4aab248f | |||
| c32142cf80 | |||
| ea90581cb1 | |||
| 505366bed8 | |||
| a6a5130a65 | |||
| a53238a07b | |||
| e085678bf2 | |||
| ad3ac443a9 | |||
| 81da134c2c | |||
| dc4a36c3b1 | |||
| 946120eeee | |||
| 82d35db47a | |||
| 83c3d04702 | |||
| 16736110d8 | |||
| 22f1a27aa3 | |||
| 30e6bf75dd | |||
| 9c4a0b8490 | |||
| da03cf8972 | |||
| 0b3e3d7c54 | |||
| 1b8ced636c | |||
| 2c73fbfc3d | |||
| 5e3c934691 | |||
| df3efafbcf | |||
| e2f2de4885 | |||
| 5279159083 | |||
| 8392586599 | |||
| 26a0581c1b | |||
| 6037a3dde0 | |||
| 471d1a4b6d | |||
| 3db93ec2a2 | |||
| ff6ac0b2fd | |||
| 57d5be04f8 | |||
| c9d3c97e74 | |||
| a772325afc | |||
| 4c1c1aa6c6 | |||
| 05e79651b6 | |||
| a8c569c368 | |||
| 12afd60ba2 | |||
| c4fb2a2141 | |||
| 97d63b6272 | |||
| 39944aee16 | |||
| 3284f1fe7e | |||
| a96ad13616 | |||
| 3c872b34c6 | |||
| 4699142c9b | |||
| 613e7f8a88 | |||
| b240173b66 | |||
| 74730a2d63 | |||
| 0273fc7731 | |||
| eecb28b386 | |||
| 88f81cf8d1 | |||
| fe177d5dec | |||
| d80644a986 | |||
| bfe2779cea | |||
| 39916b12fd | |||
| b861c1dec7 | |||
| 808a335c90 | |||
| 3b55f2847a | |||
| a2e4730a35 | |||
| 7c2b6ed44f | |||
| 3c4d30bcf4 | |||
| c34c6a75df | |||
| 0464ae85ca | |||
| 3211454a2a | |||
| 06bebd6820 | |||
| 3f0cc3d712 | |||
| f71bfc90b1 | |||
| ae29d57085 | |||
| a2cf272e00 | |||
| 4736877c43 | |||
| aa6c15c975 | |||
| d7d8a654f3 | |||
| 2794943d3c | |||
| 8c62673dc5 | |||
| f7a80e3f2b | |||
| 773e7cd612 | |||
| 8f23a60ba8 | |||
| ce059497ac | |||
| 6c712c9caf | |||
| 6cfed9eebb | |||
| 2370d0c629 | |||
| 7cae4c7570 | |||
| 86f6671fe6 | |||
| 91a40ccce5 | |||
| 20c93ebb6f | |||
| 62ee8fd5c8 | |||
| 35c6838f14 | |||
| 596e7c7b0c | |||
| 1720bef450 | |||
| c197d3f14b | |||
| 42934afbed | |||
| a97de58aa7 | |||
| 47d87f7243 | |||
| 56f849dcc6 | |||
| 3784dcc912 | |||
| 2ad3d1a7f1 | |||
| 3265a02f6c | |||
| 6bc25f23fd | |||
| 42631c3e34 | |||
| 4e75827d72 | |||
| 6b06a36646 | |||
| bbf96cb595 | |||
| 918d45dfbd | |||
| 35b29cf219 | |||
| 6f5266c717 | |||
| 841fd94499 | |||
| 3c8abccaaa | |||
| 23cc13e8ef | |||
| b665e5aa33 | |||
| 5fc3b83497 | |||
| 56fdffa68e | |||
| 2b442c2fb7 | |||
| 7215c71486 | |||
| 9b3a8f7bd1 | |||
| 51b2acfae3 | |||
| 5dbf93200a | |||
| 4b987e5f7a | |||
| 337704e555 | |||
| 9f01cf6122 | |||
| 8872202049 | |||
| 855b9e504b | |||
| f253fa7f7e | |||
| 8ee6f49384 | |||
| f4f6534b40 | |||
| 1d70790168 | |||
| 17a5e73a9b | |||
| 5987bed350 | |||
| b6d0dec01e | |||
| c0d519b426 | |||
| 05c66fcebd | |||
| 5c545ca2ff | |||
| 4037bdd825 | |||
| c211e62465 | |||
| ff27bc6c5a | |||
| bab8ea6608 | |||
| 4501c37045 | |||
| b8322f7dd1 | |||
| a2221d6d8d | |||
| f702707ef7 | |||
| 71bb7f306e | |||
| 616935475b | |||
| 8d6cbcb248 | |||
| 31b1a9601f | |||
| b70b06bc61 | |||
| f8811d5661 |
+1
-1
@@ -89,4 +89,4 @@ theme/boost/amd/src/bootstrap/toast.js
|
||||
theme/boost/amd/src/bootstrap/tooltip.js
|
||||
theme/boost/amd/src/bootstrap/util.js
|
||||
theme/boost/amd/src/tether.js
|
||||
theme/boost/scss/fontawesome/
|
||||
theme/boost/scss/fontawesome/
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
name: Core
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
- MOODLE_[0-9]+_STABLE
|
||||
tags-ignore:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
|
||||
env:
|
||||
php: 7.4
|
||||
|
||||
@@ -44,3 +44,4 @@ composer.phar
|
||||
/lib/yuilib/*/*/*-coverage.js
|
||||
atlassian-ide-plugin.xml
|
||||
/node_modules/
|
||||
moodle-plugin-ci.phar
|
||||
|
||||
+1
-1
@@ -90,4 +90,4 @@ theme/boost/amd/src/bootstrap/toast.js
|
||||
theme/boost/amd/src/bootstrap/tooltip.js
|
||||
theme/boost/amd/src/bootstrap/util.js
|
||||
theme/boost/amd/src/tether.js
|
||||
theme/boost/scss/fontawesome/
|
||||
theme/boost/scss/fontawesome/
|
||||
|
||||
+7
-1
@@ -10,7 +10,7 @@ language: php
|
||||
|
||||
os: linux
|
||||
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
|
||||
services:
|
||||
- mysql
|
||||
@@ -19,6 +19,12 @@ services:
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
|
||||
branches:
|
||||
except:
|
||||
- master
|
||||
- /MOODLE_[0-9]+_STABLE/
|
||||
- /^v[0-9]+\.[0-9]+\.[0-9]+.*/
|
||||
|
||||
jobs:
|
||||
# Enable fast finish.
|
||||
# This will fail the build if a single job fails (except those in allow_failures).
|
||||
|
||||
+2
-2
@@ -424,7 +424,7 @@ module.exports = function(grunt) {
|
||||
'*/**/yui/src/*/meta/',
|
||||
'*/**/build/',
|
||||
].concat(thirdPartyPaths);
|
||||
grunt.file.write('.eslintignore', eslintIgnores.join('\n'));
|
||||
grunt.file.write('.eslintignore', eslintIgnores.join('\n') + '\n');
|
||||
|
||||
// Generate .stylelintignore.
|
||||
const stylelintIgnores = [
|
||||
@@ -433,7 +433,7 @@ module.exports = function(grunt) {
|
||||
'theme/boost/style/moodle.css',
|
||||
'theme/classic/style/moodle.css',
|
||||
].concat(thirdPartyPaths);
|
||||
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
|
||||
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n') + '\n');
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ MOODLE TRADEMARKS POLICY
|
||||
========================
|
||||
|
||||
The word "Moodle" is trademarked in many countries around the world. The word
|
||||
was originally an acronym: Modular Object-Oriented Dynamic Learning
|
||||
was originally coined as an acronym: Modular Object-Oriented Dynamic Learning
|
||||
Environment. The trademark is owned by Martin Dougiamas, Founder and Director
|
||||
of Moodle.
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Fix orphaned calendar events that were broken by MDL-67494.
|
||||
*
|
||||
* This script will look for all the calendar events which userids
|
||||
* where broken by a wrong upgrade step, affecting to Moodle 3.9.5
|
||||
* and up.
|
||||
*
|
||||
* It performs checks to both:
|
||||
* a) Detect if the site was affected (ran the wrong upgrade step).
|
||||
* b) Look for orphaned calendar events, categorising them as:
|
||||
* - standard: site / category / course / group / user events
|
||||
* - subscription: events created via subscriptions.
|
||||
* - action: normal action events, created to show common important dates.
|
||||
* - override: user and group override events, particular, that some activities support.
|
||||
* - custom: other events, not being any of the above, common or particular.
|
||||
* By specifying it (--fix) try to recover as many broken events (missing userid) as
|
||||
* possible. Standard, subscription, action, override events in core are fully supported but
|
||||
* override or custom events should be fixed by each plugin as far as there isn't any standard
|
||||
* API (plugin-wise) to launch a rebuild of the calendar events.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2021 onwards Simey Lameze <simey@moodle.com>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir . "/clilib.php");
|
||||
require_once($CFG->libdir . '/db/upgradelib.php');
|
||||
|
||||
// Supported options.
|
||||
$long = ['fix' => false, 'help' => false];
|
||||
$short = ['f' => 'fix', 'h' => 'help'];
|
||||
|
||||
// CLI options.
|
||||
[$options, $unrecognized] = cli_get_params($long, $short);
|
||||
|
||||
if ($unrecognized) {
|
||||
$unrecognized = implode("\n ", $unrecognized);
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help']) {
|
||||
$help = <<<EOT
|
||||
Fix orphaned calendar events.
|
||||
|
||||
This script detects calendar events that have had their
|
||||
userid lost. By default it will perform various checks
|
||||
and report them, showing the site status in an easy way.
|
||||
|
||||
Also, optionally (--fix), it wil try to recover as many
|
||||
lost userids as possible from different sources. Note that
|
||||
this script aims to process well-know events in core,
|
||||
leaving custom events in 3rd part plugins mostly unmodified
|
||||
because there isn't any consistent way to regenerate them.
|
||||
|
||||
For more details: https://tracker.moodle.org/browse/MDL-71156
|
||||
|
||||
Options:
|
||||
-h, --help Print out this help.
|
||||
-f, --fix Fix the orphaned calendar events in the DB.
|
||||
If not specified only check and report problems to STDERR.
|
||||
|
||||
Usage:
|
||||
- Only report: \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php
|
||||
- Report and fix: \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php -f
|
||||
EOT;
|
||||
|
||||
cli_writeln($help);
|
||||
die;
|
||||
}
|
||||
|
||||
// Check various usual pre-requisites.
|
||||
if (empty($CFG->version)) {
|
||||
cli_error('Database is not yet installed.');
|
||||
}
|
||||
|
||||
$admin = get_admin();
|
||||
if (!$admin) {
|
||||
cli_error('Error: No admin account was found.');
|
||||
}
|
||||
|
||||
if (moodle_needs_upgrading()) {
|
||||
cli_error('Moodle upgrade pending, script execution suspended.');
|
||||
}
|
||||
|
||||
// Do everything as admin by default.
|
||||
\core\session\manager::set_user($admin);
|
||||
|
||||
// Report current site status.
|
||||
cli_heading('Checking the site status');
|
||||
$needsfix = upgrade_calendar_site_status();
|
||||
|
||||
// Report current calendar events status.
|
||||
cli_heading('Checking the calendar events status');
|
||||
$info = upgrade_calendar_events_status();
|
||||
$hasbadevents = $info['total']->bad > 0 || $info['total']->bad != $info['other']->bad;
|
||||
$needsfix = $needsfix || $hasbadevents;
|
||||
|
||||
// If, selected, fix as many calendar events as possible.
|
||||
if ($options['fix']) {
|
||||
|
||||
// If the report has told us that the fix was not needed... ask for confirmation!
|
||||
if (!$needsfix) {
|
||||
cli_writeln("This site DOES NOT NEED to run the calendar events fix.");
|
||||
$input = cli_input('Are you completely sure that you want to run the fix? (y/N)', 'N', ['y', 'Y', 'n', 'N']);
|
||||
if (strtolower($input) != 'y') {
|
||||
exit(0);
|
||||
}
|
||||
cli_writeln("");
|
||||
}
|
||||
cli_heading('Fixing as many as possible calendar events');
|
||||
upgrade_calendar_events_fix_remaining($info);
|
||||
// Report current (after fix) calendar events status.
|
||||
cli_heading('Checking the calendar events status (after fix)');
|
||||
upgrade_calendar_events_status();
|
||||
} else if ($needsfix) {
|
||||
// Fix option was not provided but problem events have been found. Notify the user and provide info how to fix these events.
|
||||
cli_writeln("This site NEEDS to run the calendar events fix!");
|
||||
cli_writeln("To fix the calendar events, re-run this script with the --fix option.");
|
||||
}
|
||||
@@ -119,7 +119,7 @@ if (!empty($options['collation'])) {
|
||||
if ($dbcollation->value !== $collation || $dbcharset->value !== $charset) {
|
||||
// Try to convert the DB.
|
||||
echo "Converting database to '$collation' for $CFG->wwwroot:\n";
|
||||
$sql = "ALTER DATABASE $CFG->dbname DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation";
|
||||
$sql = "ALTER DATABASE `$CFG->dbname` DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation";
|
||||
try {
|
||||
$DB->change_database_structure($sql);
|
||||
} catch (exception $e) {
|
||||
@@ -146,7 +146,7 @@ if (!empty($options['collation'])) {
|
||||
|
||||
} else {
|
||||
try {
|
||||
$DB->change_database_structure("ALTER TABLE $table->name CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
$DB->change_database_structure("ALTER TABLE `$table->name` CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
echo "CONVERTED\n";
|
||||
$converted++;
|
||||
} catch (ddl_exception $e) {
|
||||
@@ -163,7 +163,7 @@ if (!empty($options['collation'])) {
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SHOW FULL COLUMNS FROM $table->name WHERE collation IS NOT NULL";
|
||||
$sql = "SHOW FULL COLUMNS FROM `$table->name` WHERE collation IS NOT NULL";
|
||||
$rs2 = $DB->get_recordset_sql($sql);
|
||||
foreach ($rs2 as $column) {
|
||||
$column = (object)array_change_key_case((array)$column, CASE_LOWER);
|
||||
@@ -181,7 +181,7 @@ if (!empty($options['collation'])) {
|
||||
$notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
|
||||
$default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
|
||||
// primary, unique and inc are not supported for texts
|
||||
$sql = "ALTER TABLE $table->name
|
||||
$sql = "ALTER TABLE `$table->name`
|
||||
MODIFY COLUMN $column->field $column->type
|
||||
CHARACTER SET $charset
|
||||
COLLATE $collation $notnull $default";
|
||||
@@ -192,11 +192,11 @@ if (!empty($options['collation'])) {
|
||||
$default = !is_null($column->default) ? "DEFAULT '$column->default'" : '';
|
||||
|
||||
if ($rowformat != '') {
|
||||
$sql = "ALTER TABLE $table->name $rowformat";
|
||||
$sql = "ALTER TABLE `$table->name` $rowformat";
|
||||
$DB->change_database_structure($sql);
|
||||
}
|
||||
|
||||
$sql = "ALTER TABLE $table->name
|
||||
$sql = "ALTER TABLE `$table->name`
|
||||
MODIFY COLUMN $column->field $column->type
|
||||
CHARACTER SET $charset
|
||||
COLLATE $collation $notnull $default";
|
||||
@@ -315,8 +315,8 @@ function mysql_set_row_format($tablename, $charset, $collation, $engine) {
|
||||
if ($rs->row_format == 'Compact' || $rs->row_format == 'Redundant') {
|
||||
$rowformat = $DB->get_row_format_sql($engine, $collation);
|
||||
// Try to convert to compressed format and then try updating the collation again.
|
||||
$DB->change_database_structure("ALTER TABLE $tablename $rowformat");
|
||||
$DB->change_database_structure("ALTER TABLE $tablename CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
$DB->change_database_structure("ALTER TABLE `$tablename` $rowformat");
|
||||
$DB->change_database_structure("ALTER TABLE `$tablename` CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
} else {
|
||||
// Row format may not be the problem. Can not diagnose problem. Send fail reply.
|
||||
return false;
|
||||
|
||||
@@ -158,7 +158,7 @@ if (!empty($options['info'])) {
|
||||
}
|
||||
|
||||
foreach ($fixtables as $table) {
|
||||
$DB->change_database_structure("ALTER TABLE {$prefix}$table ROW_FORMAT=Compressed");
|
||||
$DB->change_database_structure("ALTER TABLE `{$prefix}$table` ROW_FORMAT=Compressed");
|
||||
echo str_pad($prefix . $table, 32, ' ', STR_PAD_RIGHT) . " ... Compressed\n";
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ if (!empty($options['info'])) {
|
||||
echo "SET GLOBAL innodb_file_per_table=1;\n";
|
||||
echo "SET GLOBAL innodb_file_format=Barracuda;\n";
|
||||
foreach ($fixtables as $table) {
|
||||
echo "ALTER TABLE {$prefix}$table ROW_FORMAT=Compressed;\n";
|
||||
echo "ALTER TABLE `{$prefix}$table` ROW_FORMAT=Compressed;\n";
|
||||
}
|
||||
echo "\n";
|
||||
exit(0);
|
||||
|
||||
+188
-1
@@ -3447,7 +3447,7 @@
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
@@ -3527,6 +3527,193 @@
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.0" requires="3.6">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.2.29" />
|
||||
<VENDOR name="mysql" version="5.7" />
|
||||
<VENDOR name="postgres" version="10" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.3.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="optional"/>
|
||||
<PHP_EXTENSION name="exif" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -45,7 +45,7 @@ list($options, $unrecognised) = cli_get_params(
|
||||
array(
|
||||
'stop-on-failure' => 0,
|
||||
'verbose' => false,
|
||||
'replace' => false,
|
||||
'replace' => '',
|
||||
'help' => false,
|
||||
'tags' => '',
|
||||
'profile' => '',
|
||||
@@ -70,7 +70,7 @@ $help = "
|
||||
Behat utilities to run behat tests in parallel
|
||||
|
||||
Usage:
|
||||
php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace] [--fromrun=value --torun=value] [--help]
|
||||
php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace=\"{run}\"] [--fromrun=value --torun=value] [--help]
|
||||
|
||||
Options:
|
||||
--BEHAT_OPTION Any combination of behat option specified in http://behat.readthedocs.org/en/v2.5/guides/6.cli.html
|
||||
@@ -144,9 +144,10 @@ $extraopts = $unrecognised;
|
||||
if ($options['profile']) {
|
||||
$profile = $options['profile'];
|
||||
|
||||
// If profile passed is not set, then exit.
|
||||
// If profile passed is not set, then exit (note we skip if the 'replace' option is found within the 'profile' value).
|
||||
if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile]) &&
|
||||
!(isset($options['replace']) && (strpos($options['profile'], $options['replace']) >= 0 ))) {
|
||||
!($options['replace'] && (strpos($profile, (string) $options['replace']) !== false))) {
|
||||
|
||||
echo "Invalid profile passed: " . $profile . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@ Feature: Transform date time string arguments
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
And I should see "##yesterday##l, j F Y##"
|
||||
And I should see "##yesterday##%A, %d %B %Y##"
|
||||
And I log out
|
||||
|
||||
@@ -189,7 +189,12 @@ class cohort_role_assignments_table extends table_sql {
|
||||
FROM {tool_cohortroles} uca
|
||||
JOIN {user} u ON u.id = uca.userid
|
||||
JOIN {cohort} c ON c.id = uca.cohortid";
|
||||
$params = array();
|
||||
|
||||
// Check if any additional filtering is required.
|
||||
[$sqlwhere, $params] = $this->get_sql_where();
|
||||
if ($sqlwhere) {
|
||||
$sql .= " WHERE {$sqlwhere}";
|
||||
}
|
||||
|
||||
// Add order by if needed.
|
||||
if (!$count && $sqlsort = $this->get_sql_sort()) {
|
||||
|
||||
@@ -86,8 +86,8 @@ if ($removeid) {
|
||||
|
||||
$title = get_string('existingcohortroles', 'tool_cohortroles');
|
||||
echo $output->heading($title);
|
||||
$url = new moodle_url('/admin/tool/cohortroles/index.php');
|
||||
$table = new tool_cohortroles\output\cohort_role_assignments_table(uniqid(), $url);
|
||||
|
||||
$table = new tool_cohortroles\output\cohort_role_assignments_table('cohort-role-assignments', $pageurl);
|
||||
echo $table->out(50, true);
|
||||
|
||||
echo $output->spacer();
|
||||
|
||||
@@ -37,67 +37,68 @@
|
||||
This template does not have an example context because it includes ajax functionality.
|
||||
}}
|
||||
<div data-region="user-competency-full-info" data-node="user-competency" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}" data-region-id="{{uniqid}}">
|
||||
<div data-region="competency-summary">
|
||||
{{#competency}}
|
||||
{{> tool_lp/competency_summary }}
|
||||
{{/competency}}
|
||||
<dl>
|
||||
{{#usercompetency}}
|
||||
<dt>{{#str}}reviewstatus, tool_lp{{/str}}</dt>
|
||||
<dd data-region="user-competency-status">{{statusname}}
|
||||
<div data-region="competency-summary">
|
||||
{{#competency}}
|
||||
{{> tool_lp/competency_summary }}
|
||||
{{/competency}}
|
||||
<dl>
|
||||
{{#usercompetency}}
|
||||
<dt>{{#str}}reviewstatus, tool_lp{{/str}}</dt>
|
||||
<dd data-region="user-competency-status">{{statusname}}
|
||||
|
||||
{{#isstatusinreview}}
|
||||
- {{reviewer.fullname}}
|
||||
{{/isstatusinreview}}
|
||||
{{#isstatusinreview}}
|
||||
- {{reviewer.fullname}}
|
||||
{{/isstatusinreview}}
|
||||
|
||||
{{#isrequestreviewallowed}}<button class="btn btn-secondary" data-action="request-review">{{#str}}requestreview, tool_lp{{/str}}</button>{{/isrequestreviewallowed}}
|
||||
{{#iscancelreviewrequestallowed}}<button class="btn btn-secondary" data-action="cancel-review-request">{{#str}}cancelreviewrequest, tool_lp{{/str}}</button>{{/iscancelreviewrequestallowed}}
|
||||
{{#isstartreviewallowed}}<button class="btn btn-secondary" data-action="start-review">{{#str}}startreview, tool_lp{{/str}}</button>{{/isstartreviewallowed}}
|
||||
{{#isstopreviewallowed}}<button class="btn btn-secondary" data-action="stop-review">{{#str}}stopreview, tool_lp{{/str}}</button>{{/isstopreviewallowed}}
|
||||
</dd>
|
||||
<dt>{{#str}}proficient, tool_lp{{/str}}</dt>
|
||||
<dd>
|
||||
<span class="badge {{#proficiency}}badge-success{{/proficiency}}{{^proficiency}}badge-danger{{/proficiency}} float-left">
|
||||
{{proficiencyname}}
|
||||
</span>
|
||||
</dd>
|
||||
<dt>{{#str}}rating, tool_lp{{/str}}</dt>
|
||||
<dd>{{gradename}}
|
||||
{{#cangrade}}
|
||||
<button class="btn btn-secondary" id="rate_{{uniqid}}">{{#str}}rate, tool_lp{{/str}}</button>
|
||||
{{/cangrade}}
|
||||
</dd>
|
||||
{{#js}}
|
||||
require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info', 'tool_lp/user_competency_workflow'], function($, mod, info, UserCompWorkflow) {
|
||||
{{#isrequestreviewallowed}}<button class="btn btn-secondary" data-action="request-review">{{#str}}requestreview, tool_lp{{/str}}</button>{{/isrequestreviewallowed}}
|
||||
{{#iscancelreviewrequestallowed}}<button class="btn btn-secondary" data-action="cancel-review-request">{{#str}}cancelreviewrequest, tool_lp{{/str}}</button>{{/iscancelreviewrequestallowed}}
|
||||
{{#isstartreviewallowed}}<button class="btn btn-secondary" data-action="start-review">{{#str}}startreview, tool_lp{{/str}}</button>{{/isstartreviewallowed}}
|
||||
{{#isstopreviewallowed}}<button class="btn btn-secondary" data-action="stop-review">{{#str}}stopreview, tool_lp{{/str}}</button>{{/isstopreviewallowed}}
|
||||
</dd>
|
||||
<dt>{{#str}}proficient, tool_lp{{/str}}</dt>
|
||||
<dd>
|
||||
<span class="badge {{#proficiency}}badge-success{{/proficiency}}{{^proficiency}}badge-danger{{/proficiency}} float-left">
|
||||
{{proficiencyname}}
|
||||
</span>
|
||||
</dd>
|
||||
<dt>{{#str}}rating, tool_lp{{/str}}</dt>
|
||||
<dd>{{gradename}}
|
||||
{{#cangrade}}
|
||||
<button class="btn btn-secondary" id="rate_{{uniqid}}">{{#str}}rate, tool_lp{{/str}}</button>
|
||||
{{/cangrade}}
|
||||
</dd>
|
||||
{{#js}}
|
||||
require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info', 'tool_lp/user_competency_workflow'], function($, mod, info, UserCompWorkflow) {
|
||||
|
||||
var competencyElement = $('[data-region-id="{{uniqid}}"]');
|
||||
var infoReloader = new info(competencyElement, '{{competency.competency.id}}', '{{user.id}}');
|
||||
var competencyElement = $('[data-region-id="{{uniqid}}"]');
|
||||
var infoReloader = new info(competencyElement, '{{competency.competency.id}}', '{{user.id}}');
|
||||
|
||||
var ucw = new UserCompWorkflow();
|
||||
ucw.registerEvents('[data-region="user-competency-status"]');
|
||||
ucw.on('status-changed', infoReloader.reload.bind(infoReloader));
|
||||
ucw.on('error-occured', infoReloader.reload.bind(infoReloader));
|
||||
var ucw = new UserCompWorkflow();
|
||||
ucw.registerEvents('[data-region="user-competency-status"]');
|
||||
ucw.on('status-changed', infoReloader.reload.bind(infoReloader));
|
||||
ucw.on('error-occured', infoReloader.reload.bind(infoReloader));
|
||||
|
||||
var inlineGrader = new mod('#rate_{{uniqid}}', '{{competency.scaleid}}', '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
|
||||
inlineGrader.on('competencyupdated', infoReloader.reload.bind(infoReloader));
|
||||
});
|
||||
{{/js}}
|
||||
{{/usercompetency}}
|
||||
</dl>
|
||||
{{#commentarea}}
|
||||
{{#canpostorhascomments}}
|
||||
{{>tool_lp/comment_area}}
|
||||
{{/canpostorhascomments}}
|
||||
{{/commentarea}}
|
||||
<dl data-region="evidence-listing">
|
||||
<dt>{{#str}}evidence, tool_lp{{/str}}</dt>
|
||||
<dd>
|
||||
{{#evidence}}
|
||||
{{> tool_lp/evidence_summary }}
|
||||
{{/evidence}}
|
||||
{{^evidence}}
|
||||
<p>{{#str}}noevidence, tool_lp{{/str}}</p>
|
||||
{{/evidence}}
|
||||
</dd>
|
||||
</dl>
|
||||
var inlineGrader = new mod('#rate_{{uniqid}}', '{{competency.scaleid}}', '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
|
||||
inlineGrader.on('competencyupdated', infoReloader.reload.bind(infoReloader));
|
||||
});
|
||||
{{/js}}
|
||||
{{/usercompetency}}
|
||||
</dl>
|
||||
{{#commentarea}}
|
||||
{{#canpostorhascomments}}
|
||||
{{>tool_lp/comment_area}}
|
||||
{{/canpostorhascomments}}
|
||||
{{/commentarea}}
|
||||
<dl data-region="evidence-listing">
|
||||
<dt>{{#str}}evidence, tool_lp{{/str}}</dt>
|
||||
<dd>
|
||||
{{#evidence}}
|
||||
{{> tool_lp/evidence_summary }}
|
||||
{{/evidence}}
|
||||
{{^evidence}}
|
||||
<p>{{#str}}noevidence, tool_lp{{/str}}</p>
|
||||
{{/evidence}}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -132,12 +132,12 @@ Feature: Upload users
|
||||
# Boost check.
|
||||
And I log in as "jonest"
|
||||
And I am on "Maths" course homepage
|
||||
And "Turn editing on" "button" should not exist
|
||||
And "Calendar" "link" should be visible
|
||||
And I log out
|
||||
# Classic check.
|
||||
And I log in as "reznor"
|
||||
And I am on "Maths" course homepage
|
||||
And "Turn editing on" "button" should exist
|
||||
And "Calendar" "link" should not be visible
|
||||
|
||||
@javascript
|
||||
Scenario: Upload users setting their user theme when allowuserthemes is false
|
||||
|
||||
@@ -438,17 +438,7 @@ class manager {
|
||||
$filename = 'tour_export_' . $tour->get_id() . '_' . time() . '.json';
|
||||
|
||||
// Force download.
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
|
||||
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . 'GMT');
|
||||
header('Pragma: no-cache');
|
||||
header('Accept-Ranges: none');
|
||||
header('Content-disposition: attachment; filename=' . $filename);
|
||||
header('Content-length: ' . strlen($exportstring));
|
||||
header('Content-type: text/calendar; charset=utf-8');
|
||||
|
||||
echo $exportstring;
|
||||
die;
|
||||
send_file($exportstring, $filename, 0, 0, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,7 +68,9 @@ if ($dataformat) {
|
||||
$downloadusers = new ArrayObject($SESSION->bulk_users);
|
||||
$iterator = $downloadusers->getIterator();
|
||||
|
||||
\core\dataformat::download_data($filename, $dataformat, $fields, $iterator, function($userid) use ($extrafields, $fields) {
|
||||
\core\dataformat::download_data($filename, $dataformat, $fields, $iterator, function($userid, $supportshtml)
|
||||
use ($extrafields, $fields) {
|
||||
|
||||
global $DB;
|
||||
|
||||
if (!$user = $DB->get_record('user', array('id' => $userid))) {
|
||||
@@ -86,6 +88,8 @@ if ($dataformat) {
|
||||
// We only take the text.
|
||||
if (is_array($user->$field)) {
|
||||
$userprofiledata[$field] = reset($user->$field);
|
||||
} else if ($supportshtml) {
|
||||
$userprofiledata[$field] = s($user->$field);
|
||||
} else {
|
||||
$userprofiledata[$field] = $user->$field;
|
||||
}
|
||||
|
||||
@@ -178,6 +178,33 @@ class prediction {
|
||||
\core\event\prediction_action_started::create($eventdata)->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the executed actions.
|
||||
*
|
||||
* Actions could be filtered by actionname.
|
||||
*
|
||||
* @param array $actionnamefilter Limit the results obtained to this list of action names.
|
||||
* @param int $userid the user id. Current user by default.
|
||||
* @return array of actions.
|
||||
*/
|
||||
public function get_executed_actions(array $actionnamefilter = null, int $userid = 0): array {
|
||||
global $USER, $DB;
|
||||
|
||||
$conditions[] = "predictionid = :predictionid";
|
||||
$params['predictionid'] = $this->get_prediction_data()->id;
|
||||
if (!$userid) {
|
||||
$userid = $USER->id;
|
||||
}
|
||||
$conditions[] = "userid = :userid";
|
||||
$params['userid'] = $userid;
|
||||
if ($actionnamefilter) {
|
||||
list($actionsql, $actionparams) = $DB->get_in_or_equal($actionnamefilter, SQL_PARAMS_NAMED);
|
||||
$conditions[] = "actionname $actionsql";
|
||||
$params = $params + $actionparams;
|
||||
}
|
||||
return $DB->get_records_select('analytics_prediction_actions', implode(' AND ', $conditions), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* format_calculations
|
||||
*
|
||||
|
||||
@@ -112,6 +112,86 @@ class analytics_prediction_actions_testcase extends advanced_testcase {
|
||||
$this->assertEquals(2, $DB->count_records('analytics_prediction_actions'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_executed_actions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function execute_actions_provider(): array {
|
||||
return [
|
||||
'Empty actions with no filter' => [
|
||||
[],
|
||||
[],
|
||||
0
|
||||
],
|
||||
'Empty actions with filter' => [
|
||||
[],
|
||||
[\core_analytics\prediction::ACTION_FIXED],
|
||||
0
|
||||
],
|
||||
'Multiple actions with no filter' => [
|
||||
[
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
|
||||
],
|
||||
[],
|
||||
3
|
||||
],
|
||||
'Multiple actions applying filter' => [
|
||||
[
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
|
||||
],
|
||||
[\core_analytics\prediction::ACTION_FIXED],
|
||||
2
|
||||
],
|
||||
'Multiple actions not applying filter' => [
|
||||
[
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
|
||||
],
|
||||
[\core_analytics\prediction::ACTION_NOT_APPLICABLE],
|
||||
0
|
||||
],
|
||||
'Multiple actions with multiple filter' => [
|
||||
[
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_FIXED,
|
||||
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED
|
||||
],
|
||||
[\core_analytics\prediction::ACTION_FIXED, \core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED],
|
||||
3
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for get_executed_actions() function.
|
||||
*
|
||||
* @dataProvider execute_actions_provider
|
||||
* @param array $actionstoexecute An array of actions to execute
|
||||
* @param array $actionnamefilter Actions to filter
|
||||
* @param int $returned Number of actions returned
|
||||
*
|
||||
* @covers \core_analytics\prediction::get_executed_actions
|
||||
*/
|
||||
public function test_get_executed_actions(array $actionstoexecute, array $actionnamefilter, int $returned) {
|
||||
|
||||
$this->setUser($this->teacher2);
|
||||
list($ignored, $predictions) = $this->model->get_predictions($this->context, true);
|
||||
$prediction = reset($predictions);
|
||||
$target = $this->model->get_target();
|
||||
foreach($actionstoexecute as $action) {
|
||||
$prediction->action_executed($action, $target);
|
||||
}
|
||||
|
||||
$filteredactions = $prediction->get_executed_actions($actionnamefilter);
|
||||
$this->assertCount($returned, $filteredactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* test_get_predictions
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
This files describes API changes in analytics sub system,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.9.7 ===
|
||||
|
||||
* A new function get_executed_actions() has been added to \core_analytics\prediction class
|
||||
to get all (or filtered by action name) executed actions of a prediction
|
||||
|
||||
=== 3.8 ===
|
||||
|
||||
* "Time-splitting method" have been replaced by "Analysis interval" for the language strings that are
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Description of phpCAS 1.3.8 library import
|
||||
|
||||
* downloaded from http://downloads.jasig.org/cas-clients/php/current/
|
||||
* downloaded source code from https://github.com/apereo/phpCAS/releases
|
||||
@@ -4,7 +4,7 @@
|
||||
<location>CAS</location>
|
||||
<name>CAS</name>
|
||||
<license>Apache</license>
|
||||
<version>1.3.7</version>
|
||||
<version>1.3.8</version>
|
||||
<licenseversion>2.0</licenseversion>
|
||||
</library>
|
||||
</libraries>
|
||||
|
||||
@@ -45,7 +45,7 @@ $string['auth_dbname'] = 'Name of the database itself. Leave empty if using an O
|
||||
$string['auth_dbname_key'] = 'DB name';
|
||||
$string['auth_dbpass'] = 'Password matching the above username';
|
||||
$string['auth_dbpass_key'] = 'Password';
|
||||
$string['auth_dbpasstype'] = '<p>Specify the format that the password field is using.</p> <p>Use \'internal\' if you want the external database to manage usernames and email addresses, but Moodle to manage passwords. If you use \'internal\', you <i>must</i> provide a populated email address field in the external database, and you must execute both admin/cron.php and auth/db/cli/sync_users.php regularly. Moodle will send an email to new users with a temporary password.</p>';
|
||||
$string['auth_dbpasstype'] = '<p>Specify the format that the password field is using.</p> <p>Use \'internal\' if you want the external database to manage usernames and email addresses, but Moodle to manage passwords. If you use \'internal\', you must provide a populated email address field in the external database, and you must enable the \auth_db\task\sync_users scheduled task. Moodle will send an email to new users with a temporary password.</p>';
|
||||
$string['auth_dbpasstype_key'] = 'Password format';
|
||||
$string['auth_dbreviveduser'] = 'Revived user {$a->name} id {$a->id}';
|
||||
$string['auth_dbrevivedusererror'] = 'Error reviving user {$a}';
|
||||
|
||||
@@ -75,7 +75,7 @@ $string['auth_ldap_passwdexpire_settings'] = 'LDAP password expiry settings';
|
||||
$string['auth_ldap_preventpassindb'] = 'Select yes to prevent passwords from being stored in Moodle\'s DB.';
|
||||
$string['auth_ldap_preventpassindb_key'] = 'Prevent password caching';
|
||||
$string['auth_ldap_rolecontext'] = '{$a->localname} context';
|
||||
$string['auth_ldap_rolecontext_help'] = 'LDAP context used to select for <i>{$a->localname}</i> mapping. Separate multiple groups with \';\'. Usually something like "cn={$a->shortname},ou=staff,o=myorg".';
|
||||
$string['auth_ldap_rolecontext_help'] = 'LDAP context used to select for <i>{$a->localname}</i> mapping. Separate multiple groups with \';\'. Usually something like "cn={$a->shortname},ou=first-ou-with-role-groups,o=myorg; cn={$a->shortname},ou=second-ou-with-role-groups,o=myorg".';
|
||||
$string['auth_ldap_search_sub'] = 'Search users from subcontexts.';
|
||||
$string['auth_ldap_search_sub_key'] = 'Search subcontexts';
|
||||
$string['auth_ldap_server_settings'] = 'LDAP server settings';
|
||||
|
||||
+1
-3
@@ -716,9 +716,7 @@ class auth_plugin_mnet extends auth_plugin_base {
|
||||
|
||||
foreach($superArray as $subArray) {
|
||||
$subArray = array_values($subArray);
|
||||
$instring = "('".implode("', '",$subArray)."')";
|
||||
$query = "select id, session_id, username from {mnet_session} where username in $instring";
|
||||
$results = $DB->get_records_sql($query);
|
||||
$results = $DB->get_records_list('mnet_session', 'username', $subArray, '', 'id, session_id, username');
|
||||
|
||||
if ($results == false) {
|
||||
// We seem to have a username that breaks our query:
|
||||
|
||||
@@ -113,11 +113,22 @@ class helper {
|
||||
*/
|
||||
private static function unserializesession($serializedstring) {
|
||||
$variables = array();
|
||||
$a = preg_split("/(\w+)\|/", $serializedstring, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
|
||||
$counta = count($a);
|
||||
for ($i = 0; $i < $counta; $i = $i + 2) {
|
||||
$variables[$a[$i]] = unserialize($a[$i + 1]);
|
||||
|
||||
$index = 0;
|
||||
|
||||
// Find next delimiter after current index. It's key being the characters between those points.
|
||||
while ($delimiterpos = strpos($serializedstring, '|', $index)) {
|
||||
$key = substr($serializedstring, $index, $delimiterpos - $index);
|
||||
|
||||
// Start unserializing immediately after the delimiter. PHP will read as much valid data as possible.
|
||||
$value = unserialize(substr($serializedstring, $delimiterpos + 1),
|
||||
['allowed_classes' => ['stdClass']]);
|
||||
$variables[$key] = $value;
|
||||
|
||||
// Advance index beyond the length of the previously captured serialized value.
|
||||
$index = $delimiterpos + 1 + strlen(serialize($value));
|
||||
}
|
||||
|
||||
return $variables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,9 +290,9 @@ abstract class info {
|
||||
*/
|
||||
protected function warn_about_invalid_availability(\coding_exception $e) {
|
||||
$name = $this->get_thing_name();
|
||||
// If it occurs while building modinfo based on somebody calling $cm->name,
|
||||
// we can't get $cm->name, and this line will cause a warning.
|
||||
$htmlname = @$this->format_info($name, $this->course);
|
||||
$htmlname = $this->format_info($name, $this->course);
|
||||
// Because we call format_info here, likely in the middle of building dynamic data for the
|
||||
// activity, there could be a chance that the name might not be available.
|
||||
if ($htmlname === '') {
|
||||
// So instead use the numbers (cmid) from the tag.
|
||||
$htmlname = preg_replace('~[^0-9]~', '', $name);
|
||||
@@ -739,11 +739,11 @@ abstract class info {
|
||||
$info = preg_replace_callback('~<AVAILABILITY_CMNAME_([0-9]+)/>~',
|
||||
function($matches) use($modinfo, $context) {
|
||||
$cm = $modinfo->get_cm($matches[1]);
|
||||
if ($cm->has_view() and $cm->uservisible) {
|
||||
if ($cm->has_view() and $cm->get_user_visible()) {
|
||||
// Help student by providing a link to the module which is preventing availability.
|
||||
return \html_writer::link($cm->url, format_string($cm->name, true, array('context' => $context)));
|
||||
return \html_writer::link($cm->get_url(), format_string($cm->get_name(), true, ['context' => $context]));
|
||||
} else {
|
||||
return format_string($cm->name, true, array('context' => $context));
|
||||
return format_string($cm->get_name(), true, ['context' => $context]);
|
||||
}
|
||||
}, $info);
|
||||
|
||||
|
||||
@@ -511,4 +511,33 @@ class info_testcase extends advanced_testcase {
|
||||
sort($result);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the info_module class when involved in a recursive call to $cm->name.
|
||||
*/
|
||||
public function test_info_recursive_name_call() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a course and page.
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$page1 = $generator->create_module('page', ['course' => $course->id, 'name' => 'Page1']);
|
||||
|
||||
// Set invalid availability.
|
||||
$DB->set_field('course_modules', 'availability', 'not valid', ['id' => $page1->cmid]);
|
||||
|
||||
// Get the cm_info object.
|
||||
$this->setAdminUser();
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm1 = $modinfo->get_cm($page1->cmid);
|
||||
|
||||
// At this point we will generate dynamic data for $cm1, which will cause the debugging
|
||||
// call below.
|
||||
$this->assertEquals('Page1', $cm1->name);
|
||||
|
||||
$this->assertDebuggingCalled('Error processing availability data for ' .
|
||||
'‘Page1’: Invalid availability text');
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+17
-8
@@ -198,8 +198,11 @@ M.core_availability.form = {
|
||||
// If the groupmode and grouping id aren't set, disable it.
|
||||
var groupmode = Y.one('#id_groupmode');
|
||||
var groupingid = Y.one('#id_groupingid');
|
||||
if ((!groupmode || Number(groupmode.get('value')) === 0) &&
|
||||
(!groupingid || Number(groupingid.get('value')) === 0)) {
|
||||
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
|
||||
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
|
||||
|
||||
if ((!groupmode || Number(groupmode.get('value')) === 0 || !groupavailability) &&
|
||||
(!groupingid || Number(groupingid.get('value')) === 0 || !groupingavailability)) {
|
||||
this.restrictByGroup.set('disabled', true);
|
||||
return;
|
||||
}
|
||||
@@ -220,22 +223,28 @@ M.core_availability.form = {
|
||||
e.preventDefault();
|
||||
|
||||
// Add the condition.
|
||||
var groupmode = Y.one('#id_groupmode');
|
||||
var groupingid = Y.one('#id_groupingid');
|
||||
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
|
||||
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
|
||||
|
||||
var newChild;
|
||||
if (groupingid && Number(groupingid.get('value')) !== 0) {
|
||||
if (groupingid && Number(groupingid.get('value')) !== 0 && groupingavailability) {
|
||||
// Add a grouping restriction if one is specified.
|
||||
newChild = new M.core_availability.Item(
|
||||
{type: 'grouping', id: Number(groupingid.get('value'))}, true);
|
||||
} else {
|
||||
} else if (groupmode && groupavailability) {
|
||||
// Otherwise just add a group restriction.
|
||||
newChild = new M.core_availability.Item({type: 'group'}, true);
|
||||
}
|
||||
|
||||
// Refresh HTML.
|
||||
this.rootList.addChild(newChild);
|
||||
this.update();
|
||||
this.rootList.renumber();
|
||||
this.rootList.updateHtml();
|
||||
if (newChild !== null) {
|
||||
this.rootList.addChild(newChild);
|
||||
this.update();
|
||||
this.rootList.renumber();
|
||||
this.rootList.updateHtml();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+3
-3
File diff suppressed because one or more lines are too long
+17
-8
@@ -198,8 +198,11 @@ M.core_availability.form = {
|
||||
// If the groupmode and grouping id aren't set, disable it.
|
||||
var groupmode = Y.one('#id_groupmode');
|
||||
var groupingid = Y.one('#id_groupingid');
|
||||
if ((!groupmode || Number(groupmode.get('value')) === 0) &&
|
||||
(!groupingid || Number(groupingid.get('value')) === 0)) {
|
||||
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
|
||||
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
|
||||
|
||||
if ((!groupmode || Number(groupmode.get('value')) === 0 || !groupavailability) &&
|
||||
(!groupingid || Number(groupingid.get('value')) === 0 || !groupingavailability)) {
|
||||
this.restrictByGroup.set('disabled', true);
|
||||
return;
|
||||
}
|
||||
@@ -220,22 +223,28 @@ M.core_availability.form = {
|
||||
e.preventDefault();
|
||||
|
||||
// Add the condition.
|
||||
var groupmode = Y.one('#id_groupmode');
|
||||
var groupingid = Y.one('#id_groupingid');
|
||||
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
|
||||
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
|
||||
|
||||
var newChild;
|
||||
if (groupingid && Number(groupingid.get('value')) !== 0) {
|
||||
if (groupingid && Number(groupingid.get('value')) !== 0 && groupingavailability) {
|
||||
// Add a grouping restriction if one is specified.
|
||||
newChild = new M.core_availability.Item(
|
||||
{type: 'grouping', id: Number(groupingid.get('value'))}, true);
|
||||
} else {
|
||||
} else if (groupmode && groupavailability) {
|
||||
// Otherwise just add a group restriction.
|
||||
newChild = new M.core_availability.Item({type: 'group'}, true);
|
||||
}
|
||||
|
||||
// Refresh HTML.
|
||||
this.rootList.addChild(newChild);
|
||||
this.update();
|
||||
this.rootList.renumber();
|
||||
this.rootList.updateHtml();
|
||||
if (newChild !== null) {
|
||||
this.rootList.addChild(newChild);
|
||||
this.update();
|
||||
this.rootList.renumber();
|
||||
this.rootList.updateHtml();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Vendored
+17
-8
@@ -196,8 +196,11 @@ M.core_availability.form = {
|
||||
// If the groupmode and grouping id aren't set, disable it.
|
||||
var groupmode = Y.one('#id_groupmode');
|
||||
var groupingid = Y.one('#id_groupingid');
|
||||
if ((!groupmode || Number(groupmode.get('value')) === 0) &&
|
||||
(!groupingid || Number(groupingid.get('value')) === 0)) {
|
||||
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
|
||||
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
|
||||
|
||||
if ((!groupmode || Number(groupmode.get('value')) === 0 || !groupavailability) &&
|
||||
(!groupingid || Number(groupingid.get('value')) === 0 || !groupingavailability)) {
|
||||
this.restrictByGroup.set('disabled', true);
|
||||
return;
|
||||
}
|
||||
@@ -218,22 +221,28 @@ M.core_availability.form = {
|
||||
e.preventDefault();
|
||||
|
||||
// Add the condition.
|
||||
var groupmode = Y.one('#id_groupmode');
|
||||
var groupingid = Y.one('#id_groupingid');
|
||||
var groupavailability = Number(this.restrictByGroup.getData('groupavailability')) === 1;
|
||||
var groupingavailability = Number(this.restrictByGroup.getData('groupingavailability')) === 1;
|
||||
|
||||
var newChild;
|
||||
if (groupingid && Number(groupingid.get('value')) !== 0) {
|
||||
if (groupingid && Number(groupingid.get('value')) !== 0 && groupingavailability) {
|
||||
// Add a grouping restriction if one is specified.
|
||||
newChild = new M.core_availability.Item(
|
||||
{type: 'grouping', id: Number(groupingid.get('value'))}, true);
|
||||
} else {
|
||||
} else if (groupmode && groupavailability) {
|
||||
// Otherwise just add a group restriction.
|
||||
newChild = new M.core_availability.Item({type: 'group'}, true);
|
||||
}
|
||||
|
||||
// Refresh HTML.
|
||||
this.rootList.addChild(newChild);
|
||||
this.update();
|
||||
this.rootList.renumber();
|
||||
this.rootList.updateHtml();
|
||||
if (newChild !== null) {
|
||||
this.rootList.addChild(newChild);
|
||||
this.update();
|
||||
this.rootList.renumber();
|
||||
this.rootList.updateHtml();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4046,9 +4046,30 @@ class restore_contentbankcontent_structure_step extends restore_structure_step {
|
||||
$exists = $DB->record_exists('contentbank_content', $params);
|
||||
if (!$exists) {
|
||||
$params['configdata'] = $data->configdata;
|
||||
$params['usercreated'] = $this->get_mappingid('user', $data->usercreated);
|
||||
$params['usermodified'] = $this->get_mappingid('user', $data->usermodified);
|
||||
$params['timemodified'] = time();
|
||||
|
||||
// Trying to map users. Users cannot always be mapped, e.g. when copying.
|
||||
$params['usercreated'] = $this->get_mappingid('user', $data->usercreated);
|
||||
if (!$params['usercreated']) {
|
||||
// Leave the content creator unchanged when we are restoring the same site.
|
||||
// Otherwise use current user id.
|
||||
if ($this->task->is_samesite()) {
|
||||
$params['usercreated'] = $data->usercreated;
|
||||
} else {
|
||||
$params['usercreated'] = $this->task->get_userid();
|
||||
}
|
||||
}
|
||||
$params['usermodified'] = $this->get_mappingid('user', $data->usermodified);
|
||||
if (!$params['usermodified']) {
|
||||
// Leave the content modifier unchanged when we are restoring the same site.
|
||||
// Otherwise use current user id.
|
||||
if ($this->task->is_samesite()) {
|
||||
$params['usermodified'] = $data->usermodified;
|
||||
} else {
|
||||
$params['usermodified'] = $this->task->get_userid();
|
||||
}
|
||||
}
|
||||
|
||||
$newitemid = $DB->insert_record('contentbank_content', $params);
|
||||
$this->set_mapping('contentbank_content', $oldid, $newitemid, true);
|
||||
}
|
||||
|
||||
@@ -278,6 +278,73 @@ class core_backup_automated_backup_testcase extends advanced_testcase {
|
||||
$this->assertTrue($skipped);
|
||||
$this->expectOutputRegex("/Skipping $course->fullname \(Not modified since previous backup\)/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the task completes when coureid is missing.
|
||||
*/
|
||||
public function test_task_complete_when_courseid_is_missing() {
|
||||
global $DB;
|
||||
$admin = get_admin();
|
||||
$classobject = $this->backupcronautomatedhelper->return_this();
|
||||
|
||||
// Create this backup course.
|
||||
$backupcourse = new stdClass;
|
||||
$backupcourse->courseid = $this->course->id;
|
||||
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
|
||||
$DB->insert_record('backup_courses', $backupcourse);
|
||||
$backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]);
|
||||
|
||||
// Create a backup task.
|
||||
$method = new ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task');
|
||||
$method->setAccessible(true); // Allow accessing of private method.
|
||||
$method->invokeArgs($classobject, [$backupcourse, $admin]);
|
||||
|
||||
// Delete course for this test.
|
||||
delete_course($this->course->id, false);
|
||||
|
||||
$task = core\task\manager::get_next_adhoc_task(time());
|
||||
|
||||
ob_start();
|
||||
$task->execute();
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertStringContainsString('Invalid course id: ' . $this->course->id . ', task aborted.', $output);
|
||||
core\task\manager::adhoc_task_complete($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the task completes when backup course is missing.
|
||||
*/
|
||||
public function test_task_complete_when_backup_course_is_missing() {
|
||||
global $DB;
|
||||
$admin = get_admin();
|
||||
$classobject = $this->backupcronautomatedhelper->return_this();
|
||||
|
||||
// Create this backup course.
|
||||
$backupcourse = new stdClass;
|
||||
$backupcourse->courseid = $this->course->id;
|
||||
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
|
||||
$DB->insert_record('backup_courses', $backupcourse);
|
||||
$backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]);
|
||||
|
||||
// Create a backup task.
|
||||
$method = new ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task');
|
||||
$method->setAccessible(true); // Allow accessing of private method.
|
||||
$method->invokeArgs($classobject, [$backupcourse, $admin]);
|
||||
|
||||
// Delete backup course for this test.
|
||||
$DB->delete_records('backup_courses', ['courseid' => $this->course->id]);
|
||||
|
||||
$task = core\task\manager::get_next_adhoc_task(time());
|
||||
|
||||
ob_start();
|
||||
$task->execute();
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertStringContainsString('Automated backup for course: ' . $this->course->fullname . ' encounters an error.',
|
||||
$output);
|
||||
core\task\manager::adhoc_task_complete($task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -116,7 +116,9 @@ class backup_ui_stage_initial extends backup_ui_stage {
|
||||
if (isset($data->$name) && $data->$name != $setting->get_value()) {
|
||||
$setting->set_value($data->$name);
|
||||
$changes++;
|
||||
} else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
|
||||
} else if (!isset($data->$name) && $setting->get_value() &&
|
||||
$setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX &&
|
||||
$setting->get_status() !== backup_setting::LOCKED_BY_HIERARCHY) {
|
||||
$setting->set_value(0);
|
||||
$changes++;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ if (!is_null($action)) {
|
||||
} else { // Revoked badge.
|
||||
header("HTTP/1.0 410 Gone");
|
||||
$assertion = array();
|
||||
if ($obversion == OPEN_BADGES_V2) {
|
||||
if ($obversion >= OPEN_BADGES_V2) {
|
||||
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash));
|
||||
$assertion['id'] = $assertionurl->out();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ if ($persistedissuer) {
|
||||
$returnurl = new moodle_url('/badges/backpack-connect.php',
|
||||
['action' => 'authorization', 'sesskey' => sesskey()]);
|
||||
|
||||
// If scope is not passed as parameter, use the issuer supported scopes.
|
||||
if (empty($scope)) {
|
||||
$scope = $issuer->get('scopessupported');
|
||||
}
|
||||
$client = new core_badges\oauth2\client($issuer, $returnurl, $scope, $externalbackpack);
|
||||
if ($client) {
|
||||
if (!$client->is_logged_in()) {
|
||||
|
||||
@@ -128,7 +128,7 @@ class core_badges_assertion {
|
||||
$email = empty($this->_data->backpackemail) ? $this->_data->email : $this->_data->backpackemail;
|
||||
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
|
||||
|
||||
if ($this->_obversion == OPEN_BADGES_V2) {
|
||||
if ($this->_obversion >= OPEN_BADGES_V2) {
|
||||
$classurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id()));
|
||||
} else {
|
||||
$classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1));
|
||||
@@ -325,7 +325,7 @@ class core_badges_assertion {
|
||||
*/
|
||||
protected function embed_data_badge_version2 (&$json, $type = OPEN_BADGES_V2_TYPE_ASSERTION) {
|
||||
// Specification Version 2.0.
|
||||
if ($this->_obversion == OPEN_BADGES_V2) {
|
||||
if ($this->_obversion >= OPEN_BADGES_V2) {
|
||||
$badge = new badge($this->_data->id);
|
||||
if (empty($this->_data->courseid)) {
|
||||
$context = context_system::instance();
|
||||
|
||||
@@ -61,12 +61,12 @@ class external_backpack extends \moodleform {
|
||||
$mform->addElement('text', 'backpackapiurl', get_string('backpackapiurl', 'core_badges'));
|
||||
$mform->setType('backpackapiurl', PARAM_URL);
|
||||
$mform->addRule('backpackapiurl', null, 'required', null, 'client');
|
||||
$mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 50, 'client');
|
||||
$mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
|
||||
|
||||
$mform->addElement('text', 'backpackweburl', get_string('backpackweburl', 'core_badges'));
|
||||
$mform->setType('backpackweburl', PARAM_URL);
|
||||
$mform->addRule('backpackweburl', null, 'required', null, 'client');
|
||||
$mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 50, 'client');
|
||||
$mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
|
||||
|
||||
$apiversions = badges_get_badge_api_versions();
|
||||
$mform->addElement('select', 'apiversion', get_string('apiversion', 'core_badges'), $apiversions);
|
||||
|
||||
@@ -222,6 +222,7 @@ class client extends \core\oauth2\client {
|
||||
$callbackurl = self::callback_url();
|
||||
|
||||
if ($granttype == 'authorization_code') {
|
||||
$this->basicauth = true;
|
||||
$params = array('code' => $code,
|
||||
'grant_type' => $granttype,
|
||||
'redirect_uri' => $callbackurl->out(false),
|
||||
@@ -236,7 +237,7 @@ class client extends \core\oauth2\client {
|
||||
);
|
||||
}
|
||||
if ($this->basicauth) {
|
||||
$idsecret = urlencode($this->clientid) . ':' . urlencode($this->clientsecret);
|
||||
$idsecret = $this->clientid . ':' . $this->clientsecret;
|
||||
$this->setHeader('Authorization: Basic ' . base64_encode($idsecret));
|
||||
} else {
|
||||
$params['client_id'] = $this->clientid;
|
||||
@@ -244,11 +245,13 @@ class client extends \core\oauth2\client {
|
||||
}
|
||||
// Requests can either use http GET or POST.
|
||||
$response = $this->post($this->token_url(), $this->build_post_data($params));
|
||||
$r = json_decode($response);
|
||||
if ($this->info['http_code'] !== 200) {
|
||||
throw new moodle_exception('Could not upgrade oauth token');
|
||||
$debuginfo = !empty($this->error) ? $this->error : $response;
|
||||
throw new moodle_exception('oauth2refreshtokenerror', 'core_error', '', $this->info['http_code'], $debuginfo);
|
||||
}
|
||||
|
||||
$r = json_decode($response);
|
||||
|
||||
if (is_null($r)) {
|
||||
throw new moodle_exception("Could not decode JSON token response");
|
||||
}
|
||||
|
||||
@@ -240,13 +240,23 @@ class award_criteria_cohort extends award_criteria {
|
||||
return array($join, $where, $params);
|
||||
} else {
|
||||
// User is a member of ALL of the specified cohorts.
|
||||
$join = " LEFT JOIN {cohort_members} cm ON cm.userid = u.id";
|
||||
$join = ' LEFT JOIN {cohort_members} cm ON cm.userid = u.id';
|
||||
$where = ' AND (';
|
||||
$i = 0;
|
||||
foreach ($this->params as $param) {
|
||||
$i++;
|
||||
$where = ' AND cm.cohortid = :cohortid'.$i;
|
||||
if ($i == 0) {
|
||||
$where .= 'cm.cohortid = :cohortid'.$i;
|
||||
} else {
|
||||
$where .= ' OR cm.cohortid = :cohortid'.$i;
|
||||
}
|
||||
$params['cohortid'.$i] = $param['cohort'];
|
||||
$i++;
|
||||
}
|
||||
$where .= ')
|
||||
GROUP BY u.id, bi.badgeid
|
||||
HAVING COUNT(cm.cohortid) = :cohortcount';
|
||||
$params['cohortcount'] = $i;
|
||||
|
||||
return array($join, $where, $params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,35 +620,127 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test badges observer when cohort_member_added event is fired.
|
||||
* Test badges observer when cohort_member_added event is fired and user required to belong to any cohort.
|
||||
*
|
||||
* @covers award_criteria_cohort
|
||||
*/
|
||||
public function test_badges_observer_cohort_criteria_review() {
|
||||
public function test_badges_observer_any_cohort_criteria_review() {
|
||||
global $CFG;
|
||||
|
||||
require_once("$CFG->dirroot/cohort/lib.php");
|
||||
|
||||
$cohort = $this->getDataGenerator()->create_cohort();
|
||||
$cohort1 = $this->getDataGenerator()->create_cohort();
|
||||
$cohort2 = $this->getDataGenerator()->create_cohort();
|
||||
|
||||
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
|
||||
|
||||
$badge = new badge($this->badgeid);
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
|
||||
// Set up the badge criteria.
|
||||
$criteriaoverall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
|
||||
$criteriaoverall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
|
||||
$criteriaoverall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COHORT, 'badgeid' => $badge->id));
|
||||
$criteriaoverall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'cohort_cohorts' => array('0' => $cohort->id)));
|
||||
|
||||
// Make the badge active.
|
||||
$criteriaoverall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY,
|
||||
'cohort_cohorts' => array('0' => $cohort1->id, '1' => $cohort2->id)));
|
||||
$badge->set_status(BADGE_STATUS_ACTIVE);
|
||||
|
||||
// Reload it to contain criteria.
|
||||
$badge = new badge($this->badgeid);
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
|
||||
// Add the user to the cohort.
|
||||
cohort_add_member($cohort->id, $this->user->id);
|
||||
cohort_add_member($cohort2->id, $this->user->id);
|
||||
$this->assertDebuggingCalled();
|
||||
|
||||
// Verify that the badge was awarded.
|
||||
$this->assertDebuggingCalled();
|
||||
$this->assertTrue($badge->is_issued($this->user->id));
|
||||
// As the badge has been awarded to user because core_badges_observer been called when the member has been added to the
|
||||
// cohort, there are no other users that can award this badge.
|
||||
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
}
|
||||
|
||||
/**
|
||||
* Test badges observer when cohort_member_added event is fired and user required to belong to multiple (all) cohorts.
|
||||
*
|
||||
* @covers award_criteria_cohort
|
||||
*/
|
||||
public function test_badges_observer_all_cohort_criteria_review() {
|
||||
global $CFG;
|
||||
|
||||
require_once("$CFG->dirroot/cohort/lib.php");
|
||||
|
||||
$cohort1 = $this->getDataGenerator()->create_cohort();
|
||||
$cohort2 = $this->getDataGenerator()->create_cohort();
|
||||
$cohort3 = $this->getDataGenerator()->create_cohort();
|
||||
|
||||
// Add user2 to cohort1 and cohort3.
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
cohort_add_member($cohort3->id, $user2->id);
|
||||
cohort_add_member($cohort1->id, $user2->id);
|
||||
|
||||
// Add user3 to cohort1, cohort2 and cohort3.
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
cohort_add_member($cohort1->id, $user3->id);
|
||||
cohort_add_member($cohort2->id, $user3->id);
|
||||
cohort_add_member($cohort3->id, $user3->id);
|
||||
|
||||
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
|
||||
|
||||
// Cohort criteria are used in site badges.
|
||||
$badge = new badge($this->badgeid);
|
||||
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
|
||||
// Set up the badge criteria.
|
||||
$criteriaoverall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
|
||||
$criteriaoverall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
|
||||
$criteriaoverall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COHORT, 'badgeid' => $badge->id));
|
||||
$criteriaoverall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL,
|
||||
'cohort_cohorts' => array('0' => $cohort1->id, '1' => $cohort2->id, '2' => $cohort3->id)));
|
||||
$badge->set_status(BADGE_STATUS_ACTIVE);
|
||||
|
||||
// Reload it to contain criteria.
|
||||
$badge = new badge($this->badgeid);
|
||||
|
||||
// Verify that the badge was not awarded yet (ALL cohorts are needed and review_all_criteria has to be called).
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
$this->assertFalse($badge->is_issued($user2->id));
|
||||
$this->assertFalse($badge->is_issued($user3->id));
|
||||
|
||||
// Verify that after calling review_all_criteria, users with the criteria (user3) award the badge instantly.
|
||||
$this->assertSame(1, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
$this->assertFalse($badge->is_issued($user2->id));
|
||||
$this->assertTrue($badge->is_issued($user3->id));
|
||||
$this->assertDebuggingCalled();
|
||||
|
||||
// Add the user to the cohort1.
|
||||
cohort_add_member($cohort1->id, $this->user->id);
|
||||
|
||||
// Verify that the badge was not awarded yet (ALL cohorts are needed).
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
|
||||
// Add the user to the cohort3.
|
||||
cohort_add_member($cohort3->id, $this->user->id);
|
||||
|
||||
// Verify that the badge was not awarded yet (ALL cohorts are needed).
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
|
||||
// Add user to cohort2.
|
||||
cohort_add_member($cohort2->id, $this->user->id);
|
||||
$this->assertDebuggingCalled();
|
||||
|
||||
// Verify that the badge was awarded (ALL cohorts).
|
||||
$this->assertTrue($badge->is_issued($this->user->id));
|
||||
// As the badge has been awarded to user because core_badges_observer been called when the member has been added to the
|
||||
// cohort, there are no other users that can award this badge.
|
||||
$this->assertSame(0, $badge->review_all_criteria()); // Verify award_criteria_cohort->get_completed_criteria_sql().
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -694,6 +786,16 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
$this->assertStringMatchesFormat($testassertion2->badge, json_encode($assertion2->get_badge_assertion()));
|
||||
$this->assertStringMatchesFormat($testassertion2->class, json_encode($assertion2->get_badge_class()));
|
||||
$this->assertStringMatchesFormat($testassertion2->issuer, json_encode($assertion2->get_issuer()));
|
||||
|
||||
// Test Openbadge specification version 2.1. It has the same format as OBv2.0.
|
||||
// Get assertion version 2.1.
|
||||
$award = reset($awards);
|
||||
$assertion2 = new core_badges_assertion($award->uniquehash, OPEN_BADGES_V2P1);
|
||||
|
||||
// Make sure JSON strings have the same structure.
|
||||
$this->assertStringMatchesFormat($testassertion2->badge, json_encode($assertion2->get_badge_assertion()));
|
||||
$this->assertStringMatchesFormat($testassertion2->class, json_encode($assertion2->get_badge_class()));
|
||||
$this->assertStringMatchesFormat($testassertion2->issuer, json_encode($assertion2->get_issuer()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,4 +120,4 @@ Feature: Backpack badges
|
||||
And I click on "Delete" "button" in the "Delete site backpack" "dialogue"
|
||||
Then I should see "The site backpack has been deleted."
|
||||
And I should not see "https://dc.imsglobal.org"
|
||||
And "Delete" "button" should not exist
|
||||
And "Delete" "button" should not be visible
|
||||
|
||||
@@ -64,6 +64,7 @@ Feature: Award badges based on competency completion
|
||||
And I set the following fields to these values:
|
||||
| Rating | C |
|
||||
And I click on "Rate" "button" in the "Rate" "dialogue"
|
||||
And I should see "The competency rating was manually set in the course"
|
||||
And I log out
|
||||
# See if we got the badge
|
||||
Then I log in as "user1"
|
||||
@@ -114,8 +115,8 @@ Feature: Award badges based on competency completion
|
||||
And I press "Rate"
|
||||
And I set the following fields to these values:
|
||||
| Rating | C |
|
||||
And I wait until the page is ready
|
||||
And I click on "Rate" "button" in the "Rate" "dialogue"
|
||||
And I should see "The competency rating was manually set in the course"
|
||||
And I log out
|
||||
# See if we got the badge
|
||||
Then I log in as "user1"
|
||||
@@ -168,8 +169,8 @@ Feature: Award badges based on competency completion
|
||||
And I press "Rate"
|
||||
And I set the following fields to these values:
|
||||
| Rating | C |
|
||||
And I wait until the page is ready
|
||||
And I click on "Rate" "button" in the "Rate" "dialogue"
|
||||
And I should see "The competency rating was manually set in the course"
|
||||
And I log out
|
||||
# We should not get the badge yet.
|
||||
Then I log in as "user1"
|
||||
@@ -184,8 +185,8 @@ Feature: Award badges based on competency completion
|
||||
And I press "Rate"
|
||||
And I set the following fields to these values:
|
||||
| Rating | C |
|
||||
And I wait until the page is ready
|
||||
And I click on "Rate" "button" in the "Rate" "dialogue"
|
||||
And I should see "The competency rating was manually set in the course"
|
||||
And I log out
|
||||
# See if we got the badge now.
|
||||
Then I log in as "user1"
|
||||
|
||||
@@ -147,7 +147,6 @@ Feature: My overview block pagination
|
||||
| student1 | C13 | student |
|
||||
When I log in as "student1"
|
||||
And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
|
||||
And I wait until ".block_myoverview [data-control='next']" "css_element" exists
|
||||
Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should contain "disabled"
|
||||
And I log out
|
||||
|
||||
@@ -181,7 +180,6 @@ Feature: My overview block pagination
|
||||
| student1 | C25 | student |
|
||||
When I log in as "student1"
|
||||
And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
|
||||
And I wait until ".block_myoverview [data-control='next']" "css_element" exists
|
||||
Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should not contain "disabled"
|
||||
And the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should not contain "disabled"
|
||||
And I should see "Course 13" in the "Course overview" "block"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['otherusers'] = 'Other Users ({$a})';
|
||||
$string['otherusers'] = 'Other users ({$a})';
|
||||
$string['onlinestatushiding_desc'] = 'If enabled, users have the option to hide their online status from other users.';
|
||||
$string['configtimetosee'] = 'Number of minutes determining the period of inactivity after which a user is no longer considered to be online.';
|
||||
$string['onlinestatushiding'] = 'Online status hiding';
|
||||
|
||||
@@ -37,7 +37,7 @@ Feature: The online users block allow you to see who is currently online on fron
|
||||
When I log in as "student1"
|
||||
And I am on site homepage
|
||||
Then I should not see "Admin User" in the "Online users" "block"
|
||||
And I should see "Other Users (1)" in the "Online users" "block"
|
||||
And I should see "Other users (1)" in the "Online users" "block"
|
||||
And I should see "Student 1" in the "Online users" "block"
|
||||
And I should see "Student 2" in the "Online users" "block"
|
||||
And I should see "3 online users" in the "Online users" "block"
|
||||
@@ -79,7 +79,7 @@ Feature: The online users block allow you to see who is currently online on fron
|
||||
And I am on site homepage
|
||||
Then I should see "2 online user" in the "Online users" "block"
|
||||
And I should not see "Admin" in the "Online users" "block"
|
||||
And I should see "Other Users (1)" in the "Online users" "block"
|
||||
And I should see "Other users (1)" in the "Online users" "block"
|
||||
And I should see "Student 2" in the "Online users" "block"
|
||||
And I should not see "Student 1" in the "Online users" "block"
|
||||
And I log out
|
||||
@@ -94,7 +94,7 @@ Feature: The online users block allow you to see who is currently online on fron
|
||||
And I am on site homepage
|
||||
Then I should see "3 online users" in the "Online users" "block"
|
||||
And I should not see "Admin" in the "Online users" "block"
|
||||
And I should see "Other Users (1)" in the "Online users" "block"
|
||||
And I should see "Other users (1)" in the "Online users" "block"
|
||||
And I should see "Student 2" in the "Online users" "block"
|
||||
And I should see "Student 1" in the "Online users" "block"
|
||||
|
||||
@@ -138,7 +138,7 @@ Feature: The online users block allow you to see who is currently online on fron
|
||||
And I am on site homepage
|
||||
And I should see "2 online user" in the "Online users" "block"
|
||||
And I should not see "Admin" in the "Online users" "block"
|
||||
And I should see "Other Users (1)" in the "Online users" "block"
|
||||
And I should see "Other users (1)" in the "Online users" "block"
|
||||
And I should see "Student 2" in the "Online users" "block"
|
||||
And I should not see "Student 1" in the "Online users" "block"
|
||||
And I log out
|
||||
@@ -148,6 +148,6 @@ Feature: The online users block allow you to see who is currently online on fron
|
||||
When I am on site homepage
|
||||
Then I should see "3 online users" in the "Online users" "block"
|
||||
And I should not see "Admin" in the "Online users" "block"
|
||||
And I should see "Other Users (1)" in the "Online users" "block"
|
||||
And I should see "Other users (1)" in the "Online users" "block"
|
||||
And I should see "Student 2" in the "Online users" "block"
|
||||
And I should see "Student 1" in the "Online users" "block"
|
||||
|
||||
@@ -42,19 +42,21 @@ class block_tag_youtube_edit_form extends block_edit_form {
|
||||
|
||||
// Category setting.
|
||||
$categorychoices = ['0' => get_string('anycategory', 'block_tag_youtube')];
|
||||
$hascategoryerror = false;
|
||||
$categoryerror = '';
|
||||
|
||||
try {
|
||||
// Get all video categories through an API call and add them to the category list.
|
||||
$categorychoices += $this->block->get_categories();
|
||||
} catch (Exception $e) {
|
||||
$hascategoryerror = true;
|
||||
$categoryerror = $e->getMessage();
|
||||
}
|
||||
$mform->addElement('select', 'config_category', get_string('category', 'block_tag_youtube'),
|
||||
$categorychoices);
|
||||
$mform->setDefault('config_category', 0);
|
||||
|
||||
if ($categoryerror) {
|
||||
if ($hascategoryerror) {
|
||||
$notification = $OUTPUT->notification(get_string('categoryerror', 'block_tag_youtube', $categoryerror),
|
||||
'error');
|
||||
$mform->addElement('static', 'config_category_error', '', $notification);
|
||||
|
||||
@@ -97,18 +97,26 @@ class provider implements
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
$contextlist = new contextlist();
|
||||
|
||||
// Only events belonging to that user will be exported.
|
||||
// Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
|
||||
$params = [
|
||||
'usercontext' => CONTEXT_USER,
|
||||
'modulecontext' => CONTEXT_MODULE,
|
||||
'cuserid' => $userid,
|
||||
'muserid' => $userid,
|
||||
'sitecontext' => CONTEXT_SYSTEM,
|
||||
'categorycontext' => CONTEXT_COURSECAT,
|
||||
'coursecontext' => CONTEXT_COURSE,
|
||||
'groupcontext' => CONTEXT_COURSE,
|
||||
'usercontext' => CONTEXT_USER,
|
||||
'cuserid' => $userid,
|
||||
'modulecontext' => CONTEXT_MODULE,
|
||||
'muserid' => $userid
|
||||
];
|
||||
|
||||
// Get contexts of Calendar Events for the owner.
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {event} e ON
|
||||
(e.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
|
||||
(e.categoryid = ctx.instanceid AND e.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
|
||||
(e.courseid = ctx.instanceid AND e.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
|
||||
(e.courseid = ctx.instanceid AND e.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
|
||||
(e.userid = ctx.instanceid AND e.eventtype = 'user' AND ctx.contextlevel = :usercontext)
|
||||
WHERE e.userid = :cuserid
|
||||
UNION
|
||||
@@ -120,8 +128,12 @@ class provider implements
|
||||
WHERE e.userid = :muserid";
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// Only export subscriptions belonging to that user.
|
||||
// Calendar Subscriptions can exist at Site, Course Category, Course, Course Group, or User contexts.
|
||||
$params = [
|
||||
'sitecontext' => CONTEXT_SYSTEM,
|
||||
'categorycontext' => CONTEXT_COURSECAT,
|
||||
'coursecontext' => CONTEXT_COURSE,
|
||||
'groupcontext' => CONTEXT_COURSE,
|
||||
'usercontext' => CONTEXT_USER,
|
||||
'userid' => $userid
|
||||
];
|
||||
@@ -130,6 +142,10 @@ class provider implements
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {event_subscriptions} s ON
|
||||
(s.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
|
||||
(s.categoryid = ctx.instanceid AND s.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
|
||||
(s.courseid = ctx.instanceid AND s.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
|
||||
(s.courseid = ctx.instanceid AND s.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
|
||||
(s.userid = ctx.instanceid AND s.eventtype = 'user' AND ctx.contextlevel = :usercontext)
|
||||
WHERE s.userid = :userid";
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
@@ -148,7 +164,8 @@ class provider implements
|
||||
|
||||
$context = $userlist->get_context();
|
||||
|
||||
// Calendar Events can exist at user (CONTEXT_USER), or Course Modules (CONTEXT_MODULE) contexts.
|
||||
// Calendar Events can exist at Site (CONTEXT_SYSTEM), Course Category (CONTEXT_COURSECAT),
|
||||
// Course and Course Group (CONTEXT_COURSE), User (CONTEXT_USER), or Course Modules (CONTEXT_MODULE) contexts.
|
||||
if ($context->contextlevel == CONTEXT_MODULE) {
|
||||
$params = ['cmid' => $context->instanceid];
|
||||
|
||||
@@ -159,21 +176,43 @@ class provider implements
|
||||
WHERE cm.id = :cmid";
|
||||
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
} else if ($context->contextlevel == CONTEXT_USER) {
|
||||
$params = ['instanceid' => $context->instanceid];
|
||||
} else if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
// Get contexts of Calendar Events for the owner.
|
||||
$sql = "SELECT userid FROM {event} WHERE eventtype = 'site'";
|
||||
$userlist->add_from_sql('userid', $sql, []);
|
||||
|
||||
// Get contexts for Calendar Subscriptions for the owner.
|
||||
$sql = "SELECT userid FROM {event_subscriptions} WHERE eventtype = 'site'";
|
||||
$userlist->add_from_sql('userid', $sql, []);
|
||||
} else if (in_array($context->contextlevel, [CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_USER])) {
|
||||
$eventfields = [
|
||||
CONTEXT_COURSECAT => 'categoryid',
|
||||
CONTEXT_COURSE => 'courseid',
|
||||
CONTEXT_USER => 'userid'
|
||||
];
|
||||
$eventfield = $eventfields[$context->contextlevel];
|
||||
|
||||
$eventtypes = [
|
||||
CONTEXT_COURSECAT => 'category',
|
||||
CONTEXT_COURSE => ['course' , 'group'],
|
||||
CONTEXT_USER => 'user'
|
||||
];
|
||||
list($eventtypesql, $eventtypeparams) = $DB->get_in_or_equal($eventtypes[$context->contextlevel], SQL_PARAMS_NAMED);
|
||||
|
||||
$params = $eventtypeparams + ['instanceid' => $context->instanceid];
|
||||
|
||||
// Get contexts of Calendar Events for the owner.
|
||||
$sql = "SELECT userid
|
||||
FROM {event}
|
||||
WHERE eventtype = 'user'
|
||||
AND userid = :instanceid";
|
||||
WHERE eventtype $eventtypesql
|
||||
AND $eventfield = :instanceid";
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
|
||||
// Get contexts for Calendar Subscriptions for the owner.
|
||||
$sql = "SELECT userid
|
||||
FROM {event_subscriptions}
|
||||
WHERE eventtype = 'user'
|
||||
AND userid = :instanceid";
|
||||
WHERE eventtype $eventtypesql
|
||||
AND $eventfield = :instanceid";
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
}
|
||||
}
|
||||
@@ -237,6 +276,9 @@ class provider implements
|
||||
$userids = $userlist->get_userids();
|
||||
|
||||
$allowedcontexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_USER
|
||||
];
|
||||
@@ -407,8 +449,9 @@ class provider implements
|
||||
protected static function get_calendar_event_ids_by_context(\context $context, $userids = array()) {
|
||||
global $DB;
|
||||
|
||||
// Only events can exist at user and course module contexts can be exported.
|
||||
if (!in_array($context->contextlevel, [CONTEXT_USER, CONTEXT_MODULE])) {
|
||||
// Calendar Events can exist at Site (CONTEXT_SYSTEM), Course Category (CONTEXT_COURSECAT),
|
||||
// Course and Course Group (CONTEXT_COURSE), User (CONTEXT_USER), or Course Modules (CONTEXT_MODULE) contexts.
|
||||
if (!in_array($context->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_USER, CONTEXT_MODULE])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -428,16 +471,35 @@ class provider implements
|
||||
JOIN {modules} m ON m.id = cm.module
|
||||
JOIN {event} e ON e.modulename = m.name AND e.courseid = cm.course AND e.instance = cm.instance
|
||||
WHERE cm.id = :cmid
|
||||
AND e.userid <> 0
|
||||
$whereusersql";
|
||||
} else {
|
||||
$params = ['instanceid' => $context->instanceid];
|
||||
} else if ($context->contextlevel == CONTEXT_SYSTEM) { // Site events.
|
||||
$params = [];
|
||||
$sql = "SELECT DISTINCT e.id AS eventid
|
||||
FROM {event} e
|
||||
WHERE e.eventtype = 'site'
|
||||
$whereusersql";
|
||||
} else { // The rest.
|
||||
$eventfields = [
|
||||
CONTEXT_COURSECAT => 'categoryid',
|
||||
CONTEXT_COURSE => 'courseid',
|
||||
CONTEXT_USER => 'userid'
|
||||
];
|
||||
$eventfield = $eventfields[$context->contextlevel];
|
||||
|
||||
$eventtypes = [
|
||||
CONTEXT_COURSECAT => 'category',
|
||||
CONTEXT_COURSE => ['course' , 'group'],
|
||||
CONTEXT_USER => 'user'
|
||||
];
|
||||
list($eventtypesql, $eventtypeparams) = $DB->get_in_or_equal($eventtypes[$context->contextlevel], SQL_PARAMS_NAMED);
|
||||
|
||||
$params = $eventtypeparams + ['instanceid' => $context->instanceid];
|
||||
|
||||
// Get Calendar Events for the specified Moodle context.
|
||||
$sql = "SELECT DISTINCT e.id AS eventid
|
||||
FROM {event} e
|
||||
WHERE e.eventtype = 'user'
|
||||
AND e.userid = :instanceid
|
||||
WHERE e.eventtype $eventtypesql
|
||||
AND e.{$eventfield} = :instanceid
|
||||
$whereusersql";
|
||||
}
|
||||
|
||||
@@ -458,8 +520,9 @@ class provider implements
|
||||
protected static function get_calendar_subscription_ids_by_context(\context $context, $userids = array()) {
|
||||
global $DB;
|
||||
|
||||
// Only own user event subscriptions can be exported.
|
||||
if ($context->contextlevel != CONTEXT_USER) {
|
||||
// Calendar Subscriptions can exist at Site (CONTEXT_SYSTEM), Course Category (CONTEXT_COURSECAT),
|
||||
// Course and Course Group (CONTEXT_COURSE), or User (CONTEXT_USER) contexts.
|
||||
if (!in_array($context->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_USER])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -469,14 +532,40 @@ class provider implements
|
||||
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
||||
$whereusersql = "AND s.userid {$usersql}";
|
||||
}
|
||||
$params = ['instanceid' => $context->instanceid];
|
||||
|
||||
// Get Calendar Subscriptions for the specified context.
|
||||
$sql = "SELECT DISTINCT s.id AS subscriptionid
|
||||
FROM {event_subscriptions} s
|
||||
WHERE s.eventtype = 'user'
|
||||
AND s.userid = :instanceid
|
||||
$whereusersql";
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
$params = [];
|
||||
|
||||
// Get Calendar Subscriptions for the system context.
|
||||
$sql = "SELECT DISTINCT s.id AS subscriptionid
|
||||
FROM {event_subscriptions} s
|
||||
WHERE s.eventtype = 'site'
|
||||
$whereusersql";
|
||||
} else {
|
||||
$eventfields = [
|
||||
CONTEXT_COURSECAT => 'categoryid',
|
||||
CONTEXT_COURSE => 'courseid',
|
||||
CONTEXT_USER => 'userid'
|
||||
];
|
||||
$eventfield = $eventfields[$context->contextlevel];
|
||||
|
||||
$eventtypes = [
|
||||
CONTEXT_COURSECAT => 'category',
|
||||
CONTEXT_COURSE => ['course' , 'group'],
|
||||
CONTEXT_USER => 'user'
|
||||
];
|
||||
list($eventtypesql, $eventtypeparams) = $DB->get_in_or_equal($eventtypes[$context->contextlevel], SQL_PARAMS_NAMED);
|
||||
|
||||
$params = $eventtypeparams + ['instanceid' => $context->instanceid];
|
||||
|
||||
// Get Calendar Subscriptions for the specified context.
|
||||
$sql = "SELECT DISTINCT s.id AS subscriptionid
|
||||
FROM {event_subscriptions} s
|
||||
WHERE s.eventtype $eventtypesql
|
||||
AND s.{$eventfield} = :instanceid
|
||||
$whereusersql";
|
||||
}
|
||||
|
||||
$params += $userparams;
|
||||
|
||||
return $DB->get_records_sql($sql, $params);
|
||||
@@ -498,8 +587,12 @@ class provider implements
|
||||
list($contextsql1, $contextparams1) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
list($contextsql2, $contextparams2) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
|
||||
// Only User and Course Modules contexts belonging to the user can be exported.
|
||||
// Calendar Events can exist at Site, Course Category, Course, Course Group, User, or Course Modules contexts.
|
||||
$params = [
|
||||
'sitecontext' => CONTEXT_SYSTEM,
|
||||
'categorycontext' => CONTEXT_COURSECAT,
|
||||
'coursecontext' => CONTEXT_COURSE,
|
||||
'groupcontext' => CONTEXT_COURSE,
|
||||
'usercontext' => CONTEXT_USER,
|
||||
'cuserid' => $userid,
|
||||
'modulecontext' => CONTEXT_MODULE,
|
||||
@@ -522,6 +615,10 @@ class provider implements
|
||||
ctx.id AS ctxid
|
||||
FROM {context} ctx
|
||||
INNER JOIN {event} e ON
|
||||
(e.eventtype = 'site' AND ctx.contextlevel = :sitecontext) OR
|
||||
(e.categoryid = ctx.instanceid AND e.eventtype = 'category' AND ctx.contextlevel = :categorycontext) OR
|
||||
(e.courseid = ctx.instanceid AND e.eventtype = 'course' AND ctx.contextlevel = :coursecontext) OR
|
||||
(e.courseid = ctx.instanceid AND e.eventtype = 'group' AND ctx.contextlevel = :groupcontext) OR
|
||||
(e.userid = ctx.instanceid AND e.eventtype = 'user' AND ctx.contextlevel = :usercontext)
|
||||
WHERE e.userid = :cuserid
|
||||
AND ctx.id {$contextsql1}
|
||||
@@ -557,6 +654,10 @@ class provider implements
|
||||
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
|
||||
$params = [
|
||||
'sitecontext' => CONTEXT_SYSTEM,
|
||||
'categorycontext' => CONTEXT_COURSECAT,
|
||||
'coursecontext' => CONTEXT_COURSE,
|
||||
'groupcontext' => CONTEXT_COURSE,
|
||||
'usercontext' => CONTEXT_USER,
|
||||
'userid' => $user->id
|
||||
];
|
||||
@@ -571,6 +672,10 @@ class provider implements
|
||||
s.eventtype as eventtype
|
||||
FROM {context} c
|
||||
INNER JOIN {event_subscriptions} s ON
|
||||
(s.eventtype = 'site' AND c.contextlevel = :sitecontext) OR
|
||||
(s.categoryid = c.instanceid AND s.eventtype = 'category' AND c.contextlevel = :categorycontext) OR
|
||||
(s.courseid = c.instanceid AND s.eventtype = 'course' AND c.contextlevel = :coursecontext) OR
|
||||
(s.courseid = c.instanceid AND s.eventtype = 'group' AND c.contextlevel = :groupcontext) OR
|
||||
(s.userid = c.instanceid AND s.eventtype = 'user' AND c.contextlevel = :usercontext)
|
||||
WHERE s.userid = :userid
|
||||
AND c.id {$contextsql}";
|
||||
|
||||
+42
-92
@@ -234,18 +234,9 @@ class calendar_event {
|
||||
$data->eventtype = 'user';
|
||||
}
|
||||
|
||||
// Only user and user override type of events should record the user id.
|
||||
// For all other event types we set userid to 0 as they are considered shared events.
|
||||
// Default to the current user.
|
||||
if (empty($data->userid)) {
|
||||
if ($this->allow_custom_userid($data)) {
|
||||
$data->userid = $USER->id;
|
||||
} else {
|
||||
$data->userid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($data->courseid) && $data->eventtype == 'site') {
|
||||
$data->courseid = SITEID;
|
||||
$data->userid = $USER->id;
|
||||
}
|
||||
|
||||
if (!empty($data->timeduration) && is_array($data->timeduration)) {
|
||||
@@ -323,28 +314,6 @@ class calendar_event {
|
||||
return !empty($this->properties->{$key});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether user id should be stored on the event or not.
|
||||
*
|
||||
* Only user events and user overrides type of events should retain user id.
|
||||
*
|
||||
* @param stdClass $data The event data object.
|
||||
* @return bool
|
||||
*/
|
||||
protected function allow_custom_userid(stdClass $data): bool {
|
||||
if ($data->eventtype === 'user') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$isactionevent = !empty($data->type) && $data->type == CALENDAR_EVENT_TYPE_ACTION;
|
||||
$isuseroverride = !empty($data->priority) && $data->priority == CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
|
||||
if ($isactionevent && $isuseroverride) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the context value needed for an event.
|
||||
*
|
||||
@@ -519,18 +488,49 @@ class calendar_event {
|
||||
}
|
||||
|
||||
if ($usingeditor) {
|
||||
switch ($this->properties->eventtype) {
|
||||
case 'user':
|
||||
$this->properties->courseid = 0;
|
||||
$this->properties->course = 0;
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
case 'site':
|
||||
$this->properties->courseid = SITEID;
|
||||
$this->properties->course = SITEID;
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
case 'course':
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
case 'category':
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->category = 0;
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
case 'group':
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
default:
|
||||
// We should NEVER get here, but just incase we do lets fail gracefully.
|
||||
$usingeditor = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we are actually using the editor, we recalculate the context because some default values
|
||||
// were set when calculate_context() was called from the constructor.
|
||||
$this->properties->context = $this->calculate_context();
|
||||
$this->editorcontext = $this->get_context();
|
||||
if ($usingeditor) {
|
||||
$this->properties->context = $this->calculate_context();
|
||||
$this->editorcontext = $this->get_context();
|
||||
}
|
||||
|
||||
$editor = $this->properties->description;
|
||||
$this->properties->format = $this->properties->description['format'];
|
||||
$this->properties->description = $this->properties->description['text'];
|
||||
}
|
||||
|
||||
$this->set_default_event_ids();
|
||||
|
||||
// Insert the event into the database.
|
||||
$this->properties->id = $DB->insert_record('event', $this->properties);
|
||||
|
||||
@@ -675,8 +675,6 @@ class calendar_event {
|
||||
$event->trigger();
|
||||
}
|
||||
} else {
|
||||
$this->set_default_event_ids();
|
||||
|
||||
$DB->update_record('event', $this->properties);
|
||||
$event = self::load($this->properties->id);
|
||||
$this->properties = $event->properties();
|
||||
@@ -871,49 +869,6 @@ class calendar_event {
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default event ids.
|
||||
*/
|
||||
protected function set_default_event_ids(): void {
|
||||
global $USER;
|
||||
$userid = (!empty($this->properties->userid)) ? $this->properties->userid : $USER->id;
|
||||
switch ($this->properties->eventtype) {
|
||||
case 'user':
|
||||
$this->properties->courseid = 0;
|
||||
$this->properties->course = 0;
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->userid = $userid;
|
||||
break;
|
||||
case 'site':
|
||||
$this->properties->courseid = SITEID;
|
||||
$this->properties->course = SITEID;
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->userid = 0;
|
||||
break;
|
||||
case 'course':
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->userid = 0;
|
||||
break;
|
||||
case 'category':
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->category = 0;
|
||||
$this->properties->userid = 0;
|
||||
break;
|
||||
case 'group':
|
||||
$this->properties->userid = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Only user overrides type of events should store user's id.
|
||||
$isactionevent = !empty($this->properties->type) && $this->properties->type == CALENDAR_EVENT_TYPE_ACTION;
|
||||
$isuseroverride = $isactionevent && !empty($this->properties->priority) &&
|
||||
$this->properties->priority == CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
|
||||
|
||||
if ($isuseroverride) {
|
||||
$this->properties->userid = $userid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility of an event
|
||||
*
|
||||
@@ -2441,9 +2396,7 @@ function calendar_get_default_courses($courseid = null, $fields = '*', $canmanag
|
||||
$prefixedfields = array_map(function($value) {
|
||||
return 'c.' . trim(strtolower($value));
|
||||
}, $fieldlist);
|
||||
if (!in_array('c.visible', $prefixedfields) && !in_array('c.*', $prefixedfields)) {
|
||||
$prefixedfields[] = 'c.visible';
|
||||
}
|
||||
|
||||
$courses = get_courses('all', 'c.shortname', implode(',', $prefixedfields));
|
||||
} else {
|
||||
$courses = enrol_get_users_courses($userid, true, $fields);
|
||||
@@ -2820,9 +2773,7 @@ function calendar_add_subscription($sub) {
|
||||
// User events.
|
||||
$sub->courseid = 0;
|
||||
}
|
||||
|
||||
// Only subscriptions for user type should store user id, shared events (course,site...) should have userid set to 0.
|
||||
$sub->userid = ($sub->eventtype == 'user') ? $USER->id : 0;
|
||||
$sub->userid = $USER->id;
|
||||
|
||||
// File subscriptions never update.
|
||||
if (empty($sub->url)) {
|
||||
@@ -2951,7 +2902,7 @@ function calendar_add_icalendar_event($event, $unused = null, $subscriptionid, $
|
||||
// We should never do anything with an event without a subscription reference.
|
||||
$sub = calendar_get_subscription($subscriptionid);
|
||||
$eventrecord->subscriptionid = $subscriptionid;
|
||||
$eventrecord->userid = ($sub->eventtype == 'user') ? $sub->userid : 0;
|
||||
$eventrecord->userid = $sub->userid;
|
||||
$eventrecord->groupid = $sub->groupid;
|
||||
$eventrecord->courseid = $sub->courseid;
|
||||
$eventrecord->categoryid = $sub->categoryid;
|
||||
@@ -3253,6 +3204,7 @@ function calendar_update_subscription($subscription) {
|
||||
* @return bool true if current user can edit the subscription else false
|
||||
*/
|
||||
function calendar_can_edit_subscription($subscriptionorid) {
|
||||
global $USER;
|
||||
if (is_array($subscriptionorid)) {
|
||||
$subscription = (object)$subscriptionorid;
|
||||
} else if (is_object($subscriptionorid)) {
|
||||
@@ -3273,7 +3225,7 @@ function calendar_can_edit_subscription($subscriptionorid) {
|
||||
calendar_get_allowed_types($allowed, $courseid, null, $category);
|
||||
switch ($subscription->eventtype) {
|
||||
case 'user':
|
||||
return $allowed->user;
|
||||
return ($USER->id == $subscription->userid && $allowed->user);
|
||||
case 'course':
|
||||
if (isset($allowed->courses[$courseid])) {
|
||||
return $allowed->courses[$courseid];
|
||||
@@ -3310,8 +3262,6 @@ function calendar_get_calendar_context($subscription) {
|
||||
// Determine context based on calendar type.
|
||||
if ($subscription->eventtype === 'site') {
|
||||
$context = \context_course::instance(SITEID);
|
||||
} else if ($subscription->eventtype === 'category') {
|
||||
$context = \context_coursecat::instance($subscription->categoryid);
|
||||
} else if ($subscription->eventtype === 'group' || $subscription->eventtype === 'course') {
|
||||
$context = \context_course::instance($subscription->courseid);
|
||||
} else {
|
||||
|
||||
@@ -608,8 +608,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -619,8 +619,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 6; $i < 12; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course2->id,
|
||||
@@ -663,8 +663,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -674,8 +674,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 6; $i < 12; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course2->id,
|
||||
@@ -719,8 +719,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 6; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -730,8 +730,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 6; $i < 12; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course2->id,
|
||||
@@ -773,8 +773,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 21; $i++) {
|
||||
$records[] = create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -784,8 +784,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 21; $i < 41; $i++) {
|
||||
$records[] = create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course2->id,
|
||||
@@ -830,8 +830,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 41; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -901,8 +901,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 21; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -912,8 +912,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 21; $i < 41; $i++) {
|
||||
create_event([
|
||||
'name' => sprintf('Event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course2->id,
|
||||
@@ -979,8 +979,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 11; $i++) {
|
||||
$records[] = create_event([
|
||||
'name' => sprintf('1 event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -990,8 +990,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 11; $i++) {
|
||||
$records[] = create_event([
|
||||
'name' => sprintf('2 event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course1->id,
|
||||
@@ -1002,8 +1002,8 @@ class core_calendar_event_vault_testcase extends advanced_testcase {
|
||||
for ($i = 1; $i < 11; $i++) {
|
||||
$records[] = create_event([
|
||||
'name' => sprintf('3 event %d', $i),
|
||||
'eventtype' => 'due',
|
||||
'userid' => 0,
|
||||
'eventtype' => 'user',
|
||||
'userid' => $user->id,
|
||||
'timesort' => $i,
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
'courseid' => $course2->id,
|
||||
|
||||
@@ -96,6 +96,13 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
} else {
|
||||
$prop->repeat = 1;
|
||||
}
|
||||
if (empty($prop->userid)) {
|
||||
if (!empty($userid)) {
|
||||
$prop->userid = $userid;
|
||||
} else {
|
||||
$prop->userid = 0;
|
||||
}
|
||||
}
|
||||
if (!isset($prop->courseid)) {
|
||||
$prop->courseid = $SITE->id;
|
||||
}
|
||||
@@ -384,54 +391,44 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->setUser($user);
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// There should be a single user event.
|
||||
$this->assertEquals(1, count($events['events']));
|
||||
// There should be only two events (course and group).
|
||||
$this->assertEquals(2, count($events['warnings']));
|
||||
$this->assertEquals(2, count($events['events'])); // site, user.
|
||||
$this->assertEquals(2, count($events['warnings'])); // course, group.
|
||||
|
||||
$role = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, $role->id);
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// There should be a site and both course events.
|
||||
$this->assertEquals(3, count($events['events']));
|
||||
// There should be a warning related to group event.
|
||||
$this->assertEquals(1, count($events['warnings']));
|
||||
$this->assertEquals(4, count($events['events'])); // site, user, both course events.
|
||||
$this->assertEquals(1, count($events['warnings'])); // group.
|
||||
|
||||
$options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + HOURSECS);
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// There should be a site and a course event.
|
||||
$this->assertEquals(2, count($events['events']));
|
||||
// There should be a warning related to group event.
|
||||
$this->assertEquals(1, count($events['warnings']));
|
||||
$this->assertEquals(3, count($events['events'])); // site, user, one course event.
|
||||
$this->assertEquals(1, count($events['warnings'])); // group.
|
||||
|
||||
groups_add_member($group, $user);
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// There should be a site, group, one course event.
|
||||
$this->assertEquals(3, count($events['events']));
|
||||
$this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
|
||||
$this->assertEquals(0, count($events['warnings']));
|
||||
|
||||
$paramevents = array ('courseids' => array($course->id), 'groupids' => array($group->id));
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// There should be only site, course and group events.
|
||||
$this->assertEquals(3, count($events['events']));
|
||||
$this->assertEquals(4, count($events['events'])); // site, user, group, one course event.
|
||||
$this->assertEquals(0, count($events['warnings']));
|
||||
|
||||
$paramevents = array ('groupids' => array($group->id, 23));
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// There should be only site and group events.
|
||||
$this->assertEquals(2, count($events['events']));
|
||||
$this->assertEquals(3, count($events['events'])); // site, user, group.
|
||||
$this->assertEquals(1, count($events['warnings']));
|
||||
|
||||
$paramevents = array ('courseids' => array(23));
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// There should be a single site event.
|
||||
$this->assertEquals(1, count($events['events']));
|
||||
$this->assertEquals(2, count($events['events'])); // site, user.
|
||||
$this->assertEquals(1, count($events['warnings']));
|
||||
|
||||
$paramevents = array ();
|
||||
@@ -472,7 +469,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
|
||||
$this->assertCount(4, $events['events']);
|
||||
$this->assertCount(5, $events['events']);
|
||||
|
||||
// Hide the assignment.
|
||||
set_coursemodule_visible($assign->cmid, 0);
|
||||
@@ -483,7 +480,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// Expect one less.
|
||||
$this->assertCount(3, $events['events']);
|
||||
$this->assertCount(4, $events['events']);
|
||||
|
||||
// Create some category events.
|
||||
$this->setAdminUser();
|
||||
@@ -842,7 +839,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$now = time();
|
||||
// Create two events - one for everybody in the course and one only for the first student.
|
||||
$event1 = $this->create_calendar_event('Base event', 0, 'due', 0, $now + DAYSECS, $params + ['courseid' => $course->id]);
|
||||
$event2 = $this->create_calendar_event('User event', 0, 'user', 0, $now + 2 * DAYSECS, $params + ['courseid' => 0]);
|
||||
$event2 = $this->create_calendar_event('User event', $user->id, 'due', 0, $now + 2*DAYSECS, $params + ['courseid' => 0]);
|
||||
|
||||
// Retrieve course events for the second student - only one "Base event" is returned.
|
||||
$this->setUser($user2);
|
||||
@@ -854,13 +851,14 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals(0, count($events['warnings']));
|
||||
$this->assertEquals('Base event', $events['events'][0]['name']);
|
||||
|
||||
// Retrieve events for the first student.
|
||||
// Retrieve events for the first student - both events are returned.
|
||||
$this->setUser($user);
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
$this->assertEquals(1, count($events['events']));
|
||||
$this->assertEquals(2, count($events['events']));
|
||||
$this->assertEquals(0, count($events['warnings']));
|
||||
$this->assertEquals('Base event', $events['events'][0]['name']);
|
||||
$this->assertEquals('User event', $events['events'][1]['name']);
|
||||
|
||||
// Retrieve events by id as a teacher, 'User event' should be returned since teacher has access to this course.
|
||||
$this->setUser($teacher);
|
||||
@@ -1723,7 +1721,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$timedurationuntil->add($interval);
|
||||
$formdata = [
|
||||
'id' => 0,
|
||||
'userid' => 0,
|
||||
'userid' => $user->id,
|
||||
'modulename' => '',
|
||||
'instance' => 0,
|
||||
'visible' => 1,
|
||||
@@ -1784,7 +1782,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$timedurationuntil->add($interval);
|
||||
$formdata = [
|
||||
'id' => 0,
|
||||
'userid' => 0,
|
||||
'userid' => $user->id,
|
||||
'modulename' => '',
|
||||
'instance' => 0,
|
||||
'visible' => 1,
|
||||
@@ -1830,7 +1828,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
);
|
||||
|
||||
$event = $result['event'];
|
||||
$this->assertEquals(0, $event['userid']);
|
||||
$this->assertEquals($user->id, $event['userid']);
|
||||
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
|
||||
$this->assertEquals($formdata['name'], $event['name']);
|
||||
}
|
||||
@@ -1850,7 +1848,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$timedurationuntil->add($interval);
|
||||
$formdata = [
|
||||
'id' => 0,
|
||||
'userid' => 0,
|
||||
'userid' => $user->id,
|
||||
'modulename' => '',
|
||||
'instance' => 0,
|
||||
'visible' => 1,
|
||||
@@ -1961,7 +1959,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
);
|
||||
|
||||
$event = $result['event'];
|
||||
$this->assertEquals(0, $event['userid']);
|
||||
$this->assertEquals($user->id, $event['userid']);
|
||||
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
|
||||
$this->assertEquals($formdata['name'], $event['name']);
|
||||
$this->assertEquals($formdata['courseid'], $event['course']['id']);
|
||||
@@ -2049,7 +2047,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$timedurationuntil->add($interval);
|
||||
$formdata = [
|
||||
'id' => 0,
|
||||
'userid' => 0,
|
||||
'userid' => $user->id,
|
||||
'modulename' => '',
|
||||
'instance' => 0,
|
||||
'visible' => 1,
|
||||
@@ -2165,7 +2163,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
);
|
||||
|
||||
$event = $result['event'];
|
||||
$this->assertEquals(0, $event['userid']);
|
||||
$this->assertEquals($user->id, $event['userid']);
|
||||
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
|
||||
$this->assertEquals($formdata['name'], $event['name']);
|
||||
$this->assertEquals($group->id, $event['groupid']);
|
||||
@@ -2188,7 +2186,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$timedurationuntil->add($interval);
|
||||
$formdata = [
|
||||
'id' => 0,
|
||||
'userid' => 0,
|
||||
'userid' => $user->id,
|
||||
'modulename' => '',
|
||||
'instance' => 0,
|
||||
'visible' => 1,
|
||||
@@ -2239,7 +2237,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
);
|
||||
|
||||
$event = $result['event'];
|
||||
$this->assertEquals(0, $event['userid']);
|
||||
$this->assertEquals($user->id, $event['userid']);
|
||||
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
|
||||
$this->assertEquals($formdata['name'], $event['name']);
|
||||
$this->assertEquals($group->id, $event['groupid']);
|
||||
@@ -2262,7 +2260,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$timedurationuntil->add($interval);
|
||||
$formdata = [
|
||||
'id' => 0,
|
||||
'userid' => 0,
|
||||
'userid' => $user->id,
|
||||
'modulename' => '',
|
||||
'instance' => 0,
|
||||
'visible' => 1,
|
||||
@@ -2312,7 +2310,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
);
|
||||
|
||||
$event = $result['event'];
|
||||
$this->assertEquals(0, $event['userid']);
|
||||
$this->assertEquals($user->id, $event['userid']);
|
||||
$this->assertEquals($formdata['eventtype'], $event['eventtype']);
|
||||
$this->assertEquals($formdata['name'], $event['name']);
|
||||
$this->assertEquals($group->id, $event['groupid']);
|
||||
@@ -2584,10 +2582,8 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$category = $generator->create_category(['visible' => 0]);
|
||||
$name = 'Category Event (category: ' . $category->id . ')';
|
||||
$record = new stdClass();
|
||||
$record->eventtype = 'category';
|
||||
$record->categoryid = $category->id;
|
||||
$record->userid = 0;
|
||||
$categoryevent = $this->create_calendar_event($name, 0, 'category', 0, time(), $record);
|
||||
$categoryevent = $this->create_calendar_event($name, $USER->id, 'category', 0, time(), $record);
|
||||
|
||||
$events = [
|
||||
'eventids' => [$categoryevent->id]
|
||||
|
||||
@@ -66,6 +66,102 @@ function create_event($properties) {
|
||||
return $event->create($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a x number of events for each event type.
|
||||
*
|
||||
* @param int $quantity The quantity of events to be created.
|
||||
* @return array List of created events.
|
||||
*/
|
||||
function create_standard_events(int $quantity): array {
|
||||
$types = ['site', 'category', 'course', 'group', 'user'];
|
||||
|
||||
$events = [];
|
||||
foreach ($types as $eventtype) {
|
||||
// Create five events of each event type.
|
||||
for ($i = 0; $i < $quantity; $i++) {
|
||||
$events[] = create_event(['eventtype' => $eventtype]);
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create an action event.
|
||||
*
|
||||
* @param array $data The event data.
|
||||
* @return bool|calendar_event
|
||||
*/
|
||||
function create_action_event(array $data) {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
if (!isset($data['modulename']) || !isset($data['instance'])) {
|
||||
throw new coding_exception('Module and instance should be specified when creating an action event.');
|
||||
}
|
||||
|
||||
$isuseroverride = isset($data->priority) && $data->priority == CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
|
||||
if ($isuseroverride) {
|
||||
if (!in_array($data['modulename'], ['assign', 'lesson', 'quiz'])) {
|
||||
throw new coding_exception('Only assign, lesson and quiz modules supports overrides');
|
||||
}
|
||||
}
|
||||
|
||||
$event = array_merge($data, [
|
||||
'eventtype' => isset($data['eventtype']) ? $data['eventtype'] : 'open',
|
||||
'courseid' => isset($data['courseid']) ? $data['courseid'] : 0,
|
||||
'instance' => $data['instance'],
|
||||
'modulename' => $data['modulename'],
|
||||
'type' => CALENDAR_EVENT_TYPE_ACTION,
|
||||
]);
|
||||
|
||||
return create_event($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create an user override calendar event.
|
||||
*
|
||||
* @param string $modulename The modulename.
|
||||
* @param int $instanceid The instance id.
|
||||
* @param int $userid The user id.
|
||||
* @return calendar_event|false
|
||||
*/
|
||||
function create_user_override_event(string $modulename, int $instanceid, int $userid) {
|
||||
if (!isset($userid)) {
|
||||
throw new coding_exception('Must specify userid when creating a user override.');
|
||||
}
|
||||
|
||||
return create_action_event([
|
||||
'modulename' => $modulename,
|
||||
'instance' => $instanceid,
|
||||
'userid' => $userid,
|
||||
'priority' => CALENDAR_EVENT_USER_OVERRIDE_PRIORITY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create an group override calendar event.
|
||||
*
|
||||
* @param string $modulename The modulename.
|
||||
* @param int $instanceid The instance id.
|
||||
* @param int $courseid The course id.
|
||||
* @param int $groupid The group id.
|
||||
* @return calendar_event|false
|
||||
*/
|
||||
function create_group_override_event(string $modulename, int $instanceid, int $courseid, int $groupid) {
|
||||
if (!isset($groupid)) {
|
||||
throw new coding_exception('Must specify groupid when creating a group override.');
|
||||
}
|
||||
|
||||
return create_action_event([
|
||||
'groupid' => $groupid,
|
||||
'courseid' => $courseid,
|
||||
'modulename' => $modulename,
|
||||
'instance' => $instanceid,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A test factory that will create action events.
|
||||
*
|
||||
|
||||
@@ -154,8 +154,8 @@ class core_calendar_local_api_testcase extends advanced_testcase {
|
||||
'courseid' => $course->id,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $moduleinstance->id,
|
||||
'userid' => 0,
|
||||
'eventtype' => 'due',
|
||||
'userid' => 1,
|
||||
'eventtype' => 'user',
|
||||
'repeats' => 0,
|
||||
'timestart' => 1,
|
||||
];
|
||||
@@ -205,8 +205,8 @@ class core_calendar_local_api_testcase extends advanced_testcase {
|
||||
'courseid' => $course->id,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $moduleinstance->id,
|
||||
'userid' => 0,
|
||||
'eventtype' => 'due',
|
||||
'userid' => 1,
|
||||
'eventtype' => 'user',
|
||||
'repeats' => 0,
|
||||
'timestart' => 1,
|
||||
];
|
||||
@@ -257,8 +257,8 @@ class core_calendar_local_api_testcase extends advanced_testcase {
|
||||
'courseid' => $course->id,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $moduleinstance->id,
|
||||
'userid' => 0,
|
||||
'eventtype' => 'due',
|
||||
'userid' => 1,
|
||||
'eventtype' => 'user',
|
||||
'repeats' => 0,
|
||||
'timestart' => 1,
|
||||
];
|
||||
|
||||
+90
-103
@@ -80,8 +80,8 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
$course3context = context_course::instance($course3->id);
|
||||
|
||||
// Add Category Calendar Events for Category.
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
|
||||
$this->create_test_standard_calendar_event('category', $user->id, time(), '', $category->id);
|
||||
$this->create_test_standard_calendar_event('category', $user->id, time(), '', $category->id);
|
||||
|
||||
// Add User Calendar Events for User.
|
||||
$this->create_test_standard_calendar_event('user', $user->id, time(), '');
|
||||
@@ -89,43 +89,38 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
$this->create_test_standard_calendar_event('user', $user->id, time(), '', 0, $course2->id);
|
||||
|
||||
// Add a Course Calendar Event for Course 1.
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('course', $user->id, time(), '', 0, $course1->id);
|
||||
|
||||
// Add a Course Assignment Action Calendar Event for Course 2.
|
||||
$assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
|
||||
$params['course'] = $course2->id;
|
||||
$params['assignsubmission_onlinetext_enabled'] = 1;
|
||||
$assign1 = $assigngenerator->create_instance($params);
|
||||
$assign1cm = get_coursemodule_from_instance('assign', $assign1->id);
|
||||
$assign1context = context_module::instance($assign1cm->id);
|
||||
$this->create_test_action_calendar_event('duedate', $course2->id, $assign1->id, 'assign', 0, time());
|
||||
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $assign1->id, 'assign', 0, time());
|
||||
|
||||
// Add a due date event similar to a user override in another assign instance.
|
||||
$assign2 = $assigngenerator->create_instance($params);
|
||||
$assign2cm = get_coursemodule_from_instance('assign', $assign2->id);
|
||||
$assign2context = context_module::instance($assign2cm->id);
|
||||
$this->create_test_action_calendar_event('duedate', $course2->id, $assign2->id, 'assign', $user->id, time());
|
||||
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $assign2->id, 'assign', $user->id, time());
|
||||
$instance = $generator->create_instance($params);
|
||||
$cm = get_coursemodule_from_instance('assign', $instance->id);
|
||||
$modulecontext = context_module::instance($cm->id);
|
||||
$assign = new assign($modulecontext, $cm, $course2);
|
||||
$this->create_test_action_calendar_event('duedate', $course2->id, $instance->id, 'assign', $user->id, time());
|
||||
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $instance->id, 'assign', $user->id, time());
|
||||
|
||||
// Add a Calendar Subscription and Group Calendar Event to Course 3.
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', 0, 0, $course3->id);
|
||||
$this->create_test_standard_calendar_event('group', 0, time(), '', 0, $course3->id, $course3group->id);
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user->id, 0, $course3->id);
|
||||
$this->create_test_standard_calendar_event('group', $user->id, time(), '', 0, $course3->id, $course3group->id);
|
||||
|
||||
// The user will be in these contexts.
|
||||
$usercontextids = [
|
||||
$usercontext->id,
|
||||
$assign2context->id,
|
||||
$categorycontext->id,
|
||||
$course1context->id,
|
||||
$modulecontext->id,
|
||||
$course3context->id
|
||||
];
|
||||
// Retrieve the user's context ids.
|
||||
$contextids = provider::get_contexts_for_userid($user->id);
|
||||
|
||||
// Check the user context list and retrieved user context lists contains the same number of records.
|
||||
$this->assertCount(count($usercontextids), $contextids->get_contextids());
|
||||
$this->assertEquals(count($usercontextids), count($contextids->get_contextids()));
|
||||
// There should be no difference between the contexts.
|
||||
$this->assertEmpty(array_diff($usercontextids, $contextids->get_contextids()));
|
||||
// Check that the module context for the assignment without user override event is not included.
|
||||
$this->assertNotContains($assign1context->id, $contextids->get_contextids());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,12 +150,12 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
$event1 = $this->create_test_standard_calendar_event('user', $user->id, time(), '');
|
||||
|
||||
// Add Category Calendar Events for Category.
|
||||
$event2 = $this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
|
||||
$event2 = $this->create_test_standard_calendar_event('category', $user->id, time(), '', $category->id);
|
||||
|
||||
// Add two Course Calendar Event for Course 1 and set the same time (1 day a head).
|
||||
$time = strtotime('+1 day', time());
|
||||
$event3 = $this->create_test_standard_calendar_event('course', 0, $time, 'ABC', 0, $course1->id);
|
||||
$event4 = $this->create_test_standard_calendar_event('course', 0, $time, 'DEF', 0, $course1->id);
|
||||
$event3 = $this->create_test_standard_calendar_event('course', $user->id, $time, 'ABC', 0, $course1->id);
|
||||
$event4 = $this->create_test_standard_calendar_event('course', $user->id, $time, 'DEF', 0, $course1->id);
|
||||
|
||||
// Add a Course Assignment Action Calendar Event for Course 2.
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
|
||||
@@ -174,7 +169,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
|
||||
// Add a Calendar Subscription and Group Calendar Event to Course 3.
|
||||
$subscription1 = $this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user->id, 0, $course3->id);
|
||||
$event6 = $this->create_test_standard_calendar_event('group', 0, time(), '', 0, $course3->id, $course3group->id);
|
||||
$event6 = $this->create_test_standard_calendar_event('group', $user->id, time(), '', 0, $course3->id, $course3group->id);
|
||||
|
||||
// Retrieve the user's context ids.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
@@ -325,16 +320,16 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
|
||||
// Add a Course Calendar Event by User 1 for Course 1 and Course 2.
|
||||
$this->setUser($user1);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course2->id);
|
||||
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course2->id);
|
||||
|
||||
// Add a Calendar Subscription by User 1 for Course 1.
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user1->id, 0, $course1->id);
|
||||
|
||||
// Add a Course Calendar Event by User 2 for Course 1 and Course 2.
|
||||
$this->setUser($user2);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course2->id);
|
||||
$this->create_test_standard_calendar_event('course', $user2->id, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('course', $user2->id, time(), '', 0, $course2->id);
|
||||
|
||||
// Add a Calendar Subscription by User 2 for Course 2.
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user2->id, 0, $course2->id);
|
||||
@@ -347,23 +342,23 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
$cm = get_coursemodule_from_instance('assign', $instance->id);
|
||||
$modulecontext = context_module::instance($cm->id);
|
||||
$assign = new assign($modulecontext, $cm, $course2);
|
||||
$this->create_test_action_calendar_event('duedate', $course2->id, $instance->id, 'assign', 0, time());
|
||||
$this->create_test_action_calendar_event('duedate', $course2->id, $instance->id, 'assign', $user2->id, time());
|
||||
$this->create_test_action_calendar_event('gradingduedate', $course2->id, $instance->id, 'assign', $user2->id, time());
|
||||
|
||||
// Delete all Calendar Events for all Users by Context for Course 1.
|
||||
provider::delete_data_for_all_users_in_context($course1context);
|
||||
|
||||
// The course1 events should still exist, since it belongs to the course and not to the user who created them.
|
||||
// Verify all Calendar Events for Course 1 were deleted.
|
||||
$events = $DB->get_records('event', array('courseid' => $course1->id));
|
||||
$this->assertCount(2, $events);
|
||||
// Course1 subscription should still exist, since it belongs to the course and not to the user who created them.
|
||||
$this->assertCount(0, $events);
|
||||
// Verify all Calendar Subscriptions for Course 1 were deleted.
|
||||
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course1->id));
|
||||
$this->assertCount(1, $subscriptions);
|
||||
$this->assertCount(0, $subscriptions);
|
||||
|
||||
// The course2 events should still exist, since it belongs to the course and not to the user who created them.
|
||||
// Verify all Calendar Events for Course 2 exists still.
|
||||
$events = $DB->get_records('event', array('courseid' => $course2->id));
|
||||
$this->assertCount(4, $events);
|
||||
// Course2 subscription should still exist, since it belongs to the course and not to the user who created them.
|
||||
// Verify all Calendar Subscriptions for Course 2 exists still.
|
||||
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course2->id));
|
||||
$this->assertCount(1, $subscriptions);
|
||||
|
||||
@@ -372,10 +367,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
|
||||
// Verify all Calendar Events for Course 2 context were deleted.
|
||||
$events = $DB->get_records('event', array('courseid' => $course2->id, 'modulename' => '0'));
|
||||
$this->assertCount(2, $events);
|
||||
$this->assertCount(0, $events);
|
||||
// Verify all Calendar Subscriptions for Course 2 were deleted.
|
||||
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course2->id));
|
||||
$this->assertCount(1, $subscriptions);
|
||||
$this->assertCount(0, $subscriptions);
|
||||
|
||||
// Verify all Calendar Events for the assignment exists still.
|
||||
$events = $DB->get_records('event', array('modulename' => 'assign'));
|
||||
@@ -384,9 +379,9 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
// Delete all Calendar Events for all Users by Context for the assignment.
|
||||
provider::delete_data_for_all_users_in_context($modulecontext);
|
||||
|
||||
// Verify that the action event still exists since it is not a user override.
|
||||
// Verify all Calendar Events for the assignment context were deleted.
|
||||
$events = $DB->get_records('event', array('modulename' => 'assign'));
|
||||
$this->assertCount(1, $events);
|
||||
$this->assertCount(0, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,28 +401,25 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
$course1 = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
|
||||
// Add 6 events for user 1 for various contexts.
|
||||
// Add 5 Calendar Events for User 1 for various contexts.
|
||||
$this->setUser($user1);
|
||||
$this->create_test_standard_calendar_event('user', $user1->id, time(), '');
|
||||
$this->create_test_standard_calendar_event('user', $user1->id, time(), '');
|
||||
$this->create_test_standard_calendar_event('site', 0, time(), '', 0, 1);
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course2->id);
|
||||
$this->create_test_standard_calendar_event('site', $user1->id, time(), '', 0, 1);
|
||||
$this->create_test_standard_calendar_event('category', $user1->id, time(), '', $category->id);
|
||||
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('course', $user1->id, time(), '', 0, $course2->id);
|
||||
|
||||
// Add two subscription for user 1 at course and user contexts.
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', 0, 0, $course2->id);
|
||||
$this->create_test_calendar_subscription('user', 'https://calendar.google.com/', $user1->id);
|
||||
// Add 1 Calendar Subscription for User 1 at course context.
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user1->id, 0, $course2->id);
|
||||
|
||||
// Add four events for user 2 for various contexts.
|
||||
// Add 3 Calendar Events for User 2 for various contexts.
|
||||
$this->setUser($user2);
|
||||
$this->create_test_standard_calendar_event('user', $user2->id, time(), '');
|
||||
$this->create_test_standard_calendar_event('user', $user2->id, time(), '');
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '', $category->id);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '', 0, $course1->id);
|
||||
$this->create_test_standard_calendar_event('category', $user2->id, time(), '', $category->id);
|
||||
$this->create_test_standard_calendar_event('course', $user2->id, time(), '', 0, $course1->id);
|
||||
|
||||
// Add a calendar Subscription for User 2 at course context.
|
||||
$this->create_test_calendar_subscription('user', 'https://calendar.google.com/', $user2->id, 0, $course2->id);
|
||||
// Add 1 Calendar Subscription for User 2 at course context.
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user2->id, 0, $course2->id);
|
||||
|
||||
// Retrieve the user's context ids.
|
||||
$contextlist = provider::get_contexts_for_userid($user1->id);
|
||||
@@ -436,31 +428,17 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
// Delete all Calendar data for User 1.
|
||||
provider::delete_data_for_user($approvedcontextlist);
|
||||
|
||||
// All user events created by user1 should have been removed.
|
||||
// Test all Calendar Events and Subscriptions for User 1 equals zero.
|
||||
$events = $DB->get_records('event', ['userid' => $user1->id]);
|
||||
$this->assertCount(0, $events);
|
||||
// The subscription created by user1 should have been deleted.
|
||||
$eventsubscriptions = $DB->get_records('event_subscriptions', ['userid' => $user1->id]);
|
||||
$this->assertCount(0, $eventsubscriptions);
|
||||
|
||||
// Test all calendar events and subscriptions for user2 still exists and matches the same number created.
|
||||
// Test all Calendar Events and Subscriptions for User 2 still exists and matches the same number created.
|
||||
$events = $DB->get_records('event', ['userid' => $user2->id]);
|
||||
$this->assertCount(2, $events);
|
||||
// The subscription created by user2 should have not been deleted.
|
||||
$this->assertCount(3, $events);
|
||||
$eventsubscriptions = $DB->get_records('event_subscriptions', ['userid' => $user2->id]);
|
||||
$this->assertCount(1, $eventsubscriptions);
|
||||
|
||||
// There should be only six "shared" events created (userid equal zero).
|
||||
// One site, one category and two course events that were created by user1.
|
||||
// One category and one course event created by user2.
|
||||
$events = $DB->get_records('event', ['userid' => 0]);
|
||||
$this->assertCount(6, $events);
|
||||
|
||||
// There should be eight event created in total.
|
||||
// One site, one category and two course events that were created by user1.
|
||||
// One category, one course and two user events created by user2.
|
||||
$events = $DB->get_records('event');
|
||||
$this->assertCount(8, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -496,10 +474,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
|
||||
// Add Category Calendar Events for Category.
|
||||
$this->setUser($user1);
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('category', $user1->id, time(), '',
|
||||
$category->id);
|
||||
$this->setUser($user2);
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('category', $user2->id, time(), '',
|
||||
$category->id);
|
||||
|
||||
// Add User Calendar Events for user1 and user2.
|
||||
@@ -515,10 +493,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
|
||||
// Add a Course Calendar Events for Course 1.
|
||||
$this->setUser($user1);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('course', $user1->id, time(), '',
|
||||
0, $course1->id);
|
||||
$this->setUser($user2);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('course', $user2->id, time(), '',
|
||||
0, $course1->id);
|
||||
|
||||
// Add a Course Assignment Action Calendar Event for Course 2.
|
||||
@@ -536,7 +514,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
'assign', $user2->id, time());
|
||||
|
||||
// Add a Calendar Subscription and Group Calendar Event to Course 3.
|
||||
$this->create_test_standard_calendar_event('group', 0, time(), '', 0,
|
||||
$this->create_test_standard_calendar_event('group', $user2->id, time(), '', 0,
|
||||
$course3->id, $course3group->id);
|
||||
$this->setUser($user3);
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user3->id,
|
||||
@@ -552,22 +530,28 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
provider::get_users_in_context($userlist2);
|
||||
$this->assertCount(1, $userlist2);
|
||||
$this->assertTrue(in_array($user2->id, $userlist2->get_userids()));
|
||||
// The user list for course1context should not return any users.
|
||||
// The user list for course1context should return user1 and user2.
|
||||
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
|
||||
provider::get_users_in_context($userlist3);
|
||||
$this->assertCount(0, $userlist3);
|
||||
$this->assertCount(2, $userlist3);
|
||||
$this->assertTrue(in_array($user1->id, $userlist3->get_userids()));
|
||||
$this->assertTrue(in_array($user2->id, $userlist3->get_userids()));
|
||||
// The user list for course2context should not return any users.
|
||||
$userlist4 = new \core_privacy\local\request\userlist($course2context, $component);
|
||||
provider::get_users_in_context($userlist4);
|
||||
$this->assertCount(0, $userlist4);
|
||||
// The user list for course3context should not return any users.
|
||||
// The user list for course3context should return user2 and user3.
|
||||
$userlist5 = new \core_privacy\local\request\userlist($course3context, $component);
|
||||
provider::get_users_in_context($userlist5);
|
||||
$this->assertCount(0, $userlist5);
|
||||
// The user list for categorycontext should not return any users.
|
||||
$this->assertCount(2, $userlist5);
|
||||
$this->assertTrue(in_array($user2->id, $userlist5->get_userids()));
|
||||
$this->assertTrue(in_array($user3->id, $userlist5->get_userids()));
|
||||
// The user list for categorycontext should return user1 and user2.
|
||||
$userlist6 = new \core_privacy\local\request\userlist($categorycontext, $component);
|
||||
provider::get_users_in_context($userlist6);
|
||||
$this->assertCount(0, $userlist6);
|
||||
$this->assertCount(2, $userlist6);
|
||||
$this->assertTrue(in_array($user1->id, $userlist6->get_userids()));
|
||||
$this->assertTrue(in_array($user2->id, $userlist6->get_userids()));
|
||||
// The user list for modulecontext should return user2.
|
||||
$userlist7 = new \core_privacy\local\request\userlist($modulecontext, $component);
|
||||
provider::get_users_in_context($userlist7);
|
||||
@@ -612,10 +596,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
|
||||
// Add Category Calendar Events for Category.
|
||||
$this->setUser($user1);
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('category', $user1->id, time(), '',
|
||||
$category->id);
|
||||
$this->setUser($user2);
|
||||
$this->create_test_standard_calendar_event('category', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('category', $user2->id, time(), '',
|
||||
$category->id);
|
||||
|
||||
// Add User Calendar Events for user1 and user2.
|
||||
@@ -631,10 +615,10 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
|
||||
// Add a Course Calendar Events for Course 1.
|
||||
$this->setUser($user1);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('course', $user1->id, time(), '',
|
||||
0, $course1->id);
|
||||
$this->setUser($user2);
|
||||
$this->create_test_standard_calendar_event('course', 0, time(), '',
|
||||
$this->create_test_standard_calendar_event('course', $user2->id, time(), '',
|
||||
0, $course1->id);
|
||||
|
||||
// Add a Course Assignment Action Calendar Event for Course 2.
|
||||
@@ -652,7 +636,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
'assign', $user2->id, time());
|
||||
|
||||
// Add a Calendar Subscription and Group Calendar Event to Course 3.
|
||||
$this->create_test_standard_calendar_event('group', 0, time(), '', 0,
|
||||
$this->create_test_standard_calendar_event('group', $user2->id, time(), '', 0,
|
||||
$course3->id, $course3group->id);
|
||||
$this->setUser($user3);
|
||||
$this->create_test_calendar_subscription('course', 'https://calendar.google.com/', $user3->id,
|
||||
@@ -666,22 +650,22 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
$userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component);
|
||||
provider::get_users_in_context($userlist2);
|
||||
$this->assertCount(1, $userlist2);
|
||||
// The user list for course1context should not return any users.
|
||||
// The user list for course1context should return user1 and user2.
|
||||
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
|
||||
provider::get_users_in_context($userlist3);
|
||||
$this->assertCount(0, $userlist3);
|
||||
$this->assertCount(2, $userlist3);
|
||||
// The user list for course2context should not return any users.
|
||||
$userlist4 = new \core_privacy\local\request\userlist($course2context, $component);
|
||||
provider::get_users_in_context($userlist4);
|
||||
$this->assertCount(0, $userlist4);
|
||||
// The user list for course3context should not return any users.
|
||||
// The user list for course3context should return user2 and user3.
|
||||
$userlist5 = new \core_privacy\local\request\userlist($course3context, $component);
|
||||
provider::get_users_in_context($userlist5);
|
||||
$this->assertCount(0, $userlist5);
|
||||
// The user list for categorycontext should not return any users.
|
||||
$this->assertCount(2, $userlist5);
|
||||
// The user list for categorycontext should return user1 and user2.
|
||||
$userlist6 = new \core_privacy\local\request\userlist($categorycontext, $component);
|
||||
provider::get_users_in_context($userlist6);
|
||||
$this->assertCount(0, $userlist6);
|
||||
$this->assertCount(2, $userlist6);
|
||||
// The user list for modulecontext should return user2.
|
||||
$userlist7 = new \core_privacy\local\request\userlist($modulecontext, $component);
|
||||
provider::get_users_in_context($userlist7);
|
||||
@@ -709,25 +693,28 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
$approvedlist2 = new approved_userlist($course1context, $component, []);
|
||||
// Delete using delete_data_for_user.
|
||||
provider::delete_data_for_users($approvedlist2);
|
||||
// The user list for course1context should not return any users.
|
||||
// The user list for course1context should return user1 and user2.
|
||||
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
|
||||
provider::get_users_in_context($userlist3);
|
||||
$this->assertCount(0, $userlist3);
|
||||
$this->assertCount(2, $userlist3);
|
||||
$this->assertTrue(in_array($user1->id, $userlist3->get_userids()));
|
||||
$this->assertTrue(in_array($user2->id, $userlist3->get_userids()));
|
||||
|
||||
// Convert $userlist3 into an approved_contextlist.
|
||||
// Pass the ID of user1 as a value for the approved user list.
|
||||
$approvedlist2 = new approved_userlist($course1context, $component, [$user1->id]);
|
||||
// Delete using delete_data_for_user.
|
||||
provider::delete_data_for_users($approvedlist2);
|
||||
// The user list for course1context should not return any users.
|
||||
// The user list for course1context should return user2.
|
||||
$userlist3 = new \core_privacy\local\request\userlist($course1context, $component);
|
||||
provider::get_users_in_context($userlist3);
|
||||
$this->assertCount(0, $userlist3);
|
||||
$this->assertCount(1, $userlist3);
|
||||
$this->assertTrue(in_array($user2->id, $userlist3->get_userids()));
|
||||
|
||||
// The user list for course3context should not return any users.
|
||||
// The user list for course3context should still return user2 and user3.
|
||||
$userlist5 = new \core_privacy\local\request\userlist($course3context, $component);
|
||||
provider::get_users_in_context($userlist5);
|
||||
$this->assertCount(0, $userlist5);
|
||||
$this->assertCount(2, $userlist5);
|
||||
|
||||
// Convert $userlist6 into an approved_contextlist.
|
||||
$approvedlist3 = new approved_userlist($categorycontext, $component, $userlist6->get_userids());
|
||||
@@ -775,7 +762,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
'categoryid' => $categoryid,
|
||||
'courseid' => $courseid,
|
||||
'groupid' => $groupid,
|
||||
'userid' => ($eventtype == 'user') ? $userid : 0,
|
||||
'userid' => $userid,
|
||||
'modulename' => 0,
|
||||
'instance' => 0,
|
||||
'eventtype' => $eventtype,
|
||||
@@ -835,7 +822,7 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
'categoryid' => $categoryid,
|
||||
'courseid' => $courseid,
|
||||
'groupid' => $groupid,
|
||||
'userid' => ($eventtype == 'user') ? $userid : 0,
|
||||
'userid' => $userid,
|
||||
'eventtype' => $eventtype
|
||||
];
|
||||
|
||||
|
||||
@@ -44,4 +44,4 @@ Feature: Manage H5P content from the content bank
|
||||
And I expand "Site pages" node
|
||||
And I click on "Content bank" "link"
|
||||
When I follow "filltheblanks.h5p"
|
||||
Then "region-main-settings-menu" "region" should not exist
|
||||
Then "Rename" "link" should not exist in the "region-main-settings-menu" "region"
|
||||
|
||||
@@ -58,7 +58,7 @@ Feature: Delete H5P file from the content bank
|
||||
And I click on "Content bank" "link" in the "Navigation" "block"
|
||||
And I should see "filltheblanks.h5p"
|
||||
And I follow "filltheblanks.h5p"
|
||||
Then ".header-actions-container" "css_element" should not exist
|
||||
Then "Delete" "link" should not exist in the "region-main-settings-menu" "region"
|
||||
And I click on "Content bank" "link"
|
||||
And I click on "Upload" "link"
|
||||
And I click on "Choose a file..." "button"
|
||||
|
||||
@@ -159,3 +159,37 @@ Feature: Content bank use editor feature
|
||||
And I click on "Content bank" "link"
|
||||
And I follow "filltheblanks.h5p"
|
||||
Then "Edit" "link" should not exist in the "region-main" "region"
|
||||
|
||||
Scenario: Teachers keep their content authoring in copied courses
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "contentbank content" exist:
|
||||
| contextlevel | reference | contenttype | user | contentname | filepath |
|
||||
| Course | C1 | contenttype_h5p | admin | filltheblanks.h5p | /h5p/tests/fixtures/filltheblanks.h5p |
|
||||
| Course | C1 | contenttype_h5p | teacher1 | ipsums.h5p | /h5p/tests/fixtures/ipsums.h5p |
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Copy course" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Course full name | Copy |
|
||||
| Course short name | Copy |
|
||||
| Teacher | 1 |
|
||||
When I press "Copy and view"
|
||||
And I trigger cron
|
||||
And I am on homepage
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Copy" course homepage
|
||||
And I expand "Site pages" node
|
||||
And I click on "Content bank" "link"
|
||||
And I follow "ipsums.h5p"
|
||||
Then "Edit" "link" should exist in the "region-main" "region"
|
||||
And I click on "Content bank" "link"
|
||||
And I follow "filltheblanks.h5p"
|
||||
Then "Edit" "link" should not exist in the "region-main" "region"
|
||||
|
||||
@@ -24,16 +24,18 @@ Feature: Search content in the content bank
|
||||
And I expand "Site pages" node
|
||||
And I click on "Content bank" "link"
|
||||
And I should see "santjordi.h5p"
|
||||
And "Clear search input" "button" should not exist
|
||||
And "Clear search input" "button" should not be visible
|
||||
And I should not see "items found"
|
||||
When I set the field "Search" to "book"
|
||||
Then "Clear search input" "button" should exist
|
||||
# Waiting for the animation to show the button to finish.
|
||||
And I wait "1" seconds
|
||||
Then "Clear search input" "button" should be visible
|
||||
And I should see "3 items found"
|
||||
And I should see "SantJordi_book"
|
||||
And I should see "mathsbook.h5p"
|
||||
And I should see "historybook.h5p"
|
||||
And I set the field "Search" to "sant"
|
||||
And "Clear search input" "button" should exist
|
||||
And "Clear search input" "button" should be visible
|
||||
And I should see "5 items found"
|
||||
And I set the field "Search" to "santjordi"
|
||||
And I should see "4 items found"
|
||||
@@ -42,10 +44,14 @@ Feature: Search content in the content bank
|
||||
And I should see "SantJordi_book"
|
||||
And I should see "Dragon_santjordi.h5p"
|
||||
And I click on "Clear search input" "button"
|
||||
And "Clear search input" "button" should not exist
|
||||
# Waiting for the animation to hide the button to finish.
|
||||
And I wait "1" seconds
|
||||
And "Clear search input" "button" should not be visible
|
||||
And I should not see "items found"
|
||||
And I set the field "Search" to ".h5p"
|
||||
And "Clear search input" "button" should exist
|
||||
# Waiting for the animation to show the button to finish.
|
||||
And I wait "1" seconds
|
||||
And "Clear search input" "button" should be visible
|
||||
And I should see "7 items found"
|
||||
And I set the field "Search" to "friend"
|
||||
And I should see "0 items found"
|
||||
|
||||
@@ -103,6 +103,10 @@ class no_recent_accesses extends course_enrolments {
|
||||
return get_string('nocoursestudents', 'course');
|
||||
}
|
||||
|
||||
if (!$fortraining && !$course->get_course_data()->visible) {
|
||||
return get_string('hiddenfromstudents');
|
||||
}
|
||||
|
||||
if ($course->get_end() && $course->get_end() < $course->get_start()) {
|
||||
return get_string('errorendbeforestart', 'course');
|
||||
}
|
||||
|
||||
+24
-25
@@ -57,8 +57,6 @@ M.course_dndupload = {
|
||||
// The selector identifying the list of modules within a section (note changing this may require
|
||||
// changes to the get_mods_element function)
|
||||
modslistselector: 'ul.section',
|
||||
// Original onbeforeunload event.
|
||||
originalUnloadEvent: null,
|
||||
|
||||
/**
|
||||
* Initalise the drag and drop upload interface
|
||||
@@ -765,7 +763,10 @@ M.course_dndupload = {
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 1) {
|
||||
this.originalUnloadEvent = window.onbeforeunload;
|
||||
self.reportUploadDirtyState(true);
|
||||
// Trigger form upload start events.
|
||||
require(['core_form/events'], function(FormEvent) {
|
||||
FormEvent.triggerUploadStarted(section.get('id'));
|
||||
});
|
||||
}
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200) {
|
||||
@@ -793,7 +794,10 @@ M.course_dndupload = {
|
||||
} else {
|
||||
new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
|
||||
}
|
||||
self.reportUploadDirtyState(false);
|
||||
// Trigger form upload complete events.
|
||||
require(['core_form/events'], function(FormEvent) {
|
||||
FormEvent.triggerUploadCompleted(section.get('id'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1025,7 +1029,10 @@ M.course_dndupload = {
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 1) {
|
||||
this.originalUnloadEvent = window.onbeforeunload;
|
||||
self.reportUploadDirtyState(true);
|
||||
// Trigger form upload start events.
|
||||
require(['core_form/events'], function(FormEvent) {
|
||||
FormEvent.triggerUploadStarted(section.get('id'));
|
||||
});
|
||||
}
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200) {
|
||||
@@ -1043,13 +1050,24 @@ M.course_dndupload = {
|
||||
} else {
|
||||
// Error - remove the dummy element
|
||||
resel.parent.removeChild(resel.li);
|
||||
// Trigger form upload complete events.
|
||||
require(['core_form/events'], function(FormEvent) {
|
||||
FormEvent.triggerUploadCompleted(section.get('id'));
|
||||
});
|
||||
new M.core.alert({message: result.error});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Trigger form upload complete events.
|
||||
require(['core_form/events'], function(FormEvent) {
|
||||
FormEvent.triggerUploadCompleted(section.get('id'));
|
||||
});
|
||||
new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
|
||||
}
|
||||
self.reportUploadDirtyState(false);
|
||||
// Trigger form upload complete events.
|
||||
require(['core_form/events'], function(FormEvent) {
|
||||
FormEvent.triggerUploadCompleted(section.get('id'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1083,24 +1101,5 @@ M.course_dndupload = {
|
||||
if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
|
||||
M.core.actionmenu.newDOMNode(node);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the event to prevent user navigate away when upload progress still running.
|
||||
*
|
||||
* @param {bool} enable true if upload progress is running, false otherwise
|
||||
*/
|
||||
reportUploadDirtyState: function(enable) {
|
||||
if (!enable) {
|
||||
window.onbeforeunload = this.originalUnloadEvent;
|
||||
} else {
|
||||
window.onbeforeunload = function(e) {
|
||||
var warningMessage = M.util.get_string('changesmadereallygoaway', 'moodle');
|
||||
if (e) {
|
||||
e.returnValue = warningMessage;
|
||||
}
|
||||
return warningMessage;
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+24
-6
@@ -4720,21 +4720,39 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
|
||||
$basefields = array('id', 'idnumber', 'summary', 'summaryformat', 'startdate', 'enddate', 'category',
|
||||
'shortname', 'fullname', 'timeaccess', 'component', 'visible');
|
||||
|
||||
$sort = trim($sort);
|
||||
if (empty($sort)) {
|
||||
$sort = 'timeaccess DESC';
|
||||
} else {
|
||||
// The SQL string for sorting can define sorting by multiple columns.
|
||||
$rawsorts = explode(',', $sort);
|
||||
$sorts = array();
|
||||
// Validate and trim the sort parameters in the SQL string for sorting.
|
||||
foreach ($rawsorts as $rawsort) {
|
||||
$rawsort = trim($rawsort);
|
||||
$sorts[] = trim($rawsort);
|
||||
$sort = trim($rawsort);
|
||||
$sortparams = explode(' ', $sort);
|
||||
// A valid sort statement can not have more than 2 params (ex. 'summary desc' or 'timeaccess').
|
||||
if (count($sortparams) > 2) {
|
||||
throw new invalid_parameter_exception(
|
||||
'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].');
|
||||
}
|
||||
$sortfield = trim($sortparams[0]);
|
||||
// Validate the value which defines the field to sort by.
|
||||
if (!in_array($sortfield, $basefields)) {
|
||||
throw new invalid_parameter_exception('Invalid field in the sort parameter, allowed fields: ' .
|
||||
implode(', ', $basefields) . '.');
|
||||
}
|
||||
$sortdirection = isset($sortparams[1]) ? trim($sortparams[1]) : '';
|
||||
// Validate the value which defines the sort direction (if present).
|
||||
$allowedsortdirections = ['asc', 'desc'];
|
||||
if (!empty($sortdirection) && !in_array(strtolower($sortdirection), $allowedsortdirections)) {
|
||||
throw new invalid_parameter_exception('Invalid sort direction in the sort parameter, allowed values: ' .
|
||||
implode(', ', $allowedsortdirections) . '.');
|
||||
}
|
||||
$sorts[] = $sort;
|
||||
}
|
||||
$sort = implode(',', $sorts);
|
||||
}
|
||||
|
||||
$orderby = "ORDER BY $sort";
|
||||
|
||||
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
|
||||
|
||||
$coursefields = 'c.' .join(',', $basefields);
|
||||
@@ -4767,7 +4785,7 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
|
||||
AND ue.timestart < :now1
|
||||
AND (ue.timeend = 0 OR ue.timeend > :now2)
|
||||
))
|
||||
$orderby";
|
||||
ORDER BY $sort";
|
||||
|
||||
$now = round(time(), -2); // Improves db caching.
|
||||
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_COURSE, 'visible' => 1, 'status' => ENROL_USER_ACTIVE,
|
||||
|
||||
@@ -621,10 +621,23 @@ abstract class moodleform_mod extends moodleform {
|
||||
if (!empty($CFG->enableavailability)) {
|
||||
// Add special button to end of previous section if groups/groupings
|
||||
// are enabled.
|
||||
if ($this->_features->groups || $this->_features->groupings) {
|
||||
|
||||
$availabilityplugins = \core\plugininfo\availability::get_enabled_plugins();
|
||||
$groupavailability = $this->_features->groups && array_key_exists('group', $availabilityplugins);
|
||||
$groupingavailability = $this->_features->groupings && array_key_exists('grouping', $availabilityplugins);
|
||||
|
||||
if ($groupavailability || $groupingavailability) {
|
||||
// When creating the button, we need to set type=button to prevent it behaving as a submit.
|
||||
$mform->addElement('static', 'restrictgroupbutton', '',
|
||||
html_writer::tag('button', get_string('restrictbygroup', 'availability'),
|
||||
array('id' => 'restrictbygroup', 'disabled' => 'disabled', 'class' => 'btn btn-secondary')));
|
||||
html_writer::tag('button', get_string('restrictbygroup', 'availability'), [
|
||||
'id' => 'restrictbygroup',
|
||||
'type' => 'button',
|
||||
'disabled' => 'disabled',
|
||||
'class' => 'btn btn-secondary',
|
||||
'data-groupavailability' => $groupavailability,
|
||||
'data-groupingavailability' => $groupingavailability
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
// Availability field. This is just a textarea; the user interface
|
||||
|
||||
@@ -51,8 +51,8 @@ Feature: Display and choose from the available activities in course
|
||||
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "help" "core_course > Activity chooser screen"
|
||||
And I should see "Back" in the "help" "core_course > Activity chooser screen"
|
||||
When I click on "Back" "button" in the "help" "core_course > Activity chooser screen"
|
||||
Then "modules" "core_course > Activity chooser screen" should exist
|
||||
And "help" "core_course > Activity chooser screen" should not exist
|
||||
Then "modules" "core_course > Activity chooser screen" should be visible
|
||||
And "help" "core_course > Activity chooser screen" should not be visible
|
||||
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
|
||||
And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue"
|
||||
|
||||
@@ -123,24 +123,26 @@ Feature: Display and choose from the available activities in course
|
||||
And I should see "Lesson" in the "Add an activity or resource" "dialogue"
|
||||
When I set the field "search" to ""
|
||||
And I should not see "1 results found" in the "Add an activity or resource" "dialogue"
|
||||
Then ".searchresultscontainer" "css_element" should not exist
|
||||
Then ".searchresultscontainer" "css_element" should not be visible
|
||||
And ".optionscontainer" "css_element" should exist
|
||||
|
||||
Scenario: Teacher can not see a "clear" button if a search query is not entered in the activity chooser search bar
|
||||
When I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
Then "Clear search input" "button" should not exist
|
||||
Then "Clear search input" "button" should not be visible
|
||||
|
||||
Scenario: Teacher can see a "clear" button after entering a search query in the activity chooser search bar
|
||||
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
When I set the field "search" to "Search query"
|
||||
Then "Clear search input" "button" should not exist
|
||||
Then "Clear search input" "button" should not be visible
|
||||
|
||||
Scenario: Teacher can not see a "clear" button if the search query is removed in the activity chooser search bar
|
||||
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
And I set the field "search" to "Search query"
|
||||
And "Clear search input" "button" should exist
|
||||
When I set the field "search" to ""
|
||||
Then "Clear search input" "button" should not exist
|
||||
# Waiting for the animation to hide the button to finish.
|
||||
And I wait "1" seconds
|
||||
Then "Clear search input" "button" should not be visible
|
||||
|
||||
Scenario: Teacher can instantly remove the search query from the activity search bar by clicking on the "clear" button
|
||||
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
@@ -148,7 +150,7 @@ Feature: Display and choose from the available activities in course
|
||||
And I should see "results found" in the "Add an activity or resource" "dialogue"
|
||||
When I click on "Clear search input" "button"
|
||||
Then I should not see "Search query"
|
||||
And ".searchresultscontainer" "css_element" should not exist
|
||||
And ".searchresultscontainer" "css_element" should not be visible
|
||||
And ".optionscontainer" "css_element" should exist
|
||||
|
||||
Scenario: Teacher gets the base case for the Activity Chooser tab mode
|
||||
|
||||
@@ -4550,7 +4550,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 0,
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'expecteddbqueries' => 1,
|
||||
'expecteddbqueries' => 4,
|
||||
'expectedresult' => $buildexpectedresult(0, 0)
|
||||
],
|
||||
'less than query limit' => [
|
||||
@@ -4558,7 +4558,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 2,
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'expecteddbqueries' => 1,
|
||||
'expecteddbqueries' => 2,
|
||||
'expectedresult' => $buildexpectedresult(2, 0)
|
||||
],
|
||||
'more than query limit' => [
|
||||
@@ -4566,7 +4566,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 7,
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'expecteddbqueries' => 3,
|
||||
'expecteddbqueries' => 4,
|
||||
'expectedresult' => $buildexpectedresult(7, 0)
|
||||
],
|
||||
'limit less than query limit' => [
|
||||
@@ -4574,7 +4574,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 7,
|
||||
'limit' => 2,
|
||||
'offset' => 0,
|
||||
'expecteddbqueries' => 1,
|
||||
'expecteddbqueries' => 2,
|
||||
'expectedresult' => $buildexpectedresult(2, 0)
|
||||
],
|
||||
'limit less than query limit with offset' => [
|
||||
@@ -4582,7 +4582,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 7,
|
||||
'limit' => 2,
|
||||
'offset' => 2,
|
||||
'expecteddbqueries' => 1,
|
||||
'expecteddbqueries' => 2,
|
||||
'expectedresult' => $buildexpectedresult(2, 2)
|
||||
],
|
||||
'limit less than total' => [
|
||||
@@ -4590,7 +4590,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 9,
|
||||
'limit' => 6,
|
||||
'offset' => 0,
|
||||
'expecteddbqueries' => 2,
|
||||
'expecteddbqueries' => 3,
|
||||
'expectedresult' => $buildexpectedresult(6, 0)
|
||||
],
|
||||
'less results than limit' => [
|
||||
@@ -4598,7 +4598,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 9,
|
||||
'limit' => 20,
|
||||
'offset' => 0,
|
||||
'expecteddbqueries' => 3,
|
||||
'expecteddbqueries' => 4,
|
||||
'expectedresult' => $buildexpectedresult(9, 0)
|
||||
],
|
||||
'less results than limit exact divisible' => [
|
||||
@@ -4606,7 +4606,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 9,
|
||||
'limit' => 20,
|
||||
'offset' => 0,
|
||||
'expecteddbqueries' => 4,
|
||||
'expecteddbqueries' => 5,
|
||||
'expectedresult' => $buildexpectedresult(9, 0)
|
||||
],
|
||||
'less results than limit with offset' => [
|
||||
@@ -4614,7 +4614,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
'totalcourses' => 9,
|
||||
'limit' => 10,
|
||||
'offset' => 5,
|
||||
'expecteddbqueries' => 2,
|
||||
'expecteddbqueries' => 3,
|
||||
'expectedresult' => $buildexpectedresult(4, 5)
|
||||
],
|
||||
];
|
||||
@@ -5442,7 +5442,16 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
// Every course accessed, order by shortname DESC. The last create course ($course[2]) should have the greater shortname.
|
||||
$result = course_get_recent_courses($student->id, 0, 0, 'shortname DESC');
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertEquals($courses[2]->id, array_shift($result)->id);
|
||||
$this->assertEquals($courses[2]->id, array_values($result)[0]->id);
|
||||
$this->assertEquals($courses[1]->id, array_values($result)[1]->id);
|
||||
$this->assertEquals($courses[0]->id, array_values($result)[2]->id);
|
||||
|
||||
// Every course accessed, order by shortname ASC.
|
||||
$result = course_get_recent_courses($student->id, 0, 0, 'shortname ASC');
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertEquals($courses[0]->id, array_values($result)[0]->id);
|
||||
$this->assertEquals($courses[1]->id, array_values($result)[1]->id);
|
||||
$this->assertEquals($courses[2]->id, array_values($result)[2]->id);
|
||||
|
||||
$guestcourse = $generator->create_course(
|
||||
(object)array('shortname' => 'guestcourse',
|
||||
@@ -5464,6 +5473,56 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$this->assertArrayNotHasKey($courses[0]->id, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the validation of the sort value in course_get_recent_courses().
|
||||
*
|
||||
* @dataProvider course_get_recent_courses_sort_validation_provider
|
||||
* @param string $sort The sort value
|
||||
* @param string $expectedexceptionmsg The expected exception message
|
||||
*/
|
||||
public function test_course_get_recent_courses_sort_validation(string $sort, string $expectedexceptionmsg) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
if (!empty($expectedexceptionmsg)) {
|
||||
$this->expectException('invalid_parameter_exception');
|
||||
$this->expectExceptionMessage($expectedexceptionmsg);
|
||||
}
|
||||
course_get_recent_courses($user->id, 0, 0, $sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_course_get_recent_courses_sort_validation().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function course_get_recent_courses_sort_validation_provider() {
|
||||
return [
|
||||
'Invalid sort format (SQL injection attempt)' =>
|
||||
[
|
||||
'shortname DESC LIMIT 1--',
|
||||
'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].',
|
||||
],
|
||||
'Sort uses \'sort by\' field that does not exist' =>
|
||||
[
|
||||
'shortname DESC, xyz ASC',
|
||||
'Invalid field in the sort parameter, allowed fields: id, idnumber, summary, summaryformat, ' .
|
||||
'startdate, enddate, category, shortname, fullname, timeaccess, component, visible.',
|
||||
],
|
||||
'Sort uses invalid value for the sorting direction' =>
|
||||
[
|
||||
'shortname xyz, lastaccess',
|
||||
'Invalid sort direction in the sort parameter, allowed values: asc, desc.',
|
||||
],
|
||||
'Valid sort format' =>
|
||||
[
|
||||
'shortname asc, timeaccess',
|
||||
''
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the course_get_recent_courses function.
|
||||
*/
|
||||
|
||||
@@ -2765,7 +2765,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
/**
|
||||
* Test cases for the get_enrolled_courses_by_timeline_classification test.
|
||||
*/
|
||||
public function get_get_enrolled_courses_by_timeline_classification_test_cases() {
|
||||
public function get_get_enrolled_courses_by_timeline_classification_test_cases():array {
|
||||
$now = time();
|
||||
$day = 86400;
|
||||
|
||||
@@ -2864,6 +2864,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'future',
|
||||
'limit' => 2,
|
||||
'offset' => 0,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0
|
||||
],
|
||||
@@ -2873,6 +2874,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'future',
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
|
||||
'expectednextoffset' => 15
|
||||
],
|
||||
@@ -2881,6 +2883,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'future',
|
||||
'limit' => 2,
|
||||
'offset' => 0,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => ['afuture', 'bfuture'],
|
||||
'expectednextoffset' => 4
|
||||
],
|
||||
@@ -2889,6 +2892,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'future',
|
||||
'limit' => 2,
|
||||
'offset' => 2,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => ['bfuture', 'cfuture'],
|
||||
'expectednextoffset' => 7
|
||||
],
|
||||
@@ -2897,6 +2901,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'future',
|
||||
'limit' => 5,
|
||||
'offset' => 0,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
|
||||
'expectednextoffset' => 13
|
||||
],
|
||||
@@ -2905,6 +2910,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'future',
|
||||
'limit' => 10,
|
||||
'offset' => 0,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
|
||||
'expectednextoffset' => 15
|
||||
],
|
||||
@@ -2913,6 +2919,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'future',
|
||||
'limit' => 10,
|
||||
'offset' => 5,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => ['cfuture', 'dfuture', 'efuture'],
|
||||
'expectednextoffset' => 15
|
||||
],
|
||||
@@ -2921,6 +2928,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'all',
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => [
|
||||
'afuture',
|
||||
'ainprogress',
|
||||
@@ -2945,6 +2953,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 0,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => [
|
||||
'afuture',
|
||||
'ainprogress',
|
||||
@@ -2959,6 +2968,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => [
|
||||
'bpast',
|
||||
'cfuture',
|
||||
@@ -2973,9 +2983,161 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 50,
|
||||
'sort' => 'shortname ASC',
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 50
|
||||
],
|
||||
'all limit and offset with sort ul.timeaccess desc' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'inprogress',
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'sort' => 'ul.timeaccess desc',
|
||||
'expectedcourses' => [
|
||||
'ainprogress',
|
||||
'binprogress',
|
||||
'cinprogress',
|
||||
'dinprogress',
|
||||
'einprogress'
|
||||
],
|
||||
'expectednextoffset' => 15
|
||||
],
|
||||
'all limit and offset with sort sql injection for sort or 1==1' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => 'ul.timeaccess desc or 1==1',
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with sql injection of sort a custom one' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "ul.timeaccess LIMIT 1--",
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong sort direction' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "ul.timeaccess abcdasc",
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong sort direction' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "ul.timeaccess.foo ascd",
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid sort direction in $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong sort param' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "foobar",
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong field name' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "ul.foobar",
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong field separator' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "ul.timeaccess.foo",
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong field separator #' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "ul#timeaccess",
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong field separator $' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => 'ul$timeaccess',
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with wrong field name' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => 'timeaccess123',
|
||||
'expectedcourses' => [],
|
||||
'expectednextoffset' => 0,
|
||||
'expectedexception' => 'Invalid $sort parameter in enrol_get_my_courses()'
|
||||
],
|
||||
'all limit and offset with no sort direction for ul' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'inprogress',
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'sort' => "ul.timeaccess",
|
||||
'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'],
|
||||
'expectednextoffset' => 15,
|
||||
],
|
||||
'all limit and offset with valid field name and no prefix, test for ul' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'inprogress',
|
||||
'limit' => 0,
|
||||
'offset' => 0,
|
||||
'sort' => "timeaccess",
|
||||
'expectedcourses' => ['ainprogress', 'binprogress', 'cinprogress', 'dinprogress', 'einprogress'],
|
||||
'expectednextoffset' => 15,
|
||||
],
|
||||
'all limit and offset with valid field name and no prefix' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "fullname",
|
||||
'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'],
|
||||
'expectednextoffset' => 10,
|
||||
],
|
||||
'all limit and offset with valid field name and no prefix and with sort direction' => [
|
||||
'coursedata' => $coursedata,
|
||||
'classification' => 'all',
|
||||
'limit' => 5,
|
||||
'offset' => 5,
|
||||
'sort' => "fullname desc",
|
||||
'expectedcourses' => ['bpast', 'cpast', 'dfuture', 'dpast', 'efuture'],
|
||||
'expectednextoffset' => 10,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2987,16 +3149,20 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
* @param string $classification Timeline classification
|
||||
* @param int $limit Maximum number of results
|
||||
* @param int $offset Offset the unfiltered courses result set by this amount
|
||||
* @param string $sort sort the courses
|
||||
* @param array $expectedcourses Expected courses in result
|
||||
* @param int $expectednextoffset Expected next offset value in result
|
||||
* @param string|null $expectedexception Expected exception string
|
||||
*/
|
||||
public function test_get_enrolled_courses_by_timeline_classification(
|
||||
$coursedata,
|
||||
$classification,
|
||||
$limit,
|
||||
$offset,
|
||||
$sort,
|
||||
$expectedcourses,
|
||||
$expectednextoffset
|
||||
$expectednextoffset,
|
||||
$expectedexception = null
|
||||
) {
|
||||
$this->resetAfterTest();
|
||||
$generator = $this->getDataGenerator();
|
||||
@@ -3013,6 +3179,11 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
|
||||
$this->setUser($student);
|
||||
|
||||
if (isset($expectedexception)) {
|
||||
$this->expectException('coding_exception');
|
||||
$this->expectExceptionMessage($expectedexception);
|
||||
}
|
||||
|
||||
// NOTE: The offset applies to the unfiltered full set of courses before the classification
|
||||
// filtering is done.
|
||||
// E.g. In our example if an offset of 2 is given then it would mean the first
|
||||
@@ -3021,7 +3192,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
$classification,
|
||||
$limit,
|
||||
$offset,
|
||||
'shortname ASC'
|
||||
$sort
|
||||
);
|
||||
$result = external_api::clean_returnvalue(
|
||||
core_course_external::get_enrolled_courses_by_timeline_classification_returns(),
|
||||
@@ -3032,7 +3203,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
return $course['shortname'];
|
||||
}, $result['courses']);
|
||||
|
||||
$this->assertEquals($expectedcourses, $actual);
|
||||
$this->assertEqualsCanonicalizing($expectedcourses, $actual);
|
||||
$this->assertEquals($expectednextoffset, $result['nextoffset']);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_customfield/form",["jquery","core/str","core/notification","core/ajax","core/templates","core/sortable_list","core/inplace_editable"],function(a,b,c,d,e,f){var g=function(f,g,h,i,j){b.get_strings([{key:"confirm"},{key:"confirmdelete"+g,component:"core_customfield"},{key:"yes"},{key:"no"}]).done(function(b){c.confirm(b[0],b[1],b[2],b[3],function(){var b="field"===g?"core_customfield_delete_field":"core_customfield_delete_category";d.call([{methodname:b,args:{id:f}},{methodname:"core_customfield_reload_template",args:{component:h,area:i,itemid:j}}])[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);return null}).fail(c.exception)})}).fail(c.exception)},h=function(b,f,g){var h=d.call([{methodname:"core_customfield_create_category",args:{component:b,area:f,itemid:g}},{methodname:"core_customfield_reload_template",args:{component:b,area:f,itemid:g}}]),i;h[0].then(function(a){i=a;return null}).fail(c.exception);h[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);window.location.href="#category-"+i;return null}).fail(c.exception)};return{init:function init(){var e=a("#customfield_catlist"),i=e.attr("data-component"),j=e.attr("data-area"),k=e.attr("data-itemid");a("[data-role=deletefield]").on("click",function(b){g(a(this).attr("data-id"),"field",i,j,k);b.preventDefault()});a("[data-role=deletecategory]").on("click",function(b){g(a(this).attr("data-id"),"category",i,j,k);b.preventDefault()});a("[data-role=addnewcategory]").on("click",function(){h(i,j,k)});var l=function(a){return a.closest("[data-category-id]").find("[data-inplaceeditable][data-itemtype=category][data-component=core_customfield]").attr("data-value")},m=new f(a("#customfield_catlist .categorieslist"),{moveHandlerSelector:".movecategory [data-drag-type=move]"});m.getElementName=function(b){return a.Deferred().resolve(l(b))};a("[data-category-id]").on("sortablelist-drop",function(a,b){if(b.positionChanged){var e=d.call([{methodname:"core_customfield_move_category",args:{id:b.element.data("category-id"),beforeid:b.targetNextElement.data("category-id")}}]);e[0].fail(c.exception)}a.stopPropagation()});var n=new f(a("#customfield_catlist .fieldslist tbody"),{moveHandlerSelector:".movefield [data-drag-type=move]"});n.getDestinationName=function(c,d){if(!d.length){return b.get_string("totopofcategory","customfield",l(c))}else if(d.attr("data-field-name")){return b.get_string("afterfield","customfield",d.attr("data-field-name"))}else{return a.Deferred().resolve("")}};a("[data-field-name]").on("sortablelist-drop",function(a,b){a.stopPropagation();if(b.positionChanged){var e=d.call([{methodname:"core_customfield_move_field",args:{id:b.element.data("field-id"),beforeid:b.targetNextElement.data("field-id"),categoryid:+b.targetList.closest("[data-category-id]").attr("data-category-id")}}]);e[0].fail(c.exception)}}).on("sortablelist-drag",function(d){d.stopPropagation();b.get_string("therearenofields","core_customfield").then(function(b){a("#customfield_catlist .categorieslist").children().each(function(){var c=a(this).find(a(".field")),d=a(this).find(a(".nofields"));if(!c.length&&!d.length){a(this).find("tbody").append("<tr class=\"nofields\"><td colspan=\"5\">"+b+"</td></tr>")}if(c.length&&d.length){d.remove()}});return null}).fail(c.exception)});a("[data-category-id], [data-field-name]").on("sortablelist-dragstart",function(b,c){setTimeout(function(){a(".sortable-list-is-dragged").width(c.element.width())},501)})}}});
|
||||
define ("core_customfield/form",["jquery","core/str","core/notification","core/ajax","core/templates","core/sortable_list","core/pending","core/inplace_editable"],function(a,b,c,d,e,f,g){var h=function(f,h,i,j,k){var l=new g("core_customfield/form:confirmDelete");b.get_strings([{key:"confirm"},{key:"confirmdelete"+h,component:"core_customfield"},{key:"yes"},{key:"no"}]).then(function(b){c.confirm(b[0],b[1],b[2],b[3],function(){var b=new g("core_customfield/form:confirmDelete"),l="field"===h?"core_customfield_delete_field":"core_customfield_delete_category";d.call([{methodname:l,args:{id:f}},{methodname:"core_customfield_reload_template",args:{component:i,area:j,itemid:k}}])[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);return null}).then(b.resolve).catch(c.exception)})}).then(l.resolve).catch(c.exception)},i=function(b,f,h){var i=new g("core_customfield/form:confirmDelete"),j=d.call([{methodname:"core_customfield_create_category",args:{component:b,area:f,itemid:h}},{methodname:"core_customfield_reload_template",args:{component:b,area:f,itemid:h}}]),k;j[0].then(function(a){k=a;return null}).catch(c.exception);j[1].then(function(a){return e.render("core_customfield/list",a)}).then(function(b,c){e.replaceNode(a("[data-region=\"list-page\"]"),b,c);window.location.href="#category-"+k;return null}).catch(c.exception);Promise.all(j).then(i.resolve).catch()};return{init:function init(){var e=a("#customfield_catlist"),j=e.attr("data-component"),k=e.attr("data-area"),l=e.attr("data-itemid");a("[data-role=deletefield]").on("click",function(b){h(a(this).attr("data-id"),"field",j,k,l);b.preventDefault()});a("[data-role=deletecategory]").on("click",function(b){h(a(this).attr("data-id"),"category",j,k,l);b.preventDefault()});a("[data-role=addnewcategory]").on("click",function(){i(j,k,l)});var m=function(a){return a.closest("[data-category-id]").find("[data-inplaceeditable][data-itemtype=category][data-component=core_customfield]").attr("data-value")},n=new f(a("#customfield_catlist .categorieslist"),{moveHandlerSelector:".movecategory [data-drag-type=move]"});n.getElementName=function(b){return a.Deferred().resolve(m(b))};a("[data-category-id]").on("sortablelist-drop",function(a,b){if(b.positionChanged){var e=new g("core_customfield/form:categoryid:on:sortablelist-drop");d.call([{methodname:"core_customfield_move_category",args:{id:b.element.data("category-id"),beforeid:b.targetNextElement.data("category-id")}}])[0].then(e.resolve).catch(c.exception)}a.stopPropagation()});var o=new f(a("#customfield_catlist .fieldslist tbody"),{moveHandlerSelector:".movefield [data-drag-type=move]"});o.getDestinationName=function(c,d){if(!d.length){return b.get_string("totopofcategory","customfield",m(c))}else if(d.attr("data-field-name")){return b.get_string("afterfield","customfield",d.attr("data-field-name"))}else{return a.Deferred().resolve("")}};a("[data-field-name]").on("sortablelist-drop",function(a,b){a.stopPropagation();if(b.positionChanged){var e=new g("core_customfield/form:fieldname:on:sortablelist-drop");d.call([{methodname:"core_customfield_move_field",args:{id:b.element.data("field-id"),beforeid:b.targetNextElement.data("field-id"),categoryid:+b.targetList.closest("[data-category-id]").attr("data-category-id")}}])[0].then(e.resolve).catch(c.exception)}});a("[data-field-name]").on("sortablelist-drag",function(d){var e=new g("core_customfield/form:fieldname:on:sortablelist-drag");d.stopPropagation();b.get_string("therearenofields","core_customfield").then(function(b){a("#customfield_catlist .categorieslist").children().each(function(){var c=a(this).find(a(".field")),d=a(this).find(a(".nofields"));if(!c.length&&!d.length){a(this).find("tbody").append("<tr class=\"nofields\"><td colspan=\"5\">"+b+"</td></tr>")}if(c.length&&d.length){d.remove()}});return null}).then(e.resolve).catch(c.exception)});a("[data-category-id], [data-field-name]").on("sortablelist-dragstart",function(b,c){setTimeout(function(){a(".sortable-list-is-dragged").width(c.element.width())},501)})}}});
|
||||
//# sourceMappingURL=form.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+66
-27
@@ -21,9 +21,16 @@
|
||||
* @copyright 2018 Toni Barbera
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates', 'core/sortable_list', 'core/inplace_editable'],
|
||||
function(
|
||||
$, Str, Notification, Ajax, Templates, SortableList) {
|
||||
define([
|
||||
'jquery',
|
||||
'core/str',
|
||||
'core/notification',
|
||||
'core/ajax',
|
||||
'core/templates',
|
||||
'core/sortable_list',
|
||||
'core/pending',
|
||||
'core/inplace_editable',
|
||||
], function($, Str, Notification, Ajax, Templates, SortableList, Pending) {
|
||||
|
||||
/**
|
||||
* Display confirmation dialogue
|
||||
@@ -35,25 +42,36 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
* @param {Number} itemid
|
||||
*/
|
||||
var confirmDelete = function(id, type, component, area, itemid) {
|
||||
var pendingPromise = new Pending('core_customfield/form:confirmDelete');
|
||||
Str.get_strings([
|
||||
{'key': 'confirm'},
|
||||
{'key': 'confirmdelete' + type, component: 'core_customfield'},
|
||||
{'key': 'yes'},
|
||||
{'key': 'no'},
|
||||
]).done(function(s) {
|
||||
])
|
||||
.then(function(s) {
|
||||
Notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
var pendingDeletePromise = new Pending('core_customfield/form:confirmDelete');
|
||||
var func = (type === 'field') ? 'core_customfield_delete_field' : 'core_customfield_delete_category';
|
||||
Ajax.call([
|
||||
{methodname: func, args: {id: id}},
|
||||
{methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
|
||||
])[1].then(function(response) {
|
||||
])[1]
|
||||
.then(function(response) {
|
||||
return Templates.render('core_customfield/list', response);
|
||||
}).then(function(html, js) {
|
||||
})
|
||||
.then(function(html, js) {
|
||||
Templates.replaceNode($('[data-region="list-page"]'), html, js);
|
||||
return null;
|
||||
}).fail(Notification.exception);
|
||||
})
|
||||
.then(pendingDeletePromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
});
|
||||
}).fail(Notification.exception);
|
||||
|
||||
return;
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,24 +82,31 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
* @param {Number} itemid
|
||||
*/
|
||||
var createNewCategory = function(component, area, itemid) {
|
||||
var pendingPromise = new Pending('core_customfield/form:confirmDelete');
|
||||
var promises = Ajax.call([
|
||||
{methodname: 'core_customfield_create_category', args: {component: component, area: area, itemid: itemid}},
|
||||
{methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
|
||||
]),
|
||||
categoryid;
|
||||
{methodname: 'core_customfield_create_category', args: {component: component, area: area, itemid: itemid}},
|
||||
{methodname: 'core_customfield_reload_template', args: {component: component, area: area, itemid: itemid}}
|
||||
]);
|
||||
var categoryid;
|
||||
|
||||
promises[0].then(function(response) {
|
||||
categoryid = response;
|
||||
return null;
|
||||
}).fail(Notification.exception);
|
||||
}).catch(Notification.exception);
|
||||
|
||||
promises[1].then(function(response) {
|
||||
return Templates.render('core_customfield/list', response);
|
||||
}).then(function(html, js) {
|
||||
})
|
||||
.then(function(html, js) {
|
||||
Templates.replaceNode($('[data-region="list-page"]'), html, js);
|
||||
window.location.href = '#category-' + categoryid;
|
||||
return null;
|
||||
}).fail(Notification.exception);
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
|
||||
Promise.all(promises)
|
||||
.then(pendingPromise.resolve)
|
||||
.catch();
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -89,18 +114,21 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
* Initialise the custom fields manager
|
||||
*/
|
||||
init: function() {
|
||||
var mainlist = $('#customfield_catlist'),
|
||||
component = mainlist.attr('data-component'),
|
||||
area = mainlist.attr('data-area'),
|
||||
itemid = mainlist.attr('data-itemid');
|
||||
var mainlist = $('#customfield_catlist');
|
||||
var component = mainlist.attr('data-component');
|
||||
var area = mainlist.attr('data-area');
|
||||
var itemid = mainlist.attr('data-itemid');
|
||||
|
||||
$("[data-role=deletefield]").on('click', function(e) {
|
||||
confirmDelete($(this).attr('data-id'), 'field', component, area, itemid);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$("[data-role=deletecategory]").on('click', function(e) {
|
||||
confirmDelete($(this).attr('data-id'), 'category', component, area, itemid);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$('[data-role=addnewcategory]').on('click', function() {
|
||||
createNewCategory(component, area, itemid);
|
||||
});
|
||||
@@ -124,7 +152,8 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
|
||||
$('[data-category-id]').on('sortablelist-drop', function(evt, info) {
|
||||
if (info.positionChanged) {
|
||||
var promises = Ajax.call([
|
||||
var pendingPromise = new Pending('core_customfield/form:categoryid:on:sortablelist-drop');
|
||||
Ajax.call([
|
||||
{
|
||||
methodname: 'core_customfield_move_category',
|
||||
args: {
|
||||
@@ -133,8 +162,9 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
}
|
||||
|
||||
},
|
||||
]);
|
||||
promises[0].fail(Notification.exception);
|
||||
])[0]
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
}
|
||||
evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
|
||||
});
|
||||
@@ -158,7 +188,8 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
$('[data-field-name]').on('sortablelist-drop', function(evt, info) {
|
||||
evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
|
||||
if (info.positionChanged) {
|
||||
var promises = Ajax.call([
|
||||
var pendingPromise = new Pending('core_customfield/form:fieldname:on:sortablelist-drop');
|
||||
Ajax.call([
|
||||
{
|
||||
methodname: 'core_customfield_move_field',
|
||||
args: {
|
||||
@@ -167,11 +198,17 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
categoryid: Number(info.targetList.closest('[data-category-id]').attr('data-category-id'))
|
||||
},
|
||||
},
|
||||
]);
|
||||
promises[0].fail(Notification.exception);
|
||||
])[0]
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
}
|
||||
}).on('sortablelist-drag', function(evt) {
|
||||
});
|
||||
|
||||
$('[data-field-name]').on('sortablelist-drag', function(evt) {
|
||||
var pendingPromise = new Pending('core_customfield/form:fieldname:on:sortablelist-drag');
|
||||
|
||||
evt.stopPropagation(); // Important for nested lists to prevent multiple targets.
|
||||
|
||||
// Refreshing fields tables.
|
||||
Str.get_string('therearenofields', 'core_customfield').then(function(s) {
|
||||
$('#customfield_catlist .categorieslist').children().each(function() {
|
||||
@@ -187,7 +224,9 @@ define(['jquery', 'core/str', 'core/notification', 'core/ajax', 'core/templates'
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}).fail(Notification.exception);
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
});
|
||||
|
||||
$('[data-category-id], [data-field-name]').on('sortablelist-dragstart',
|
||||
|
||||
@@ -55,12 +55,16 @@ class field_controller extends \core_customfield\field_controller {
|
||||
|
||||
$mform->addElement('text', 'configdata[displaysize]', get_string('displaysize', 'customfield_text'), ['size' => 6]);
|
||||
$mform->setType('configdata[displaysize]', PARAM_INT);
|
||||
$mform->setDefault('configdata[displaysize]', 50);
|
||||
if (!$this->get_configdata_property('displaysize')) {
|
||||
$mform->setDefault('configdata[displaysize]', 50);
|
||||
}
|
||||
$mform->addRule('configdata[displaysize]', null, 'numeric', null, 'client');
|
||||
|
||||
$mform->addElement('text', 'configdata[maxlength]', get_string('maxlength', 'customfield_text'), ['size' => 6]);
|
||||
$mform->setType('configdata[maxlength]', PARAM_INT);
|
||||
$mform->setDefault('configdata[maxlength]', 1333);
|
||||
if (!$this->get_configdata_property('maxlength')) {
|
||||
$mform->setDefault('configdata[maxlength]', 1333);
|
||||
}
|
||||
$mform->addRule('configdata[maxlength]', null, 'numeric', null, 'client');
|
||||
|
||||
$mform->addElement('selectyesno', 'configdata[ispassword]', get_string('ispassword', 'customfield_text'));
|
||||
@@ -113,7 +117,7 @@ class field_controller extends \core_customfield\field_controller {
|
||||
$errors['configdata[link]'] = get_string('errorconfiglinkplaceholder', 'customfield_text');
|
||||
} else if (!validateUrlSyntax(str_replace('$$', 'XYZ', $link), 's+H?S?F-E-u-P-a?I?p?f?q?r?')) {
|
||||
// This validation is more strict than PARAM_URL - it requires the protocol and it must be either http or https.
|
||||
$errors['configdata[link]'] = get_string('errorconfigdisplaysize', 'customfield_text');
|
||||
$errors['configdata[link]'] = get_string('errorconfiglinksyntax', 'customfield_text');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
This files describes API changes in /dataformat/ download system,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.9.8 ===
|
||||
* Optional $callback for the following \core\dataformat methods now receive a second argument to define
|
||||
whether the current format writer supports HTML:
|
||||
- download_data()
|
||||
- write_data()
|
||||
- write_data_to_filearea()
|
||||
|
||||
=== 3.9 ===
|
||||
* The following methods have been added to the base dataformat class to allow instances to export to a local
|
||||
file. They can be overridden in extending classes to define how files should be created:
|
||||
|
||||
@@ -188,7 +188,9 @@ $removeenabled = $canunenrol ? '' : 'disabled="disabled"';
|
||||
</td>
|
||||
<td id="buttonscell">
|
||||
<div id="addcontrols">
|
||||
<input name="add" <?php echo $addenabled; ?> id="add" type="submit" value="<?php echo $OUTPUT->larrow().' '.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
|
||||
<input class="btn btn-secondary" name="add" <?php echo $addenabled; ?> id="add" type="submit"
|
||||
value="<?php echo $OUTPUT->larrow() . ' ' . get_string('add'); ?>"
|
||||
title="<?php print_string('add'); ?>" /><br />
|
||||
|
||||
<div class="enroloptions">
|
||||
|
||||
@@ -205,7 +207,9 @@ $removeenabled = $canunenrol ? '' : 'disabled="disabled"';
|
||||
</div>
|
||||
|
||||
<div id="removecontrols">
|
||||
<input name="remove" id="remove" <?php echo $removeenabled; ?> type="submit" value="<?php echo get_string('remove').' '.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
|
||||
<input class="btn btn-secondary" name="remove" id="remove" <?php echo $removeenabled; ?> type="submit"
|
||||
value="<?php echo get_string('remove') . ' ' . $OUTPUT->rarrow(); ?>"
|
||||
title="<?php print_string('remove'); ?>" />
|
||||
</div>
|
||||
</td>
|
||||
<td id="potentialcell">
|
||||
|
||||
@@ -1055,7 +1055,8 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
|
||||
$this->assertArrayHasKey('customint2', $errors);
|
||||
|
||||
// Test with valid data.
|
||||
$validgroup = reset($groups);
|
||||
reset($groups);
|
||||
$validgroup = key($groups);
|
||||
$data = [
|
||||
'customint1' => [$metacourse2->id],
|
||||
'customint2' => $validgroup
|
||||
|
||||
@@ -98,6 +98,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I press "Enrol me"
|
||||
And I should see "You are enrolled in the course"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
@@ -118,6 +119,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I press "Enrol me"
|
||||
And I should see "You are enrolled in the course"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
@@ -1036,6 +1036,59 @@ class core_enrollib_testcase extends advanced_testcase {
|
||||
$this->assertEquals([$course1->id, $course2->id, $course3->id, $course4->id], array_keys($courses));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_enrol_get_my_courses_by_time}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function enrol_get_my_courses_by_time_provider(): array {
|
||||
return [
|
||||
'No start or end time' =>
|
||||
[null, null, true],
|
||||
'Start time now, no end time' =>
|
||||
[0, null, true],
|
||||
'Start time now, end time in the future' =>
|
||||
[0, MINSECS, true],
|
||||
'Start time in the past, no end time' =>
|
||||
[-MINSECS, null, true],
|
||||
'Start time in the past, end time in the future' =>
|
||||
[-MINSECS, MINSECS, true],
|
||||
'Start time in the past, end time in the past' =>
|
||||
[-DAYSECS, -HOURSECS, false],
|
||||
'Start time in the future' =>
|
||||
[MINSECS, null, false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that expected course enrolments are returned when they have timestart / timeend specified
|
||||
*
|
||||
* @param int|null $timestartoffset Null for 0, otherwise offset from current time
|
||||
* @param int|null $timeendoffset Null for 0, otherwise offset from current time
|
||||
* @param bool $expectreturn
|
||||
*
|
||||
* @dataProvider enrol_get_my_courses_by_time_provider
|
||||
*/
|
||||
public function test_enrol_get_my_courses_by_time(?int $timestartoffset, ?int $timeendoffset, bool $expectreturn): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$time = time();
|
||||
$timestart = $timestartoffset === null ? 0 : $time + $timestartoffset;
|
||||
$timeend = $timeendoffset === null ? 0 : $time + $timeendoffset;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', $timestart, $timeend);
|
||||
$this->setUser($user);
|
||||
|
||||
$courses = enrol_get_my_courses();
|
||||
if ($expectreturn) {
|
||||
$this->assertCount(1, $courses);
|
||||
$this->assertEquals($course->id, reset($courses)->id);
|
||||
} else {
|
||||
$this->assertEmpty($courses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test_course_users
|
||||
*
|
||||
@@ -1362,4 +1415,77 @@ class core_enrollib_testcase extends advanced_testcase {
|
||||
$durationinday = $duration / DAYSECS;
|
||||
$this->assertEquals(9, $durationinday);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_with_capabilities_join cannotmatchanyrows attribute.
|
||||
*
|
||||
* @dataProvider get_enrolled_with_capabilities_join_cannotmatchanyrows_data()
|
||||
* @param string $capability the tested capability
|
||||
* @param bool $useprohibit if the capability must be assigned to prohibit
|
||||
* @param int $expectedmatch expected cannotmatchanyrows value
|
||||
* @param int $expectedcount expceted count value
|
||||
*/
|
||||
public function test_get_enrolled_with_capabilities_join_cannotmatchanyrows(
|
||||
string $capability,
|
||||
bool $useprohibit,
|
||||
int $expectedmatch,
|
||||
int $expectedcount
|
||||
) {
|
||||
global $DB, $CFG;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
|
||||
$roleid = $CFG->defaultuserroleid;
|
||||
|
||||
// Override capability if necessary.
|
||||
if ($useprohibit && $capability) {
|
||||
assign_capability($capability, CAP_PROHIBIT, $roleid, $context);
|
||||
}
|
||||
|
||||
// Check if we must enrol or not.
|
||||
$this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
$join = get_enrolled_with_capabilities_join($context, '', $capability);
|
||||
|
||||
// Execute query.
|
||||
$sql = "SELECT COUNT(DISTINCT u.id)
|
||||
FROM {user} u {$join->joins}
|
||||
WHERE {$join->wheres}";
|
||||
$countrecords = $DB->count_records_sql($sql, $join->params);
|
||||
|
||||
// Validate cannotmatchanyrows.
|
||||
$this->assertEquals($expectedmatch, $join->cannotmatchanyrows);
|
||||
$this->assertEquals($expectedcount, $countrecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_enrolled_with_capabilities_join_cannotmatchanyrows
|
||||
*
|
||||
* @return @array of testing scenarios
|
||||
*/
|
||||
public function get_enrolled_with_capabilities_join_cannotmatchanyrows_data() {
|
||||
return [
|
||||
'no prohibits, no capability' => [
|
||||
'capability' => '',
|
||||
'useprohibit' => false,
|
||||
'expectedmatch' => 0,
|
||||
'expectedcount' => 1,
|
||||
],
|
||||
'no prohibits with capability' => [
|
||||
'capability' => 'moodle/course:manageactivities',
|
||||
'useprohibit' => false,
|
||||
'expectedmatch' => 0,
|
||||
'expectedcount' => 1,
|
||||
],
|
||||
'prohibits with capability' => [
|
||||
'capability' => 'moodle/course:manageactivities',
|
||||
'useprohibit' => true,
|
||||
'expectedmatch' => 1,
|
||||
'expectedcount' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,16 @@ class grade_export_xml extends grade_export {
|
||||
public $plugin = 'xml';
|
||||
public $updatedgradesonly = false; // default to export ALL grades
|
||||
|
||||
/**
|
||||
* Ensure we produce correctly formed XML content by encoding idnumbers appropriately
|
||||
*
|
||||
* @param string $idnumber
|
||||
* @return string
|
||||
*/
|
||||
private static function xml_export_idnumber(string $idnumber): string {
|
||||
return htmlspecialchars($idnumber, ENT_QUOTES | ENT_XML1);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be implemented by child classes
|
||||
* @param boolean $feedback
|
||||
@@ -84,9 +94,11 @@ class grade_export_xml extends grade_export {
|
||||
}
|
||||
|
||||
// only need id number
|
||||
fwrite($handle, "\t\t<assignment>{$grade_item->idnumber}</assignment>\n");
|
||||
$gradeitemidnumber = self::xml_export_idnumber($grade_item->idnumber);
|
||||
fwrite($handle, "\t\t<assignment>{$gradeitemidnumber}</assignment>\n");
|
||||
// this column should be customizable to use either student id, idnumber, uesrname or email.
|
||||
fwrite($handle, "\t\t<student>{$user->idnumber}</student>\n");
|
||||
$useridnumber = self::xml_export_idnumber($user->idnumber);
|
||||
fwrite($handle, "\t\t<student>{$useridnumber}</student>\n");
|
||||
// Format and display the grade in the selected display type (real, letter, percentage).
|
||||
if (is_array($this->displaytype)) {
|
||||
// Grades display type came from the return of export_bulk_export_data() on grade publishing.
|
||||
|
||||
@@ -12,10 +12,12 @@ Feature: I need to export grades as xml
|
||||
| username | firstname | lastname | email | idnumber |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
|
||||
| student1 | Student | 1 | student1@example.com | s1 |
|
||||
| student2 | Student | 2 | student2@example.com | 'Bill'&"Ben"<tag>Hello</tag> |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | course | idnumber | name | intro |
|
||||
| assign | C1 | a1 | Test assignment name | Submit something! |
|
||||
@@ -24,15 +26,21 @@ Feature: I need to export grades as xml
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name"
|
||||
And I give the grade "42.00" to the user "Student 2" for the grade item "Test assignment name"
|
||||
And I press "Save changes"
|
||||
|
||||
@javascript
|
||||
Scenario: Export grades as text
|
||||
Scenario: Export grades as XML
|
||||
When I navigate to "Export > XML file" in the course gradebook
|
||||
And I expand all fieldsets
|
||||
And I set the field "Grade export decimal places" to "1"
|
||||
And I press "Download"
|
||||
Then I should see "s1"
|
||||
And I should see "a1"
|
||||
And I should see "80.0"
|
||||
And I should not see "80.00"
|
||||
Then I should see "s1" in the "//results//result[1]//student" "xpath_element"
|
||||
And I should see "a1" in the "//results//result[1]//assignment" "xpath_element"
|
||||
And I should see "80.0" in the "//results//result[1]//score" "xpath_element"
|
||||
And I should not see "80.00" in the "//results//result[1]//score" "xpath_element"
|
||||
# Ensure we have the encoded ID number of student 2.
|
||||
And I should see "'Bill'&\"Ben\"<tag>Hello</tag>" in the "//results//result[2]//student" "xpath_element"
|
||||
And I should see "a1" in the "//results//result[2]//assignment" "xpath_element"
|
||||
And I should see "42.0" in the "//results//result[2]//score" "xpath_element"
|
||||
And I should not see "42.00" in the "//results//result[2]//score" "xpath_element"
|
||||
|
||||
+3
@@ -156,6 +156,9 @@ class fetch extends external_api {
|
||||
$hasgrade = $gradeitem->user_has_grade($gradeduser);
|
||||
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
|
||||
$instance = $gradeitem->get_advanced_grading_instance($USER, $grade);
|
||||
if (!$instance) {
|
||||
throw new moodle_exception('error:gradingunavailable', 'grading');
|
||||
}
|
||||
$controller = $instance->get_controller();
|
||||
$definition = $controller->get_definition();
|
||||
$fillings = $instance->get_guide_filling();
|
||||
|
||||
+4
@@ -149,6 +149,10 @@ class fetch extends external_api {
|
||||
$hasgrade = $gradeitem->user_has_grade($gradeduser);
|
||||
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
|
||||
$instance = $gradeitem->get_advanced_grading_instance($USER, $grade);
|
||||
if (!$instance) {
|
||||
throw new moodle_exception('error:gradingunavailable', 'grading');
|
||||
}
|
||||
|
||||
$controller = $instance->get_controller();
|
||||
$definition = $controller->get_definition();
|
||||
$fillings = $instance->get_rubric_filling();
|
||||
|
||||
@@ -231,7 +231,7 @@ if (!empty($method)) {
|
||||
} else {
|
||||
$tag = html_writer::tag('span', get_string('statusdraft', 'core_grading'), array('class' => 'status draft'));
|
||||
}
|
||||
echo $output->heading(s($definition->name) . ' ' . $tag, 3, 'definition-name');
|
||||
echo $output->heading(format_string($definition->name) . ' ' . $tag, 3, 'definition-name');
|
||||
echo $output->box($controller->render_preview($PAGE), 'definition-preview');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,10 @@ class gradeimport_csv_load_data {
|
||||
$select = "{$field} = :{$field}";
|
||||
}
|
||||
|
||||
// Validate if the user id value is numerical.
|
||||
if ($field === 'id' && !is_numeric($value)) {
|
||||
$errorkey = 'usermappingerror';
|
||||
}
|
||||
// Make sure the record exists and that there's only one matching record found.
|
||||
$user = $DB->get_record_select('user', $select, array($userfields['field'] => $value), '*', MUST_EXIST);
|
||||
} catch (dml_missing_record_exception $missingex) {
|
||||
|
||||
@@ -87,7 +87,7 @@ class provider implements
|
||||
* @param int $userid The userid of the user whose data is to be exported.
|
||||
*/
|
||||
public static function export_user_preferences(int $userid) {
|
||||
$preferences = get_user_preferences();
|
||||
$preferences = get_user_preferences(null, null, $userid);
|
||||
foreach ($preferences as $name => $value) {
|
||||
$prefname = null;
|
||||
$prefdescription = null;
|
||||
|
||||
@@ -63,14 +63,18 @@ class gradereport_grader_privacy_testcase extends \core_privacy\tests\provider_t
|
||||
* These preferences can be set on each course, but the value is shared in the whole site.
|
||||
*/
|
||||
public function test_export_user_preferences_single() {
|
||||
// Add some user preferences.
|
||||
// Create test user, add some preferences.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
set_user_preference('grade_report_showcalculations', 1, $user);
|
||||
set_user_preference('grade_report_meanselection', GRADE_REPORT_MEAN_GRADED, $user);
|
||||
set_user_preference('grade_report_studentsperpage', 50, $user);
|
||||
|
||||
// Validate exported data.
|
||||
// Switch to admin user (so we can validate preferences of our test user are still exported).
|
||||
$this->setAdminUser();
|
||||
|
||||
// Validate exported data for our test user.
|
||||
provider::export_user_preferences($user->id);
|
||||
$context = context_user::instance($user->id);
|
||||
$writer = writer::with_context($context);
|
||||
|
||||
+1
-1
@@ -397,7 +397,7 @@ Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.cor
|
||||
'<div class="{{CSS.FULLNAME}}">' +
|
||||
'<label for="{{checkboxId}}">{{fullname}}</label>' +
|
||||
'</div>' +
|
||||
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{extrafields}}</div>' +
|
||||
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{{extrafields}}}</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -397,7 +397,7 @@ Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.cor
|
||||
'<div class="{{CSS.FULLNAME}}">' +
|
||||
'<label for="{{checkboxId}}">{{fullname}}</label>' +
|
||||
'</div>' +
|
||||
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{extrafields}}</div>' +
|
||||
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{{extrafields}}}</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
@@ -395,7 +395,7 @@ Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.cor
|
||||
'<div class="{{CSS.FULLNAME}}">' +
|
||||
'<label for="{{checkboxId}}">{{fullname}}</label>' +
|
||||
'</div>' +
|
||||
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{extrafields}}</div>' +
|
||||
'<div id="{{extraFieldsId}}" class="{{CSS.EXTRAFIELDS}}">{{{extrafields}}}</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
@@ -13,4 +13,4 @@ Feature: We can use the user report
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "View > User report" in the course gradebook
|
||||
And I select "All users (0)" from the "Select all or one user" singleselect
|
||||
Then I should see "No students enrolled in this course yet"
|
||||
Then I should see "There are no students enrolled in this course."
|
||||
|
||||
+1
-1
@@ -232,7 +232,7 @@ if ($editform->is_cancelled()) {
|
||||
$newgroup = new stdClass();
|
||||
$newgroup->courseid = $data->courseid;
|
||||
$newgroup->name = $group['name'];
|
||||
$newgroup->enablemessaging = $data->enablemessaging;
|
||||
$newgroup->enablemessaging = $data->enablemessaging ?? 0;
|
||||
$groupid = groups_create_group($newgroup);
|
||||
$createdgroups[] = $groupid;
|
||||
foreach($group['members'] as $user) {
|
||||
|
||||
@@ -44,6 +44,9 @@ $action = required_param('action', PARAM_ALPHA);
|
||||
$factory = new factory();
|
||||
$editor = $factory->get_editor();
|
||||
|
||||
// Set context to default system context.
|
||||
$PAGE->set_context(null);
|
||||
|
||||
switch ($action) {
|
||||
// Load list of libraries or details for library.
|
||||
case 'libraries':
|
||||
|
||||
@@ -265,7 +265,22 @@ class editor_framework implements H5peditorStorage {
|
||||
* minorVersion as properties.
|
||||
*/
|
||||
public function alterLibraryFiles(&$files, $libraries): void {
|
||||
// This is to be implemented when the renderer is used.
|
||||
global $PAGE;
|
||||
|
||||
// Refactor dependency list.
|
||||
$librarylist = [];
|
||||
foreach ($libraries as $dependency) {
|
||||
$librarylist[$dependency['machineName']] = [
|
||||
'majorVersion' => $dependency['majorVersion'],
|
||||
'minorVersion' => $dependency['minorVersion']
|
||||
];
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('core_h5p');
|
||||
|
||||
$embedtype = 'editor';
|
||||
$renderer->h5p_alter_scripts($files['scripts'], $librarylist, $embedtype);
|
||||
$renderer->h5p_alter_styles($files['styles'], $librarylist, $embedtype);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1093,20 +1093,10 @@ class framework implements \H5PFrameworkInterface {
|
||||
* @param int $minorversion The library's minor version
|
||||
*/
|
||||
public function alterLibrarySemantics(&$semantics, $name, $majorversion, $minorversion) {
|
||||
global $DB;
|
||||
global $PAGE;
|
||||
|
||||
$library = $DB->get_record('h5p_libraries',
|
||||
array(
|
||||
'machinename' => $name,
|
||||
'majorversion' => $majorversion,
|
||||
'minorversion' => $minorversion,
|
||||
)
|
||||
);
|
||||
|
||||
if ($library) {
|
||||
$library->semantics = json_encode($semantics);
|
||||
$DB->update_record('h5p_libraries', $library);
|
||||
}
|
||||
$renderer = $PAGE->get_renderer('core_h5p');
|
||||
$renderer->h5p_alter_semantics($semantics, $name, $majorversion, $minorversion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -421,6 +421,8 @@ class helper {
|
||||
* @return array The JS array converted to PHP array.
|
||||
*/
|
||||
public static function parse_js_array(string $jscontent): array {
|
||||
// Convert all line-endings to UNIX format first.
|
||||
$jscontent = str_replace(array("\r\n", "\r"), "\n", $jscontent);
|
||||
$jsarray = preg_split('/,\n\s+/', substr($jscontent, 0, -1));
|
||||
$jsarray = preg_replace('~{?\\n~', '', $jsarray);
|
||||
|
||||
|
||||
@@ -79,7 +79,9 @@ abstract class handler {
|
||||
* Register the H5P autoloader.
|
||||
*/
|
||||
public static function register(): void {
|
||||
spl_autoload_register([static::class, 'autoload']);
|
||||
// Prepend H5P libraries in order to guarantee they are loaded first. Plugins using same libraries will need to use a
|
||||
// different namespace if they want to use a different version.
|
||||
spl_autoload_register([static::class, 'autoload'], true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,4 +37,49 @@ use plugin_renderer_base;
|
||||
*/
|
||||
class renderer extends plugin_renderer_base {
|
||||
|
||||
}
|
||||
/**
|
||||
* Alter which stylesheets are loaded for H5P.
|
||||
* This is useful for adding custom styles or replacing existing ones.
|
||||
*
|
||||
* @param array|object $scripts List of stylesheets that will be loaded
|
||||
* @param array $libraries Array of libraries indexed by the library's machineName
|
||||
* @param string $embedtype Possible values: div, iframe, external, editor
|
||||
*/
|
||||
public function h5p_alter_styles(&$scripts, array $libraries, string $embedtype) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter which scripts are loaded for H5P.
|
||||
* This is useful for adding custom scripts or replacing existing ones.
|
||||
*
|
||||
* @param array|object $scripts List of JavaScripts that will be loaded
|
||||
* @param array $libraries Array of libraries indexed by the library's machineName
|
||||
* @param string $embedtype Possible values: div, iframe, external, editor
|
||||
*/
|
||||
public function h5p_alter_scripts(&$scripts, array $libraries, string $embedtype) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter semantics before they are processed. This is useful for changing
|
||||
* how the editor looks and how content parameters are filtered.
|
||||
*
|
||||
* @param object|object $semantics Semantics as object
|
||||
* @param string $name Machine name of library
|
||||
* @param int $majorversion Major version of library
|
||||
* @param int $minorversion Minor version of library
|
||||
*/
|
||||
public function h5p_alter_semantics(&$semantics, $name, $majorversion, $minorversion) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter parameters of H5P content after it has been filtered through semantics.
|
||||
* This is useful for adapting the content to the current context.
|
||||
*
|
||||
* @param array|object $parameters The content parameters for the library
|
||||
* @param string $name The machine readable name of the library
|
||||
* @param int $majorversion Major version of the library
|
||||
* @param int $minorversion Minor version of the library
|
||||
*/
|
||||
public function h5p_alter_filtered_parameters(&$parameters, string $name, int $majorversion, int $minorversion) {
|
||||
}
|
||||
}
|
||||
|
||||
+30
-1
@@ -361,7 +361,7 @@ class player {
|
||||
$cid = $this->get_cid();
|
||||
// The filterParameters function should be called before getting the dependencyfiles because it rebuild content
|
||||
// dependency cache and export file.
|
||||
$settings['contents'][$cid]['jsonContent'] = $this->core->filterParameters($this->content);
|
||||
$settings['contents'][$cid]['jsonContent'] = $this->get_filtered_parameters();
|
||||
|
||||
$files = $this->get_dependency_files();
|
||||
if ($this->embedtype === 'div') {
|
||||
@@ -402,15 +402,44 @@ class player {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filtered parameters, modifying them by the renderer if the theme implements the h5p_alter_filtered_parameters function.
|
||||
*
|
||||
* @return string Filtered parameters.
|
||||
*/
|
||||
private function get_filtered_parameters(): string {
|
||||
global $PAGE;
|
||||
|
||||
$safeparams = $this->core->filterParameters($this->content);
|
||||
$decodedparams = json_decode($safeparams);
|
||||
$h5poutput = $PAGE->get_renderer('core_h5p');
|
||||
$h5poutput->h5p_alter_filtered_parameters(
|
||||
$decodedparams,
|
||||
$this->content['library']['name'],
|
||||
$this->content['library']['majorVersion'],
|
||||
$this->content['library']['minorVersion']
|
||||
);
|
||||
$safeparams = json_encode($decodedparams);
|
||||
|
||||
return $safeparams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds library dependencies of view
|
||||
*
|
||||
* @return array Files that the view has dependencies to
|
||||
*/
|
||||
private function get_dependency_files(): array {
|
||||
global $PAGE;
|
||||
|
||||
$preloadeddeps = $this->core->loadContentDependencies($this->h5pid, 'preloaded');
|
||||
$files = $this->core->getDependenciesFiles($preloadeddeps);
|
||||
|
||||
// Add additional asset files if required.
|
||||
$h5poutput = $PAGE->get_renderer('core_h5p');
|
||||
$h5poutput->h5p_alter_scripts($files['scripts'], $preloadeddeps, $this->embedtype);
|
||||
$h5poutput->h5p_alter_styles($files['styles'], $preloadeddeps, $this->embedtype);
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -5,52 +5,52 @@
|
||||
<json>
|
||||
<![CDATA[
|
||||
{
|
||||
"fontFamily": "h5p-core-21",
|
||||
"fontFamily": "h5p-core",
|
||||
"description": "Font generated by IcoMoon.",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 1,
|
||||
"version": "Version 1.1",
|
||||
"fontId": "h5p-core-21",
|
||||
"psName": "h5p-core-21",
|
||||
"fontId": "h5p-core",
|
||||
"psName": "h5p-core",
|
||||
"subFamily": "Regular",
|
||||
"fullName": "h5p-core-21"
|
||||
"fullName": "h5p-core"
|
||||
}
|
||||
]]>
|
||||
</json>
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="h5p-core-21" horiz-adv-x="1024">
|
||||
<font id="h5p-core" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="arrow-down" data-tags="arrow-down" d="M234 389.669h556l-278-278z" />
|
||||
<glyph unicode="" glyph-name="arrow-left" data-tags="arrow-left" d="M381-11.331v524l262-262z" />
|
||||
<glyph unicode="" glyph-name="colapse" data-tags="colapse" d="M512 447.336l256-256-60-60-196 196-196-196-60 60z" />
|
||||
<glyph unicode="" glyph-name="expand" data-tags="expand" d="M708 423.336l60-60-256-256-256 256 60 60 196-196z" />
|
||||
<glyph unicode="" glyph-name="move" data-tags="move" d="M386.662 725.063h71.27v-71.27h-71.27v71.27zM566.067 725.063h71.27v-71.27h-71.27v71.27zM386.662 568.8h71.27v-71.27h-71.27v71.27zM566.067 568.8h71.27v-71.27h-71.27v71.27zM386.662 412.435h71.27v-71.27h-71.27v71.27zM566.067 412.435h71.27v-71.27h-71.27v71.27zM386.662 256.173h71.27v-71.27h-71.27v71.27zM566.067 256.173h71.27v-71.27h-71.27v71.27zM386.662 99.808h71.27v-71.27h-71.27v71.27zM566.067 99.808h71.27v-71.27h-71.27v71.27zM386.662-56.454h71.27v-71.27h-71.27v71.27zM566.067-56.454h71.27v-71.27h-71.27v71.27z" />
|
||||
<glyph unicode="" glyph-name="check-mark" data-tags="check-mark" d="M454.299 245.924l-116.917 116.917-84.781-84.707 201.696-201.697 317.097 317.097-84.781 84.706z" />
|
||||
<glyph unicode="" glyph-name="arrow-up-circle" data-tags="arrow-up-circle" d="M512 606.057c-148.616 0-264.722-120.75-260.077-269.367 0-125.395 88.241-232.212 208.991-255.434v213.636h-92.885c-13.933 0-13.933 9.288-9.288 18.577l139.327 171.838c4.645 9.288 13.933 9.288 23.221 4.645 0 0 4.645-4.645 4.645-4.645l139.327-171.838c9.288-9.288 4.645-18.577-9.288-18.577h-92.885v-213.636c143.972 32.51 232.212 171.838 199.703 315.808-23.221 120.75-130.039 204.347-250.789 208.991z" />
|
||||
<glyph unicode="" glyph-name="info-circle" data-tags="info-circle" d="M512 601.601c-144.077 0-260.266-116.191-260.266-260.266s116.191-260.266 260.266-260.266 260.266 116.191 260.266 260.266v0c0 139.429-116.191 255.619-260.266 260.266zM470.171 550.478h88.305v-69.714h-88.305v69.714zM600.305 160.078h-181.257v51.123h51.123v162.666h-51.123v51.123h139.429v-218.438h46.477l-4.648-46.477z" />
|
||||
<glyph unicode="" glyph-name="search" data-tags="search" d="M772.098 125.51l-110.68 110.68c71.943 99.612 49.806 243.494-49.806 315.437s-243.494 44.27-315.437-55.339c-71.943-99.612-49.806-243.494 49.806-315.437 77.475-55.339 182.623-55.339 260.098 0l110.68-110.68c5.533-5.533 11.068-5.533 16.601 0 0 0 0 0 0 0l33.205 33.205c11.068 5.533 11.068 16.601 5.533 22.137 0 0 0 0 0 0zM478.795 202.985c-88.544 0-160.486 71.943-160.486 160.486s71.943 160.486 160.486 160.486 160.486-71.943 160.486-160.486-71.943-160.486-160.486-160.486v0z" />
|
||||
<glyph unicode="" glyph-name="fullscreen" data-tags="fullscreen" d="M368.55 490.521c5.737 5.737 0 5.737-5.737 5.737l-103.284 11.476c-5.737 5.737-11.476 0-11.476-5.737l11.476-109.021c0-5.737 5.737-5.737 5.737-5.737l103.284 103.284zM293.959 427.403l63.118-63.118c5.737-5.737 11.476-5.737 17.213 0l22.953 22.953c5.737 5.737 5.737 11.476 0 17.213l-63.118 57.379-40.166-34.429zM787.42 387.237c5.737-5.737 5.737 0 5.737 5.737l11.476 109.021c0 5.737-5.737 11.476-11.476 11.476l-109.021-11.476c-5.737 0-5.737-5.737-5.737-5.737l109.021-109.021zM724.305 461.832l-63.118-63.118c-5.737-5.737-5.737-11.476 0-17.213l22.953-22.953c5.737-5.737 11.476-5.737 17.213 0l63.118 63.118-40.166 40.166zM689.876 180.672c-5.737-5.737 0-5.737 5.737-5.737l109.021-11.476c5.737 0 11.476 5.737 11.476 11.476l-17.213 103.284c0 5.737-5.737 5.737-5.737 5.737l-103.284-103.284zM758.731 249.527l-63.118 63.118c-5.737 5.737-11.476 5.737-17.213 0l-22.953-22.953c-5.737-5.737-5.737-11.476 0-17.213l63.118-63.118 40.166 40.166zM265.269 283.956c-5.737 5.737-5.737 0-5.737-5.737l-11.476-109.021c0-5.737 5.737-11.476 11.476-11.476l109.021 11.476c5.737 0 5.737 5.737 5.737 5.737l-109.021 109.021zM334.124 209.362l63.118 63.118c5.737 5.737 5.737 11.476 0 17.213l-22.953 22.953c-5.737 5.737-11.476 5.737-17.213 0l-63.118-63.118 40.166-40.166zM161.985 593.805v-499.201h722.979v499.201h-722.979zM844.799 134.77h-636.911v413.13h636.911v-413.13z" />
|
||||
<glyph unicode="" glyph-name="h5p" data-tags="h5p" d="M934.072 489.192c-22.319 16.738-50.216 27.897-89.273 27.897h-139.487v-66.954h-156.225l-11.159-55.795c11.159 5.579 27.897 11.159 39.057 11.159s22.319 0 33.476 0c33.476 0 66.954-11.159 89.273-33.476s33.476-50.216 33.476-83.692c0-22.319-5.579-44.635-16.738-66.954s-27.897-39.057-50.216-50.216c-5.579-5.579-16.738 0-22.319-11.159h117.17v133.908h66.954c44.635 0 78.113 11.159 100.43 27.897 22.319 22.319 33.476 50.216 33.476 83.692 0 39.057-11.159 66.954-27.897 83.692v0zM839.221 377.603c-11.159-5.579-22.319-11.159-44.635-11.159h-33.476v83.692h39.057c22.319 0 33.476-5.579 44.635-11.159 5.579-5.579 11.159-16.738 11.159-27.897 0-16.738-5.579-27.897-16.738-33.476v0zM565.826 338.546c-16.738 0-33.476-11.159-44.635-27.897l-94.851 16.738 44.635 195.281h-94.851v-150.646h-117.17v150.646h-111.589v-362.667h111.589v133.908h117.17v-133.908h139.487c-16.738 11.159-33.476 11.159-44.635 22.319s-22.319 22.319-27.897 33.476c-5.579 11.159-11.159 22.319-16.738 39.057l94.851 16.738c5.579-16.738 22.319-27.897 44.635-27.897 27.897 0 50.216 22.319 50.216 50.216 0 22.319-22.319 44.635-50.216 44.635v0z" />
|
||||
<glyph unicode="" glyph-name="rights-of-use" data-tags="rights-of-use" d="M899.611 329.519c0-5.907 0-5.907 0-5.907-23.631-23.631-47.261-35.448-76.799-41.355-11.813 0-23.631-5.907-35.448-5.907s-17.724 0-29.537 0c0 0-5.907 0-5.907 5.907-64.985 59.079-135.877 118.153-200.863 183.139 0 0-5.907 0-5.907 0-23.631-5.907-47.261-11.813-70.892-17.724s-53.168 0-76.799 11.813c-17.724 11.813-23.631 17.724-29.537 35.448-5.907 5.907 0 23.631 11.813 23.631 41.355 11.813 88.616 29.537 129.971 47.261 11.813 5.907 29.537 5.907 41.355 5.907 5.907 0 11.813-5.907 11.813-5.907 41.355-17.724 82.709-29.537 124.060-47.261 0 0 5.907 0 5.907 0 29.537 5.907 64.985 17.724 94.523 23.631 5.907 0 5.907 0 5.907 0l106.34-212.676zM291.12 335.429c17.724 11.813 35.448 5.907 53.168-11.813 11.813-11.813 11.813-29.537 5.907-47.261 17.724 5.907 35.448-5.907 41.355-17.724 11.813-17.724 5.907-35.448-5.907-47.261 5.907 0 11.813 0 17.724 0 11.813-5.907 23.631-11.813 29.537-29.537s0-29.537-5.907-35.448c-5.907-5.907-11.813-11.813-17.724-17.724s-11.813-11.813-17.724-17.724-35.448-17.724-53.168 0c-29.537 29.537-53.168 64.985-82.709 94.523-17.724 23.631-35.448 41.355-47.261 64.985-5.907 11.813-11.813 17.724-11.813 29.537 0 5.907 0 17.724 5.907 23.631 11.813 11.813 17.724 17.724 29.537 29.537 17.724 17.724 47.261 11.813 64.985-5.907-5.907 0-5.907-5.907-5.907-11.813v0zM438.811 128.66l29.537-29.537c17.724-17.724 47.261-11.813 59.079 5.907l-5.907 5.907c-23.631 23.631-47.261 47.261-70.892 70.892-5.907 5.907-5.907 5.907-5.907 11.813s5.907 5.907 11.813 11.813c5.907 0 11.813 0 11.813-5.907 11.813-11.813 29.537-29.537 47.261-47.261 11.813-11.813 29.537-29.537 47.261-47.261 5.907-11.813 17.724-11.813 29.537-11.813 11.813 5.907 23.631 11.813 29.537 23.631 0 5.907 0 5.907 0 5.907-41.355 41.355-88.616 82.709-129.971 129.971-5.907 5.907-5.907 5.907-5.907 11.813 0 11.813 11.813 11.813 23.631 5.907 0 0 5.907 0 5.907-5.907 41.355-41.355 88.616-88.616 129.971-129.971 5.907-5.907 5.907-5.907 5.907-5.907 17.724 0 35.448 17.724 35.448 35.448 0 5.907 0 5.907 0 5.907-47.261 47.261-100.429 100.429-147.691 147.691-5.907 5.907-5.907 5.907-5.907 11.813s5.907 11.813 5.907 11.813c5.907 0 11.813 0 11.813-5.907 5.907-5.907 5.907-5.907 11.813-11.813 35.448-35.448 70.892-70.892 106.34-106.34 11.813-11.813 23.631-23.631 29.537-29.537 0 0 5.907-5.907 5.907 0 23.631 5.907 35.448 29.537 29.537 53.168h35.448c0 0 0 0 0 0 0-5.907 0-17.724 0-23.631-5.907-29.537-23.631-47.261-53.168-59.079 0 0-5.907 0-5.907-5.907-11.813-29.537-35.448-53.168-64.985-53.168-5.907 0-5.907 0-5.907-5.907-17.724-35.448-59.079-47.261-88.616-35.448-5.907 0-11.813 5.907-11.813 5.907-5.907-5.907-11.813-11.813-23.631-17.724-29.537-11.813-59.079-5.907-76.799 11.813-11.813 11.813-17.724 17.724-29.537 29.537 17.724 23.631 23.631 29.537 29.537 41.355v0 0zM273.396 642.628c29.537-11.813 64.985-23.631 94.523-29.537 35.448-11.813 64.985-23.631 100.429-29.537 0 0 0 0 5.907 0-17.724-5.907-35.448-11.813-47.261-17.724 0 0-5.907 0-5.907 0-47.261 11.813-94.523 23.631-135.877 41.355-5.907 0-5.907 0-5.907 0l-76.799-183.139c0-11.813 5.907-17.724 11.813-23.631s5.907-5.907 5.907-11.813c-5.907-5.907-11.813-17.724-23.631-23.631-17.724 17.724-29.537 35.448-29.537 64.985l88.616 212.676c-5.907-11.813 5.907 5.907 17.724 0v0z" />
|
||||
<glyph unicode="" glyph-name="delete-circle" data-tags="delete-circle" d="M512 601.601c-147.107 0-260.266-118.817-260.266-260.266s118.817-260.266 260.266-260.266 260.266 118.817 260.266 260.266-113.158 260.266-260.266 260.266zM653.449 262.123c5.659-5.659 5.659-16.973 0-28.29l-33.949-33.949c-5.659-5.659-16.973-5.659-28.29 0l-79.212 79.212-79.212-79.212c-5.659-5.659-16.973-5.659-28.29 0l-33.949 33.949c-5.659 5.659-5.659 16.973 0 28.29l84.871 79.212-79.212 79.212c-5.659 5.659-5.659 16.973 0 28.29l33.949 33.949c5.659 5.659 16.973 5.659 28.29 0l73.554-84.871 79.212 79.212c5.659 5.659 16.973 5.659 28.29 0l33.949-33.949c5.659-5.659 5.659-16.973 0-28.29l-79.212-73.554 79.212-79.212z" />
|
||||
<glyph unicode="" glyph-name="window" data-tags="window" d="M203.936 461.136c-5.704-5.704 0-5.704 5.704-5.704l108.394-11.41c5.704 0 11.41 5.704 11.41 11.41l-17.114 102.687c0 5.704-5.704 5.704-5.704 5.704l-102.687-102.687zM272.395 523.891l-62.752 62.752c-5.704 5.704-11.41 5.704-17.114 0l-17.114-22.821c-5.704-5.704-5.704-11.41 0-17.114l62.752-62.752 34.228 39.935zM751.605 558.119c-5.704 5.704-5.704 0-5.704-5.704l-11.41-108.394c0-5.704 5.704-11.41 11.41-11.41l108.394 11.41c5.704 0 5.704 5.704 5.704 5.704l-108.394 108.394zM814.357 483.957l62.752 62.752c5.704 5.704 5.704 11.41 0 17.114l-22.821 22.821c-5.704 5.704-11.41 5.704-17.114 0l-62.752-62.752 39.935-39.935zM848.588 221.534c5.704 5.704 0 5.704-5.704 5.704l-102.687 17.114c-5.704 0-11.41-5.704-11.41-11.41l11.41-108.394c0-5.704 5.704-5.704 5.704-5.704l102.687 102.687zM780.129 158.779l62.752-62.752c5.704-5.704 11.41-5.704 17.114 0l22.821 22.821c5.704 5.704 5.704 11.41 0 17.114l-62.752 62.752-39.935-39.935zM300.919 124.551c5.704-5.704 5.704 0 5.704 5.704l11.41 108.394c0 5.704-5.704 11.41-11.41 11.41l-108.394-11.41c-5.704 0-5.704-5.704-5.704-5.704l108.394-108.394zM238.167 193.010l-62.752-62.752c-5.704-5.704-5.704-11.41 0-17.114l22.821-22.821c5.704-5.704 11.41-5.704 17.114 0l62.752 62.752-39.935 39.935zM352.264 466.843v-239.605h347.998v239.605h-347.998zM654.622 267.172h-262.424v154.032h262.424v-154.032z" />
|
||||
<glyph unicode="" glyph-name="code" data-tags="code" d="M449.641 235.325c6.235-6.235 6.235-12.472 6.235-18.707v-62.359c0-6.235-6.235-6.235-6.235-6.235l-230.728 155.897c-6.235 6.235-6.235 12.472-6.235 18.707v49.886c0 6.235 6.235 12.472 6.235 18.707l230.728 155.897c6.235 6.235 6.235 0 6.235-6.235v-62.359c0-6.235-6.235-12.472-6.235-18.707l-162.134-112.245c-6.235-6.235-6.235-6.235 0-12.472l162.134-99.776zM736.493 341.335c6.235 6.235 6.235 6.235 0 12.472l-155.897 112.245c-6.235 6.235-6.235 12.472-6.235 18.707v62.359c0 6.235 6.235 6.235 6.235 6.235l230.728-155.897c6.235-6.235 6.235-12.472 6.235-18.707v-49.886c0-6.235-6.235-12.472-6.235-18.707l-230.728-155.897c-6.235-6.235-6.235 0-6.235 6.235v62.359c0 6.235 6.235 12.472 6.235 18.707l155.897 99.776z" />
|
||||
<glyph unicode="" glyph-name="download" data-tags="download" d="M358.941 435.525c-11.773 0-17.66-5.887-5.887-17.66l153.059-188.382c5.887-11.773 23.547-11.773 29.433 0l153.059 188.382c5.887 11.773 5.887 17.66-5.887 17.66h-323.782zM576.756 423.751v135.399c0 11.773-11.773 23.547-23.547 23.547h-70.643c-11.773 0-23.547-11.773-23.547-23.547v-141.286h117.739zM653.286 288.352c-5.887 0-17.66-5.887-23.547-11.773l-76.53-94.19c-5.887-5.887-17.66-17.66-23.547-23.547 0 0-5.887-5.887-11.773-5.887s-17.66 11.773-17.66 11.773c-5.887 5.887-17.66 17.66-23.547 23.547l-76.53 94.19c-5.887 5.887-17.66 11.773-23.547 11.773h-123.626c-5.887 0-17.66-5.887-17.66-17.66v-141.286c0-5.887 5.887-17.66 17.66-17.66h529.824c5.887 0 17.66 5.887 17.66 17.66v141.286c0 5.887-5.887 17.66-17.66 17.66l-129.513-5.887zM305.958 176.502c-17.66 0-29.433 11.773-29.433 29.433s11.773 29.433 29.433 29.433c17.66 0 29.433-11.773 29.433-29.433s-11.773-29.433-29.433-29.433v0z" />
|
||||
<glyph unicode="" glyph-name="delete" data-tags="delete" d="M620.266 341.335l134.045 134.045c10.311 10.311 10.311 30.934 0 41.245l-61.866 61.866c-10.311 10.311-30.934 10.311-41.245 0l-134.045-134.045-134.045 134.045c-10.311 10.311-30.934 10.311-41.245 0l-61.866-61.866c-10.311-10.311-10.311-30.934 0-41.245l134.045-134.045-134.045-134.045c-10.311-10.311-10.311-30.934 0-41.245l61.866-61.866c10.311-10.311 30.934-10.311 41.245 0l134.045 134.045 134.045-134.045c10.311-10.311 30.934-10.311 41.245 0l61.866 61.866c10.311 10.311 10.311 30.934 0 41.245l-134.045 134.045z" />
|
||||
<glyph unicode="" glyph-name="edit-image" data-tags="edit-image" d="M300.237 621.639c69.018 23.142 133.325 14.234 189.133-33.28 56.627-48.128 77.619-110.592 63.181-183.808-2.355-12.186 0.307-19.456 8.704-27.853 93.901-93.389 156.774-156.467 250.47-250.163 5.427-5.427 10.957-10.854 15.667-16.896 39.424-50.278 16.794-124.006-44.237-142.029-36.966-10.957-68.403 0-95.334 27.034-95.642 96.051-160.973 160.973-256.614 257.024-6.963 6.963-12.8 8.909-22.63 6.758-117.76-26.317-229.171 60.826-231.731 181.453-0.614 26.419 3.584 52.326 15.974 77.926 34.816-34.816 68.506-67.789 101.274-101.786 10.445-10.752 20.992-15.36 36.045-15.36 14.643 0 25.19 3.891 34.816 14.848 10.752 12.39 23.040 23.347 34.611 35.021 14.336 14.438 14.336 46.080-0.205 60.518-35.123 35.226-70.349 70.349-106.598 106.496 3.891 2.253 5.632 3.482 7.475 4.096zM703.386 63.559c-0.41-24.269 20.685-45.466 44.851-45.158 23.757 0.41 44.032 20.992 43.93 44.544-0.102 23.757-20.275 44.032-44.237 44.134-23.859 0.307-44.237-19.661-44.544-43.52z" />
|
||||
<glyph unicode="" glyph-name="hourglass" data-tags="hourglass" d="M733.286-10.579c-147.763 0-295.526 0-443.29 0 0 2.048 0.102 4.096 0 6.144-0.307 13.824-1.024 32.666-0.922 46.49 0.41 39.731 6.861 78.131 19.046 115.2 17.203 52.224 43.725 96.256 81.306 130.355 4.506 4.096 9.216 7.885 13.722 11.776-0.205 0.717-0.307 1.126-0.41 1.229-1.331 1.229-2.765 2.355-4.198 3.584-28.058 22.63-50.688 51.405-68.403 85.606-30.618 59.085-43.52 123.597-41.165 192.614 0.205 7.168 0.614 18.33 0.922 25.498 147.763 0 295.526 0 443.29 0 0.205-1.331 0.512-2.662 0.614-3.994 2.662-36.966 1.229-77.722-5.939-113.869-14.336-72.909-44.544-133.837-95.027-179.405-4.096-3.686-8.294-7.066-12.39-10.547 0.205-0.717 0.307-1.126 0.512-1.331 0.819-0.717 1.638-1.434 2.458-2.15 42.189-33.894 71.68-79.872 90.931-135.782 11.776-34.202 18.637-69.837 20.070-106.701 0.819-19.763-0.614-44.851-1.126-64.717zM687.309 32.634c0 6.554 0.205 12.493 0 18.432-1.331 37.581-7.27 74.138-19.866 108.749-17.92 49.562-45.568 88.269-88.678 108.646-2.458 1.126-2.97 3.072-2.97 5.837 0.102 16.691 0.102 33.485 0 50.176 0 3.994 1.331 5.427 4.096 6.963 9.114 5.325 18.432 10.24 26.829 16.896 29.696 23.552 49.152 56.934 62.362 95.744 10.342 30.413 15.77 62.259 17.818 94.822 0.614 9.114 0.102 18.227 0.102 27.546-116.634 0-233.574 0-351.027 0 0.307-8.704 0.614-16.998 1.024-25.395 1.946-37.274 8.499-73.216 21.504-107.213 18.125-47.104 45.261-83.558 86.528-103.117 2.253-1.024 2.867-2.662 2.867-5.427-0.102-17.203-0.102-34.509 0-51.712 0-3.072-1.024-4.506-3.277-5.632-5.632-2.867-11.366-5.734-16.691-9.216-34.304-22.733-56.832-57.754-71.987-100.147-12.493-35.123-18.125-71.987-19.661-109.875-0.205-5.325 0-10.752 0-16.282 117.35 0.205 234.189 0.205 351.027 0.205zM410.214 451.962c68.096 0 135.373 0 203.674 0-3.789-6.554-7.168-12.595-10.752-18.227-10.957-16.998-24.269-30.618-39.731-41.472s-27.75-25.293-32.768-46.592c-1.638-6.963-2.765-14.336-2.867-21.606-0.307-17.203-0.205-34.406 0.307-51.712 0.717-28.058 12.493-48.947 32.154-62.566 43.008-30.003 65.843-75.878 75.776-132.506 0.307-1.638 0.307-3.379 0.614-5.53-83.149 0-166.093 0-249.242 0 2.662 20.685 7.885 40.346 15.462 59.085 13.312 32.973 32.768 59.187 59.494 77.722 16.589 11.469 28.365 27.955 32.358 50.995 0.819 4.813 1.331 9.83 1.434 14.746 0.102 18.637 0.614 37.274-0.205 55.808-1.126 24.678-11.981 43.213-28.57 57.139-9.216 7.782-18.944 14.746-27.648 23.142-11.981 11.162-21.197 25.395-29.491 41.574z" />
|
||||
<glyph unicode="" glyph-name="plus-icon" data-tags="plus-icon" d="M768 285.015c0-19.323-15.664-34.987-34.987-34.987h-151.040v-151.467c0-19.323-15.664-34.987-34.987-34.987h-69.547c-19.323 0-34.987 15.664-34.987 34.987v151.467h-151.467c-19.323 0-34.987 15.664-34.987 34.987v69.547c0 19.323 15.664 34.987 34.987 34.987h151.467v151.467c0 19.323 15.664 34.987 34.987 34.987h69.547c19.323 0 34.987-15.664 34.987-34.987v-151.467h151.467c19.323 0 34.987-15.664 34.987-34.987z" />
|
||||
<glyph unicode="" glyph-name="video-upload-icon" data-tags="video-upload-icon" d="M384 328.535v-128c0-21.333 21.333-42.667 42.667-42.667h128c21.333 0 42.667 17.067 42.667 42.667v128c0 21.333-17.067 42.667-42.667 42.667h-128c-21.333 0-42.667-17.067-42.667-42.667zM785.067 499.201l-102.4 106.667c-12.8 12.8-38.4 21.333-55.467 21.333h-140.8l21.333-42.667h89.6v-136.533c0-17.067 12.8-29.867 29.867-29.867h140.8v-341.333h-426.667v328.533h-42.667v-341.333c0-17.067 12.8-29.867 29.867-29.867h448c17.067 0 29.867 12.8 29.867 29.867v384c4.267 17.067-8.533 38.4-21.333 51.2zM640 456.535v123.733c4.267 0 12.8-4.267 12.8-8.533l102.4-102.4c4.267-4.267 4.267-8.533 8.533-12.8h-123.733zM725.333 166.401v196.267c0 4.267-4.267 8.533-8.533 8.533s-8.533 0-12.8-4.267l-89.6-89.6v-29.867l89.6-89.6c8.533-4.267 8.533 0 12.8 0 4.267 4.267 8.533 4.267 8.533 8.533zM349.867 473.601v136.533l59.733-59.733c8.533-8.533 17.067-4.267 25.6 0l12.8 12.8c8.533 8.533 8.533 17.067 0 25.6l-115.2 106.667c-8.533 8.533-17.067 8.533-21.333 0l-115.2-110.933c-4.267-8.533-4.267-17.067 0-25.6l12.8-12.8c8.533-8.533 17.067-8.533 21.333 0l59.733 59.733v-136.533c0-8.533 8.533-17.067 17.067-17.067h25.6c0 0 17.067 8.533 17.067 21.333z" />
|
||||
<glyph unicode="" glyph-name="play-icon" data-tags="play-icon" d="M392.533 597.335c81.067 46.933 187.733 46.933 273.067 0 42.667-25.6 72.533-55.467 98.133-98.133 72.533-128 29.867-294.4-98.133-371.2-128-72.533-294.4-29.867-371.2 98.133-46.933 81.067-46.933 187.733 0 273.067 21.333 42.667 55.467 72.533 98.133 98.133zM661.333 345.601c12.8 8.533 12.8 29.867 0 38.4l-192 110.933c-8.533 4.267-12.8 4.267-21.333 0s-12.8-12.8-12.8-21.333v-226.133c0-8.533 4.267-17.067 12.8-21.333s17.067-4.267 21.333 0l192 119.467z" />
|
||||
<glyph unicode="" glyph-name="copy" data-tags="copy" d="M722.133 561.201h-247.733c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v288.533c-0.267 65.867-53.467 119.2-119.333 119.2zM778.533 156.534c0-31.333-25.067-56.4-56.4-56.4h-247.867c-31.333 0-56.4 25.067-56.4 56.4v285.467c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-285.467zM245.2 326.001v288.533c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-18.8h62.667v18.8c0 65.867-53.333 119.2-119.2 119.2h-247.467c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h18.8v62.667h-18.8c-31.467-3.067-56.533 22-56.533 56.533zM681.2 360.534h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 266.401h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 172.268h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333z" />
|
||||
<glyph unicode="" glyph-name="examples-icon" data-tags="examples-icon" d="M213.333 166.402c89.6 38.4 183.467 68.267 273.067 17.067v281.6c-68.267 46.933-157.867 55.467-234.667 12.8l-38.4-311.467zM810.667 166.402l-42.667 315.733c-72.533 38.4-166.4 34.133-234.667-17.067v-285.867c93.867 51.2 187.733 21.333 277.333-12.8zM832 520.535c-51.2 29.867-110.933 46.933-170.667 55.467-51.2 0-102.4-8.533-149.333-29.867-46.933 21.333-98.133 29.867-149.333 29.867-59.733-4.267-119.467-25.6-170.667-55.467l-64-452.267c0 0 29.867-17.067 110.933 21.333 46.933 25.6 102.4 34.133 157.867 25.6 42.667-4.267 85.333-21.333 115.2-55.467v0c29.867 34.133 72.533 51.2 115.2 55.467 55.467 4.267 106.667-4.267 157.867-29.867 81.067-38.4 110.933-21.333 110.933-21.333l-64 456.533zM793.6 115.202c-42.667 21.333-89.6 34.133-140.8 34.133-8.533 0-21.333 0-29.867 0-42.667-4.267-81.067-17.067-115.2-42.667-34.133 25.6-72.533 38.4-115.2 42.667-12.8 0-21.333 0-34.133 0-46.933 0-93.867-8.533-136.533-29.867-21.333-12.8-46.933-21.333-72.533-25.6l64 413.867c46.933 25.6 98.133 38.4 149.333 42.667 46.933 0 93.867-8.533 136.533-25.6l12.8-8.533 12.8 4.267c42.667 17.067 89.6 25.6 136.533 25.6 51.2-4.267 102.4-17.067 149.333-42.667l59.733-418.133c-29.867 8.533-51.2 17.067-76.8 29.867z" />
|
||||
<glyph unicode="" glyph-name="tutorials-icon" data-tags="tutorials-icon" d="M887.467 430.935l-375.467-110.933h-4.267l-217.6 68.267c-21.333-25.6-34.133-59.733-34.133-98.133 21.333-12.8 25.6-38.4 12.8-59.733-4.267-4.267-8.533-8.533-12.8-12.8l17.067-145.067c0-4.267 0-4.267-4.267-8.533 0 0 0 0-4.267 0h-64c-4.267 0-4.267 0-8.533 4.267 0 4.267-4.267 4.267-4.267 8.533l17.067 145.067c-12.8 8.533-17.067 21.333-17.067 34.133 0 17.067 8.533 29.867 21.333 38.4 0 38.4 12.8 76.8 34.133 110.933l-106.667 29.867c-8.533 4.267-8.533 8.533-8.533 17.067 0 4.267 4.267 4.267 4.267 4.267l375.467 119.467h4.267l375.467-123.733c4.267 0 8.533-4.267 8.533-8.533s-4.267-8.533-8.533-12.8zM725.333 234.669c4.267-46.933-93.867-85.333-213.333-85.333s-213.333 38.4-213.333 85.333l4.267 106.667 192-64c4.267 0 12.8 0 17.067 0s12.8 0 17.067 4.267l192 59.733 4.267-106.667z" />
|
||||
<glyph unicode="" glyph-name="info-important-description" data-tags="info-important-description" d="M512 697.368c-188.5 0-341.3-152.8-341.3-341.3s152.8-341.4 341.3-341.4 341.3 152.8 341.3 341.3-152.8 341.4-341.3 341.4v0zM512 43.268c-172.7 0-312.7 140-312.7 312.7s140 312.7 312.7 312.7c172.7 0 312.7-140 312.7-312.7-0.2-172.6-140.1-312.5-312.7-312.7v0zM512 605.568c-137.9 0-249.6-111.8-249.6-249.6s111.7-249.6 249.6-249.6 249.6 111.8 249.6 249.6-111.8 249.6-249.6 249.6v0z" />
|
||||
<glyph unicode="" glyph-name="icon-info" data-tags="icon-info" d="M467.2 459.522h87.467c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v70.385c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-87.462c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-69.959c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.715-4.373 9.2-7.159 15.338-7.245zM597.333 156.589h-22.187v209.92c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-130.128c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h22.182v-139.947h-22.187c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h174.075c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v46.065c0.043 0.527 0.067 1.141 0.067 1.761 0 5.302-1.791 10.185-4.8 14.079-3.742 4.424-9.36 7.247-15.636 7.247-0.489 0-0.975-0.017-1.456-0.051z" />
|
||||
<glyph unicode="" glyph-name="paste" data-tags="paste" d="M394.402 702.4h-75.333c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-56.4 37.6-100.4 87.867-116v69.067c-15.733 9.467-25.067 25.067-25.067 47.067v288.4c0 31.333 25.067 56.4 56.4 56.4h131.733c0 0 0 0 3.2 3.2v0c0 31.333-28.267 59.6-59.6 59.6zM704.802 592.533c0 0-28.267 0-40.8 0-12.533 34.533-43.867 59.6-84.667 59.6s-69.067-25.067-81.6-59.6c-12.533 0-40.8 0-40.8 0-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v285.467c3.2 65.867-53.2 122.267-119.2 122.267zM582.535 605.2c22 0 40.8-18.8 40.8-40.8s-18.8-40.8-40.8-40.8c-22 0-40.8 18.8-40.8 40.8s15.733 40.8 40.8 40.8zM764.402 181.733c0-31.333-25.067-56.4-56.4-56.4h-250.933c-31.333 0-56.4 25.067-56.4 56.4v288.533c0 18.8 9.467 37.6 25.067 47.067v0c0-43.867 34.533-78.4 78.4-78.4h160c43.867 0 78.4 34.533 78.4 78.4v0c12.533-9.467 22-28.267 22-47.067v-288.533z" />
|
||||
<glyph unicode="" glyph-name="arrow-down" data-tags="arrow-down" d="M234 565.667h556l-278-278z" />
|
||||
<glyph unicode="" glyph-name="arrow-left" data-tags="arrow-left" d="M381 164.667v524l262-262z" />
|
||||
<glyph unicode="" glyph-name="colapse" data-tags="colapse" d="M512 584.667l256-256-60-60-196 196-196-196-60 60z" />
|
||||
<glyph unicode="" glyph-name="expand" data-tags="expand" d="M708 584.667l60-60-256-256-256 256 60 60 196-196z" />
|
||||
<glyph unicode="" glyph-name="move" data-tags="move" d="M386.662 853.061h71.27v-71.27h-71.27v71.27zM566.067 853.061h71.27v-71.27h-71.27v71.27zM386.662 696.798h71.27v-71.27h-71.27v71.27zM566.067 696.798h71.27v-71.27h-71.27v71.27zM386.662 540.433h71.27v-71.27h-71.27v71.27zM566.067 540.433h71.27v-71.27h-71.27v71.27zM386.662 384.171h71.27v-71.27h-71.27v71.27zM566.067 384.171h71.27v-71.27h-71.27v71.27zM386.662 227.806h71.27v-71.27h-71.27v71.27zM566.067 227.806h71.27v-71.27h-71.27v71.27zM386.662 71.544h71.27v-71.27h-71.27v71.27zM566.067 71.544h71.27v-71.27h-71.27v71.27z" />
|
||||
<glyph unicode="" glyph-name="check-mark" data-tags="check-mark" d="M454.301 395.253l-116.917 116.917-84.781-84.707 201.696-201.697 317.097 317.097-84.781 84.706z" />
|
||||
<glyph unicode="" glyph-name="arrow-up-circle" data-tags="arrow-up-circle" d="M513.344 689.068c-148.616 0-264.722-120.75-260.077-269.367 0-125.395 88.241-232.212 208.991-255.434v213.636h-92.885c-13.933 0-13.933 9.288-9.288 18.577l139.327 171.838c4.645 9.288 13.933 9.288 23.221 4.645 0 0 4.645-4.645 4.645-4.645l139.327-171.838c9.288-9.288 4.645-18.577-9.288-18.577h-92.885v-213.636c143.972 32.51 232.212 171.838 199.703 315.808-23.221 120.75-130.039 204.347-250.789 208.991z" />
|
||||
<glyph unicode="" glyph-name="info-circle" data-tags="info-circle" d="M512 686.933c-144.077 0-260.266-116.191-260.266-260.266s116.191-260.266 260.266-260.266 260.266 116.191 260.266 260.266v0c0 139.429-116.191 255.619-260.266 260.266zM470.171 635.81h88.305v-69.714h-88.305v69.714zM600.305 245.41h-181.257v51.123h51.123v162.666h-51.123v51.123h139.429v-218.438h46.477l-4.648-46.477z" />
|
||||
<glyph unicode="" glyph-name="search" data-tags="search" d="M769.263 223.163l-110.68 110.68c71.943 99.612 49.806 243.494-49.806 315.437s-243.494 44.27-315.437-55.339c-71.943-99.612-49.806-243.494 49.806-315.437 77.475-55.339 182.623-55.339 260.098 0l110.68-110.68c5.533-5.533 11.068-5.533 16.601 0 0 0 0 0 0 0l33.205 33.205c11.068 5.533 11.068 16.601 5.533 22.137 0 0 0 0 0 0zM475.96 300.638c-88.544 0-160.486 71.943-160.486 160.486s71.943 160.486 160.486 160.486 160.486-71.943 160.486-160.486-71.943-160.486-160.486-160.486v0z" />
|
||||
<glyph unicode="" glyph-name="fullscreen" data-tags="fullscreen" d="M357.075 572.983c5.737 5.737 0 5.737-5.737 5.737l-103.284 11.476c-5.737 5.737-11.476 0-11.476-5.737l11.476-109.021c0-5.737 5.737-5.737 5.737-5.737l103.284 103.284zM282.484 509.865l63.118-63.118c5.737-5.737 11.476-5.737 17.213 0l22.953 22.953c5.737 5.737 5.737 11.476 0 17.213l-63.118 57.379-40.166-34.429zM775.945 469.699c5.737-5.737 5.737 0 5.737 5.737l11.476 109.021c0 5.737-5.737 11.476-11.476 11.476l-109.021-11.476c-5.737 0-5.737-5.737-5.737-5.737l109.021-109.021zM712.83 544.294l-63.118-63.118c-5.737-5.737-5.737-11.476 0-17.213l22.953-22.953c5.737-5.737 11.476-5.737 17.213 0l63.118 63.118-40.166 40.166zM678.401 263.134c-5.737-5.737 0-5.737 5.737-5.737l109.021-11.476c5.737 0 11.476 5.737 11.476 11.476l-17.213 103.284c0 5.737-5.737 5.737-5.737 5.737l-103.284-103.284zM747.256 331.989l-63.118 63.118c-5.737 5.737-11.476 5.737-17.213 0l-22.953-22.953c-5.737-5.737-5.737-11.476 0-17.213l63.118-63.118 40.166 40.166zM253.794 366.418c-5.737 5.737-5.737 0-5.737-5.737l-11.476-109.021c0-5.737 5.737-11.476 11.476-11.476l109.021 11.476c5.737 0 5.737 5.737 5.737 5.737l-109.021 109.021zM322.649 291.824l63.118 63.118c5.737 5.737 5.737 11.476 0 17.213l-22.953 22.953c-5.737 5.737-11.476 5.737-17.213 0l-63.118-63.118 40.166-40.166zM150.51 676.267v-499.201h722.979v499.201h-722.979zM833.324 217.232h-636.911v413.13h636.911v-413.13z" />
|
||||
<glyph unicode="" glyph-name="h5p" data-tags="h5p" d="M891.406 574.524c-22.319 16.738-50.216 27.897-89.273 27.897h-139.487v-66.954h-156.225l-11.159-55.795c11.159 5.579 27.897 11.159 39.057 11.159s22.319 0 33.476 0c33.476 0 66.954-11.159 89.273-33.476s33.476-50.216 33.476-83.692c0-22.319-5.579-44.635-16.738-66.954s-27.897-39.057-50.216-50.216c-5.579-5.579-16.738 0-22.319-11.159h117.17v133.908h66.954c44.635 0 78.113 11.159 100.43 27.897 22.319 22.319 33.476 50.216 33.476 83.692 0 39.057-11.159 66.954-27.897 83.692v0zM796.555 462.935c-11.159-5.579-22.319-11.159-44.635-11.159h-33.476v83.692h39.057c22.319 0 33.476-5.579 44.635-11.159 5.579-5.579 11.159-16.738 11.159-27.897 0-16.738-5.579-27.897-16.738-33.476v0zM523.16 423.878c-16.738 0-33.476-11.159-44.635-27.897l-94.851 16.738 44.635 195.281h-94.851v-150.646h-117.17v150.646h-111.589v-362.667h111.589v133.908h117.17v-133.908h139.487c-16.738 11.159-33.476 11.159-44.635 22.319s-22.319 22.319-27.897 33.476c-5.579 11.159-11.159 22.319-16.738 39.057l94.851 16.738c5.579-16.738 22.319-27.897 44.635-27.897 27.897 0 50.216 22.319 50.216 50.216 0 22.319-22.319 44.635-50.216 44.635v0z" />
|
||||
<glyph unicode="" glyph-name="rights-of-use" data-tags="rights-of-use" d="M878.277 414.35c0-5.907 0-5.907 0-5.907-23.631-23.631-47.261-35.448-76.799-41.355-11.813 0-23.631-5.907-35.448-5.907s-17.724 0-29.537 0c0 0-5.907 0-5.907 5.907-64.985 59.079-135.877 118.153-200.863 183.139 0 0-5.907 0-5.907 0-23.631-5.907-47.261-11.813-70.892-17.724s-53.168 0-76.799 11.813c-17.724 11.813-23.631 17.724-29.537 35.448-5.907 5.907 0 23.631 11.813 23.631 41.355 11.813 88.616 29.537 129.971 47.261 11.813 5.907 29.537 5.907 41.355 5.907 5.907 0 11.813-5.907 11.813-5.907 41.355-17.724 82.709-29.537 124.060-47.261 0 0 5.907 0 5.907 0 29.537 5.907 64.985 17.724 94.523 23.631 5.907 0 5.907 0 5.907 0l106.34-212.676zM269.786 420.26c17.724 11.813 35.448 5.907 53.168-11.813 11.813-11.813 11.813-29.537 5.907-47.261 17.724 5.907 35.448-5.907 41.355-17.724 11.813-17.724 5.907-35.448-5.907-47.261 5.907 0 11.813 0 17.724 0 11.813-5.907 23.631-11.813 29.537-29.537s0-29.537-5.907-35.448c-5.907-5.907-11.813-11.813-17.724-17.724s-11.813-11.813-17.724-17.724-35.448-17.724-53.168 0c-29.537 29.537-53.168 64.985-82.709 94.523-17.724 23.631-35.448 41.355-47.261 64.985-5.907 11.813-11.813 17.724-11.813 29.537 0 5.907 0 17.724 5.907 23.631 11.813 11.813 17.724 17.724 29.537 29.537 17.724 17.724 47.261 11.813 64.985-5.907-5.907 0-5.907-5.907-5.907-11.813v0zM417.477 213.491l29.537-29.537c17.724-17.724 47.261-11.813 59.079 5.907l-5.907 5.907c-23.631 23.631-47.261 47.261-70.892 70.892-5.907 5.907-5.907 5.907-5.907 11.813s5.907 5.907 11.813 11.813c5.907 0 11.813 0 11.813-5.907 11.813-11.813 29.537-29.537 47.261-47.261 11.813-11.813 29.537-29.537 47.261-47.261 5.907-11.813 17.724-11.813 29.537-11.813 11.813 5.907 23.631 11.813 29.537 23.631 0 5.907 0 5.907 0 5.907-41.355 41.355-88.616 82.709-129.971 129.971-5.907 5.907-5.907 5.907-5.907 11.813 0 11.813 11.813 11.813 23.631 5.907 0 0 5.907 0 5.907-5.907 41.355-41.355 88.616-88.616 129.971-129.971 5.907-5.907 5.907-5.907 5.907-5.907 17.724 0 35.448 17.724 35.448 35.448 0 5.907 0 5.907 0 5.907-47.261 47.261-100.429 100.429-147.691 147.691-5.907 5.907-5.907 5.907-5.907 11.813s5.907 11.813 5.907 11.813c5.907 0 11.813 0 11.813-5.907 5.907-5.907 5.907-5.907 11.813-11.813 35.448-35.448 70.892-70.892 106.34-106.34 11.813-11.813 23.631-23.631 29.537-29.537 0 0 5.907-5.907 5.907 0 23.631 5.907 35.448 29.537 29.537 53.168h35.448c0 0 0 0 0 0 0-5.907 0-17.724 0-23.631-5.907-29.537-23.631-47.261-53.168-59.079 0 0-5.907 0-5.907-5.907-11.813-29.537-35.448-53.168-64.985-53.168-5.907 0-5.907 0-5.907-5.907-17.724-35.448-59.079-47.261-88.616-35.448-5.907 0-11.813 5.907-11.813 5.907-5.907-5.907-11.813-11.813-23.631-17.724-29.537-11.813-59.079-5.907-76.799 11.813-11.813 11.813-17.724 17.724-29.537 29.537 17.724 23.631 23.631 29.537 29.537 41.355v0 0zM252.062 727.459c29.537-11.813 64.985-23.631 94.523-29.537 35.448-11.813 64.985-23.631 100.429-29.537 0 0 0 0 5.907 0-17.724-5.907-35.448-11.813-47.261-17.724 0 0-5.907 0-5.907 0-47.261 11.813-94.523 23.631-135.877 41.355-5.907 0-5.907 0-5.907 0l-76.799-183.139c0-11.813 5.907-17.724 11.813-23.631s5.907-5.907 5.907-11.813c-5.907-5.907-11.813-17.724-23.631-23.631-17.724 17.724-29.537 35.448-29.537 64.985l88.616 212.676c-5.907-11.813 5.907 5.907 17.724 0v0z" />
|
||||
<glyph unicode="" glyph-name="delete-circle" data-tags="delete-circle" d="M512 686.933c-147.107 0-260.266-118.817-260.266-260.266s118.817-260.266 260.266-260.266 260.266 118.817 260.266 260.266-113.158 260.266-260.266 260.266zM653.449 347.455c5.659-5.659 5.659-16.973 0-28.29l-33.949-33.949c-5.659-5.659-16.973-5.659-28.29 0l-79.212 79.212-79.212-79.212c-5.659-5.659-16.973-5.659-28.29 0l-33.949 33.949c-5.659 5.659-5.659 16.973 0 28.29l84.871 79.212-79.212 79.212c-5.659 5.659-5.659 16.973 0 28.29l33.949 33.949c5.659 5.659 16.973 5.659 28.29 0l73.554-84.871 79.212 79.212c5.659 5.659 16.973 5.659 28.29 0l33.949-33.949c5.659-5.659 5.659-16.973 0-28.29l-79.212-73.554 79.212-79.212z" />
|
||||
<glyph unicode="" glyph-name="window" data-tags="window" d="M186.821 549.319c-5.704-5.704 0-5.704 5.704-5.704l108.394-11.41c5.704 0 11.41 5.704 11.41 11.41l-17.114 102.687c0 5.704-5.704 5.704-5.704 5.704l-102.687-102.687zM255.28 612.074l-62.752 62.752c-5.704 5.704-11.41 5.704-17.114 0l-17.114-22.821c-5.704-5.704-5.704-11.41 0-17.114l62.752-62.752 34.228 39.935zM734.49 646.302c-5.704 5.704-5.704 0-5.704-5.704l-11.41-108.394c0-5.704 5.704-11.41 11.41-11.41l108.394 11.41c5.704 0 5.704 5.704 5.704 5.704l-108.394 108.394zM797.242 572.14l62.752 62.752c5.704 5.704 5.704 11.41 0 17.114l-22.821 22.821c-5.704 5.704-11.41 5.704-17.114 0l-62.752-62.752 39.935-39.935zM831.473 309.717c5.704 5.704 0 5.704-5.704 5.704l-102.687 17.114c-5.704 0-11.41-5.704-11.41-11.41l11.41-108.394c0-5.704 5.704-5.704 5.704-5.704l102.687 102.687zM763.014 246.962l62.752-62.752c5.704-5.704 11.41-5.704 17.114 0l22.821 22.821c5.704 5.704 5.704 11.41 0 17.114l-62.752 62.752-39.935-39.935zM283.804 212.734c5.704-5.704 5.704 0 5.704 5.704l11.41 108.394c0 5.704-5.704 11.41-11.41 11.41l-108.394-11.41c-5.704 0-5.704-5.704-5.704-5.704l108.394-108.394zM221.052 281.193l-62.752-62.752c-5.704-5.704-5.704-11.41 0-17.114l22.821-22.821c5.704-5.704 11.41-5.704 17.114 0l62.752 62.752-39.935 39.935zM335.149 555.026v-239.605h347.998v239.605h-347.998zM637.507 355.355h-262.424v154.032h262.424v-154.032z" />
|
||||
<glyph unicode="" glyph-name="code" data-tags="code" d="M446.523 311.303c6.235-6.235 6.235-12.472 6.235-18.707v-62.359c0-6.235-6.235-6.235-6.235-6.235l-230.728 155.897c-6.235 6.235-6.235 12.472-6.235 18.707v49.886c0 6.235 6.235 12.472 6.235 18.707l230.728 155.897c6.235 6.235 6.235 0 6.235-6.235v-62.359c0-6.235-6.235-12.472-6.235-18.707l-162.134-112.245c-6.235-6.235-6.235-6.235 0-12.472l162.134-99.776zM733.375 417.313c6.235 6.235 6.235 6.235 0 12.472l-155.897 112.245c-6.235 6.235-6.235 12.472-6.235 18.707v62.359c0 6.235 6.235 6.235 6.235 6.235l230.728-155.897c6.235-6.235 6.235-12.472 6.235-18.707v-49.886c0-6.235-6.235-12.472-6.235-18.707l-230.728-155.897c-6.235-6.235-6.235 0-6.235 6.235v62.359c0 6.235 6.235 12.472 6.235 18.707l155.897 99.776z" />
|
||||
<glyph unicode="" glyph-name="download" data-tags="download" d="M353.050 512.027c-11.773 0-17.66-5.887-5.887-17.66l153.059-188.382c5.887-11.773 23.547-11.773 29.433 0l153.059 188.382c5.887 11.773 5.887 17.66-5.887 17.66h-323.782zM570.865 500.253v135.399c0 11.773-11.773 23.547-23.547 23.547h-70.643c-11.773 0-23.547-11.773-23.547-23.547v-141.286h117.739zM647.395 364.854c-5.887 0-17.66-5.887-23.547-11.773l-76.53-94.19c-5.887-5.887-17.66-17.66-23.547-23.547 0 0-5.887-5.887-11.773-5.887s-17.66 11.773-17.66 11.773c-5.887 5.887-17.66 17.66-23.547 23.547l-76.53 94.19c-5.887 5.887-17.66 11.773-23.547 11.773h-123.626c-5.887 0-17.66-5.887-17.66-17.66v-141.286c0-5.887 5.887-17.66 17.66-17.66h529.824c5.887 0 17.66 5.887 17.66 17.66v141.286c0 5.887-5.887 17.66-17.66 17.66l-129.513-5.887zM300.067 253.004c-17.66 0-29.433 11.773-29.433 29.433s11.773 29.433 29.433 29.433c17.66 0 29.433-11.773 29.433-29.433s-11.773-29.433-29.433-29.433v0z" />
|
||||
<glyph unicode="" glyph-name="delete" data-tags="delete" d="M615.111 426.667l134.045 134.045c10.311 10.311 10.311 30.934 0 41.245l-61.866 61.866c-10.311 10.311-30.934 10.311-41.245 0l-134.045-134.045-134.045 134.045c-10.311 10.311-30.934 10.311-41.245 0l-61.866-61.866c-10.311-10.311-10.311-30.934 0-41.245l134.045-134.045-134.045-134.045c-10.311-10.311-10.311-30.934 0-41.245l61.866-61.866c10.311-10.311 30.934-10.311 41.245 0l134.045 134.045 134.045-134.045c10.311-10.311 30.934-10.311 41.245 0l61.866 61.866c10.311 10.311 10.311 30.934 0 41.245l-134.045 134.045z" />
|
||||
<glyph unicode="" glyph-name="edit-image" data-tags="edit-image" d="M300.336 749.664c69.018 23.142 133.325 14.234 189.133-33.28 56.627-48.128 77.619-110.592 63.181-183.808-2.355-12.186 0.307-19.456 8.704-27.853 93.901-93.389 156.774-156.467 250.47-250.163 5.427-5.427 10.957-10.854 15.667-16.896 39.424-50.278 16.794-124.006-44.237-142.029-36.966-10.957-68.403 0-95.334 27.034-95.642 96.051-160.973 160.973-256.614 257.024-6.963 6.963-12.8 8.909-22.63 6.758-117.76-26.317-229.171 60.826-231.731 181.453-0.614 26.419 3.584 52.326 15.974 77.926 34.816-34.816 68.506-67.789 101.274-101.786 10.445-10.752 20.992-15.36 36.045-15.36 14.643 0 25.19 3.891 34.816 14.848 10.752 12.39 23.040 23.347 34.611 35.021 14.336 14.438 14.336 46.080-0.205 60.518-35.123 35.226-70.349 70.349-106.598 106.496 3.891 2.253 5.632 3.482 7.475 4.096zM703.485 191.584c-0.41-24.269 20.685-45.466 44.851-45.158 23.757 0.41 44.032 20.992 43.93 44.544-0.102 23.757-20.275 44.032-44.237 44.134-23.859 0.307-44.237-19.661-44.544-43.52z" />
|
||||
<glyph unicode="" glyph-name="hourglass" data-tags="hourglass" d="M733.414 117.419c-147.763 0-295.526 0-443.29 0 0 2.048 0.102 4.096 0 6.144-0.307 13.824-1.024 32.666-0.922 46.49 0.41 39.731 6.861 78.131 19.046 115.2 17.203 52.224 43.725 96.256 81.306 130.355 4.506 4.096 9.216 7.885 13.722 11.776-0.205 0.717-0.307 1.126-0.41 1.229-1.331 1.229-2.765 2.355-4.198 3.584-28.058 22.63-50.688 51.405-68.403 85.606-30.618 59.085-43.52 123.597-41.165 192.614 0.205 7.168 0.614 18.33 0.922 25.498 147.763 0 295.526 0 443.29 0 0.205-1.331 0.512-2.662 0.614-3.994 2.662-36.966 1.229-77.722-5.939-113.869-14.336-72.909-44.544-133.837-95.027-179.405-4.096-3.686-8.294-7.066-12.39-10.547 0.205-0.717 0.307-1.126 0.512-1.331 0.819-0.717 1.638-1.434 2.458-2.15 42.189-33.894 71.68-79.872 90.931-135.782 11.776-34.202 18.637-69.837 20.070-106.701 0.819-19.763-0.614-44.851-1.126-64.717zM687.437 160.632c0 6.554 0.205 12.493 0 18.432-1.331 37.581-7.27 74.138-19.866 108.749-17.92 49.562-45.568 88.269-88.678 108.646-2.458 1.126-2.97 3.072-2.97 5.837 0.102 16.691 0.102 33.485 0 50.176 0 3.994 1.331 5.427 4.096 6.963 9.114 5.325 18.432 10.24 26.829 16.896 29.696 23.552 49.152 56.934 62.362 95.744 10.342 30.413 15.77 62.259 17.818 94.822 0.614 9.114 0.102 18.227 0.102 27.546-116.634 0-233.574 0-351.027 0 0.307-8.704 0.614-16.998 1.024-25.395 1.946-37.274 8.499-73.216 21.504-107.213 18.125-47.104 45.261-83.558 86.528-103.117 2.253-1.024 2.867-2.662 2.867-5.427-0.102-17.203-0.102-34.509 0-51.712 0-3.072-1.024-4.506-3.277-5.632-5.632-2.867-11.366-5.734-16.691-9.216-34.304-22.733-56.832-57.754-71.987-100.147-12.493-35.123-18.125-71.987-19.661-109.875-0.205-5.325 0-10.752 0-16.282 117.35 0.205 234.189 0.205 351.027 0.205zM410.342 579.96c68.096 0 135.373 0 203.674 0-3.789-6.554-7.168-12.595-10.752-18.227-10.957-16.998-24.269-30.618-39.731-41.472s-27.75-25.293-32.768-46.592c-1.638-6.963-2.765-14.336-2.867-21.606-0.307-17.203-0.205-34.406 0.307-51.712 0.717-28.058 12.493-48.947 32.154-62.566 43.008-30.003 65.843-75.878 75.776-132.506 0.307-1.638 0.307-3.379 0.614-5.53-83.149 0-166.093 0-249.242 0 2.662 20.685 7.885 40.346 15.462 59.085 13.312 32.973 32.768 59.187 59.494 77.722 16.589 11.469 28.365 27.955 32.358 50.995 0.819 4.813 1.331 9.83 1.434 14.746 0.102 18.637 0.614 37.274-0.205 55.808-1.126 24.678-11.981 43.213-28.57 57.139-9.216 7.782-18.944 14.746-27.648 23.142-11.981 11.162-21.197 25.395-29.491 41.574z" />
|
||||
<glyph unicode="" glyph-name="plus-icon" data-tags="plus-icon" d="M767.787 391.893c0-19.323-15.664-34.987-34.987-34.987h-151.040v-151.467c0-19.323-15.664-34.987-34.987-34.987h-69.547c-19.323 0-34.987 15.664-34.987 34.987v151.467h-151.467c-19.323 0-34.987 15.664-34.987 34.987v69.547c0 19.323 15.664 34.987 34.987 34.987h151.467v151.467c0 19.323 15.664 34.987 34.987 34.987h69.547c19.323 0 34.987-15.664 34.987-34.987v-151.467h151.467c19.323 0 34.987-15.664 34.987-34.987z" />
|
||||
<glyph unicode="" glyph-name="video-upload-icon" data-tags="video-upload-icon" d="M395.848 387.201v-128c0-21.333 21.333-42.667 42.667-42.667h128c21.333 0 42.667 17.067 42.667 42.667v128c0 21.333-17.067 42.667-42.667 42.667h-128c-21.333 0-42.667-17.067-42.667-42.667zM796.915 557.867l-102.4 106.667c-12.8 12.8-38.4 21.333-55.467 21.333h-140.8l21.333-42.667h89.6v-136.533c0-17.067 12.8-29.867 29.867-29.867h140.8v-341.333h-426.667v328.533h-42.667v-341.333c0-17.067 12.8-29.867 29.867-29.867h448c17.067 0 29.867 12.8 29.867 29.867v384c4.267 17.067-8.533 38.4-21.333 51.2zM651.848 515.201v123.733c4.267 0 12.8-4.267 12.8-8.533l102.4-102.4c4.267-4.267 4.267-8.533 8.533-12.8h-123.733zM737.181 225.067v196.267c0 4.267-4.267 8.533-8.533 8.533s-8.533 0-12.8-4.267l-89.6-89.6v-29.867l89.6-89.6c8.533-4.267 8.533 0 12.8 0 4.267 4.267 8.533 4.267 8.533 8.533zM361.715 532.267v136.533l59.733-59.733c8.533-8.533 17.067-4.267 25.6 0l12.8 12.8c8.533 8.533 8.533 17.067 0 25.6l-115.2 106.667c-8.533 8.533-17.067 8.533-21.333 0l-115.2-110.933c-4.267-8.533-4.267-17.067 0-25.6l12.8-12.8c8.533-8.533 17.067-8.533 21.333 0l59.733 59.733v-136.533c0-8.533 8.533-17.067 17.067-17.067h25.6c0 0 17.067 8.533 17.067 21.333z" />
|
||||
<glyph unicode="" glyph-name="play-icon" data-tags="play-icon" d="M375.444 661.357c81.067 46.933 187.733 46.933 273.067 0 42.667-25.6 72.533-55.467 98.133-98.133 72.533-128 29.867-294.4-98.133-371.2-128-72.533-294.4-29.867-371.2 98.133-46.933 81.067-46.933 187.733 0 273.067 21.333 42.667 55.467 72.533 98.133 98.133zM644.244 409.623c12.8 8.533 12.8 29.867 0 38.4l-192 110.933c-8.533 4.267-12.8 4.267-21.333 0s-12.8-12.8-12.8-21.333v-226.133c0-8.533 4.267-17.067 12.8-21.333s17.067-4.267 21.333 0l192 119.467z" />
|
||||
<glyph unicode="" glyph-name="copy" data-tags="copy" d="M722.133 603.867h-247.733c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v288.533c-0.267 65.867-53.467 119.2-119.333 119.2zM778.533 199.2c0-31.333-25.067-56.4-56.4-56.4h-247.867c-31.333 0-56.4 25.067-56.4 56.4v285.467c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-285.467zM245.2 368.667v288.533c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-18.8h62.667v18.8c0 65.867-53.333 119.2-119.2 119.2h-247.467c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h18.8v62.667h-18.8c-31.467-3.067-56.533 22-56.533 56.533zM681.2 403.2h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 309.067h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 214.934h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333z" />
|
||||
<glyph unicode="" glyph-name="examples-icon" data-tags="examples-icon" d="M213.333 275.201c89.6 38.4 183.467 68.267 273.067 17.067v281.6c-68.267 46.933-157.867 55.467-234.667 12.8l-38.4-311.467zM810.667 275.201l-42.667 315.733c-72.533 38.4-166.4 34.133-234.667-17.067v-285.867c93.867 51.2 187.733 21.333 277.333-12.8zM832 629.334c-51.2 29.867-110.933 46.933-170.667 55.467-51.2 0-102.4-8.533-149.333-29.867-46.933 21.333-98.133 29.867-149.333 29.867-59.733-4.267-119.467-25.6-170.667-55.467l-64-452.267c0 0 29.867-17.067 110.933 21.333 46.933 25.6 102.4 34.133 157.867 25.6 42.667-4.267 85.333-21.333 115.2-55.467v0c29.867 34.133 72.533 51.2 115.2 55.467 55.467 4.267 106.667-4.267 157.867-29.867 81.067-38.4 110.933-21.333 110.933-21.333l-64 456.533zM793.6 224.001c-42.667 21.333-89.6 34.133-140.8 34.133-8.533 0-21.333 0-29.867 0-42.667-4.267-81.067-17.067-115.2-42.667-34.133 25.6-72.533 38.4-115.2 42.667-12.8 0-21.333 0-34.133 0-46.933 0-93.867-8.533-136.533-29.867-21.333-12.8-46.933-21.333-72.533-25.6l64 413.867c46.933 25.6 98.133 38.4 149.333 42.667 46.933 0 93.867-8.533 136.533-25.6l12.8-8.533 12.8 4.267c42.667 17.067 89.6 25.6 136.533 25.6 51.2-4.267 102.4-17.067 149.333-42.667l59.733-418.133c-29.867 8.533-51.2 17.067-76.8 29.867z" />
|
||||
<glyph unicode="" glyph-name="tutorials-icon" data-tags="tutorials-icon" d="M887.468 537.598l-375.467-110.933h-4.267l-217.6 68.267c-21.333-25.6-34.133-59.733-34.133-98.133 21.333-12.8 25.6-38.4 12.8-59.733-4.267-4.267-8.533-8.533-12.8-12.8l17.067-145.067c0-4.267 0-4.267-4.267-8.533 0 0 0 0-4.267 0h-64c-4.267 0-4.267 0-8.533 4.267 0 4.267-4.267 4.267-4.267 8.533l17.067 145.067c-12.8 8.533-17.067 21.333-17.067 34.133 0 17.067 8.533 29.867 21.333 38.4 0 38.4 12.8 76.8 34.133 110.933l-106.667 29.867c-8.533 4.267-8.533 8.533-8.533 17.067 0 4.267 4.267 4.267 4.267 4.267l375.467 119.467h4.267l375.467-123.733c4.267 0 8.533-4.267 8.533-8.533s-4.267-8.533-8.533-12.8zM725.334 341.332c4.267-46.933-93.867-85.333-213.333-85.333s-213.333 38.4-213.333 85.333l4.267 106.667 192-64c4.267 0 12.8 0 17.067 0s12.8 0 17.067 4.267l192 59.733 4.267-106.667z" />
|
||||
<glyph unicode="" glyph-name="info-important-description" data-tags="info-important-description" d="M512 768.017c-188.5 0-341.3-152.8-341.3-341.3s152.8-341.4 341.3-341.4 341.3 152.8 341.3 341.3-152.8 341.4-341.3 341.4v0zM512 113.917c-172.7 0-312.7 140-312.7 312.7s140 312.7 312.7 312.7c172.7 0 312.7-140 312.7-312.7-0.2-172.6-140.1-312.5-312.7-312.7v0zM512 676.217c-137.9 0-249.6-111.8-249.6-249.6s111.7-249.6 249.6-249.6 249.6 111.8 249.6 249.6-111.8 249.6-249.6 249.6v0z" />
|
||||
<glyph unicode="" glyph-name="icon-info" data-tags="icon-info" d="M468.875 565.974h87.467c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v70.385c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-87.462c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-69.959c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.715-4.373 9.2-7.159 15.338-7.245zM599.008 263.041h-22.187v209.92c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-130.128c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h22.182v-139.947h-22.187c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h174.075c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v46.065c0.043 0.527 0.067 1.141 0.067 1.761 0 5.302-1.791 10.185-4.8 14.079-3.742 4.424-9.36 7.247-15.636 7.247-0.489 0-0.975-0.017-1.456-0.051z" />
|
||||
<glyph unicode="" glyph-name="paste" data-tags="paste" d="M394.402 745.067h-75.333c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-56.4 37.6-100.4 87.867-116v69.067c-15.733 9.467-25.067 25.067-25.067 47.067v288.4c0 31.333 25.067 56.4 56.4 56.4h131.733c0 0 0 0 3.2 3.2v0c0 31.333-28.267 59.6-59.6 59.6zM704.802 635.2c0 0-28.267 0-40.8 0-12.533 34.533-43.867 59.6-84.667 59.6s-69.067-25.067-81.6-59.6c-12.533 0-40.8 0-40.8 0-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v285.467c3.2 65.867-53.2 122.267-119.2 122.267zM582.535 647.867c22 0 40.8-18.8 40.8-40.8s-18.8-40.8-40.8-40.8c-22 0-40.8 18.8-40.8 40.8s15.733 40.8 40.8 40.8zM764.402 224.4c0-31.333-25.067-56.4-56.4-56.4h-250.933c-31.333 0-56.4 25.067-56.4 56.4v288.533c0 18.8 9.467 37.6 25.067 47.067v0c0-43.867 34.533-78.4 78.4-78.4h160c43.867 0 78.4 34.533 78.4 78.4v0c12.533-9.467 22-28.267 22-47.067v-288.533z" />
|
||||
<glyph unicode="" glyph-name="reuse" data-tags="reuse" d="M734.974 624.751c-54.605 61.619-134.123 100.573-222.977 100.573-164.936 0-298.661-133.724-298.661-298.661h74.667c0 123.721 100.272 223.993 223.993 223.993 68.214 0 128.747-30.96 169.766-79.119l-70.213-70.213h199.109v199.109l-75.689-75.689zM511.999 202.671c-68.214 0-128.747 30.96-169.766 79.119l70.213 70.213h-199.109v-199.109l75.689 75.689c54.605-61.619 134.123-100.573 222.977-100.573 164.936 0 298.661 133.724 298.661 298.661h-74.667c0-123.721-100.272-223.993-223.993-223.993z" />
|
||||
<glyph unicode="" glyph-name="info-outlined" data-tags="info-outlined" d="M467.199 181.336h89.599v268.8h-89.599v-268.8zM512 853.335c-247.296 0-448.001-200.705-448.001-448.001s200.705-448.001 448.001-448.001 448.001 200.705 448.001 448.001-200.705 448.001-448.001 448.001zM512 46.936c-197.568 0-358.398 160.83-358.398 358.398s160.83 358.398 358.398 358.398 358.398-160.83 358.398-358.398-160.83-358.398-358.398-358.398zM467.199 539.734h89.599v89.599h-89.599v-89.599z" />
|
||||
<glyph unicode="" glyph-name="spinner" data-tags="spinner" d="M600 808.001c0-48.602-39.398-88-88-88s-88 39.398-88 88 39.398 88 88 88 88-39.398 88-88zM512 133.333c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.398 88-88 88zM893.334 514.667c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.398 88-88 88zM218.666 426.667c0 48.602-39.398 88-88 88s-88-39.398-88-88 39.398-88 88-88 88 39.398 88 88zM242.357 245.024c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88c0 48.6-39.4 88-88 88zM781.643 245.024c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88c0 48.6-39.398 88-88 88zM242.357 784.31c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.4 88-88 88z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user