Compare commits
529 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de301d4237 | |||
| e2c8f4ad1b | |||
| 8c1455d220 | |||
| 7b85e141da | |||
| cd744ba504 | |||
| 974c63505b | |||
| 724210c4f7 | |||
| ae9d510288 | |||
| 981f750b81 | |||
| 721d57228f | |||
| 3c25c373d9 | |||
| 17eb41c270 | |||
| af6e65f221 | |||
| 8bbc064e29 | |||
| 78b18c00bf | |||
| 4b3b4b7d78 | |||
| 557d1c8820 | |||
| 698e1f7823 | |||
| 5b4702a6b6 | |||
| 77c0b16613 | |||
| 9adef8af21 | |||
| cc45df637f | |||
| 13655cd5ea | |||
| ea3f1d19b9 | |||
| 8256ebd431 | |||
| 8ba97d0104 | |||
| 71ee378a69 | |||
| 8208453a14 | |||
| 7c6e9b37b3 | |||
| 19dc872bca | |||
| 0a51214fb3 | |||
| 6910afa56f | |||
| 5e5e82621c | |||
| 2edff57e63 | |||
| 3b8bb4d4c1 | |||
| 920763ad00 | |||
| 1acd3cac07 | |||
| 7de4119fe2 | |||
| 6fb2fa9859 | |||
| 1d54d21ec0 | |||
| c87ed01b17 | |||
| 23541c936a | |||
| b7490a205f | |||
| 8415e0461b | |||
| 9ee41118b0 | |||
| 137b10b378 | |||
| bb9e3ec1ff | |||
| 7152b05700 | |||
| 087ccaff4f | |||
| 3fb245f481 | |||
| 8de31cf961 | |||
| 88e7da61cd | |||
| 3648b629ed | |||
| 0d833e248a | |||
| 783fba428f | |||
| 514c7ee639 | |||
| fa35b9506f | |||
| 29f7630b75 | |||
| 7a2a47d9e8 | |||
| 17211224a1 | |||
| 2394a675dd | |||
| 449c835fe2 | |||
| 322afa9f4f | |||
| 1d0777dbe4 | |||
| cd94f2bc2e | |||
| e899fd5c02 | |||
| 438b2ac8c2 | |||
| f6ae03bf41 | |||
| fc52aaf319 | |||
| ef8eb8ef1a | |||
| f1c6cf8b26 | |||
| e68cea5747 | |||
| 3f797704ea | |||
| aae57db8d2 | |||
| 8c3091e5f3 | |||
| b52ad05038 | |||
| ec5d807139 | |||
| 1f00b5d9bb | |||
| 0f53bd6b8e | |||
| bf29fea4bb | |||
| 8a733bc85f | |||
| 1b87f634a8 | |||
| 9260dad3b3 | |||
| 6002a1454f | |||
| cef3216f2a | |||
| bed6bfe7d4 | |||
| 1c39517553 | |||
| e92846ce18 | |||
| 31d0ccb078 | |||
| fc7fa7f72c | |||
| 6994f63eef | |||
| c28c0ed466 | |||
| 479b0f0eaa | |||
| 7b96e8a413 | |||
| 2321fe4da9 | |||
| 3aa05048cf | |||
| 17d6ecd26d | |||
| 7d5c5fad77 | |||
| c1296560bf | |||
| 93e25f8657 | |||
| f43de821c6 | |||
| 1b0690a7f1 | |||
| e003e8a6ff | |||
| 1e8f432437 | |||
| 51d7849f08 | |||
| ede7d22dc7 | |||
| 4355451e2f | |||
| 5e102fa8c3 | |||
| 1925b641e2 | |||
| 377da86151 | |||
| 9a2db9e2ec | |||
| 844637b880 | |||
| 9e597d3658 | |||
| 0e25936c0d | |||
| 627e14727e | |||
| ffe84e977f | |||
| 7bc67e92e2 | |||
| dd96341354 | |||
| 1714cb74b3 | |||
| c94c579aa6 | |||
| 16bcd8f057 | |||
| 5e65d02eab | |||
| 1b8bfedaa9 | |||
| 7b920d2d7b | |||
| 63b0a865dd | |||
| 7766af18a5 | |||
| 14d68d98b5 | |||
| 8676a67888 | |||
| 8af16c5709 | |||
| 7f74dc1c16 | |||
| 5bdc07a3b6 | |||
| bccfc48b94 | |||
| 95c3de0877 | |||
| d833fc2131 | |||
| 6e04f9f294 | |||
| 7f45f9ab3e | |||
| 547a6dcdde | |||
| 28fff43d2b | |||
| 7565cda9c6 | |||
| 90fa490de7 | |||
| 5f9ddb348e | |||
| ff8da7e008 | |||
| a6ab91786f | |||
| 0a43bdcc6c | |||
| b1a4bd579a | |||
| 4825c139ef | |||
| d207a2ad02 | |||
| ccc6b4d00b | |||
| cc3a909810 | |||
| c93de36436 | |||
| da3ebf216e | |||
| 7c8fd0d417 | |||
| 558ace5997 | |||
| 98254ccd1a | |||
| 5be02868af | |||
| a36b3ec48f | |||
| ea4e0ae3cb | |||
| 68c3eb8aa9 | |||
| 331b9947a3 | |||
| 4e8d58374e | |||
| 86669b0564 | |||
| e80e613f65 | |||
| 81410615ed | |||
| 1d0bd0cfdf | |||
| b4f40ae78e | |||
| 556611a864 | |||
| 7ce8e4cde2 | |||
| bde7f5d734 | |||
| e08f640061 | |||
| 4f01d454a7 | |||
| 35cc7cad05 | |||
| 7b5fa603dd | |||
| 4e72b72720 | |||
| eaf24d71f2 | |||
| ed46bff4ae | |||
| 87bcff8c6e | |||
| 81c2fe97a3 | |||
| 8b53812c8f | |||
| 78aeaf17e7 | |||
| 662d20869a | |||
| f66c4fa42d | |||
| 76a830b058 | |||
| 2f9582d754 | |||
| 12d4c82f98 | |||
| 3648ffdc8e | |||
| 1194dde152 | |||
| f727dad657 | |||
| 985d5a5824 | |||
| 0210ab0005 | |||
| 99cf35e681 | |||
| 61d0134488 | |||
| 2369ad7737 | |||
| 71dcd85182 | |||
| f5d19447e0 | |||
| b276f33850 | |||
| b932b798f1 | |||
| 14cecfa4c0 | |||
| 9582b85c16 | |||
| f920fdced0 | |||
| 53836d2035 | |||
| ff2b655928 | |||
| 706f6c93ae | |||
| 6028dc3924 | |||
| fd34a36514 | |||
| 27da8851f6 | |||
| 248ab7a4b3 | |||
| fcf07d64c8 | |||
| 897cfd7c81 | |||
| 193aa32b88 | |||
| 78908d7757 | |||
| 896c962160 | |||
| 1178406516 | |||
| 409f92b75d | |||
| 6ea0b003d7 | |||
| 918b60d700 | |||
| b997786180 | |||
| 3e8447fe9a | |||
| 28a1f0943d | |||
| 7a2d0fb5dc | |||
| 28de3d7db9 | |||
| 9fa04e3f2e | |||
| 1d41237ae8 | |||
| b0a9faf1c6 | |||
| e9961085eb | |||
| 766376261a | |||
| 8e2e459daa | |||
| fedd703ce1 | |||
| a1216eb05b | |||
| e49e58a924 | |||
| 97a03ba74d | |||
| ba43374d96 | |||
| 09d078c3b8 | |||
| a8e089a8c8 | |||
| 3e8043f3f4 | |||
| 70f695d068 | |||
| e83cad80b1 | |||
| d727c1eafd | |||
| 463b232453 | |||
| 02a8be1f80 | |||
| 7bc714ac09 | |||
| 3accc543ec | |||
| 481c57cabe | |||
| c0256a7351 | |||
| f8287cb170 | |||
| 621cd70b4c | |||
| 7e2acbfb55 | |||
| c87ccd3111 | |||
| 06be826b2e | |||
| 06a4390060 | |||
| 126cbbff7c | |||
| f252b08433 | |||
| fae048b402 | |||
| 0408008fbe | |||
| 0ff5242a3c | |||
| 2488fe37ca | |||
| 4edad07619 | |||
| 6cdcee7a65 | |||
| 28fbba8b3c | |||
| 90fde2b40d | |||
| d8bfd5b747 | |||
| 0d9c49d8d6 | |||
| 4d9ffaae34 | |||
| 21cfc35074 | |||
| 5d0b569ba1 | |||
| fd7db38d8b | |||
| 2f9f0b0fcc | |||
| 14aaba1b72 | |||
| 629657363d | |||
| 498c54d1de | |||
| a3f72b3972 | |||
| 098f09b638 | |||
| 99732c4c3b | |||
| 905d3939f5 | |||
| 051cad238b | |||
| 6a4bf785cc | |||
| 223f7cc0b4 | |||
| 287ed73429 | |||
| db1368926a | |||
| ec19837b79 | |||
| 3b0d958065 | |||
| 6722a6b105 | |||
| 920b4dd6ab | |||
| 898bdfdd67 | |||
| ed7485277e | |||
| eb238722da | |||
| da1f204a0a | |||
| 5dd385d522 | |||
| dca6513b8a | |||
| 6484e89188 | |||
| 9312e7389a | |||
| 0218859c3e | |||
| 18702b5869 | |||
| d0ab1d1540 | |||
| 7d25143979 | |||
| a067cc5ada | |||
| ea68407895 | |||
| 03f5e32712 | |||
| c0b0649582 | |||
| 72cd24d3c9 | |||
| d16aa74359 | |||
| 7e52e95d8d | |||
| 42f3862b91 | |||
| 556648db0b | |||
| 6026aa7b3c | |||
| 8e5782b848 | |||
| f38e17f16f | |||
| 465cc9e91a | |||
| faa76ee29a | |||
| d6491a67c8 | |||
| 27db5c5024 | |||
| e6b274a542 | |||
| 6f317a983e | |||
| a5293c4ed8 | |||
| 66e77f8173 | |||
| 4ba58ac02f | |||
| 21f5c148fc | |||
| 2e026689c6 | |||
| 3a718d620e | |||
| beff06190a | |||
| 975081c367 | |||
| 9417cedd78 | |||
| 19343c7bd1 | |||
| 64dd473559 | |||
| 9e4a19af6c | |||
| eeba9c1dc5 | |||
| 55a465c445 | |||
| d1d52a3399 | |||
| ad7b38f3ff | |||
| a22b74872c | |||
| 1af998d32c | |||
| 4d9ab4ca48 | |||
| 55189f02ee | |||
| 115f703440 | |||
| 5cd7b5cad0 | |||
| fb81664dc3 | |||
| 8cbc470f2a | |||
| 1bc92d37f2 | |||
| 348e338a21 | |||
| bf854f5f24 | |||
| b861165753 | |||
| 3c2208cc24 | |||
| 2d1fcf6cce | |||
| 2f502066cb | |||
| 41cd0c4d3a | |||
| 0efc7095e3 | |||
| 6178388d94 | |||
| f0e9fa2a7b | |||
| c651779095 | |||
| ce5efbc1da | |||
| e9506c638b | |||
| bb3f9ee492 | |||
| 14017f40b9 | |||
| 413736095b | |||
| f78101f638 | |||
| 105e40d377 | |||
| ce2f6179cd | |||
| 47ff67ec29 | |||
| 3fb1660734 | |||
| 6e7c392d84 | |||
| 806e6bec59 | |||
| 8d7fe960a0 | |||
| 8d1cbe3a35 | |||
| 197ec7e822 | |||
| fbab65f86b | |||
| cd054d77f1 | |||
| 6168569323 | |||
| 5da2fa002e | |||
| 4b137eb20c | |||
| 34ffdc2c81 | |||
| 83d478ce93 | |||
| 565bc965b1 | |||
| e2cdcc563d | |||
| ae61985191 | |||
| 2b169e9b31 | |||
| ccbd35915c | |||
| 114d1a051e | |||
| 856c31cc32 | |||
| 25049f4f19 | |||
| 00a46671fa | |||
| d008aa7c99 | |||
| 2d462ee8bd | |||
| 3f50f83dc1 | |||
| 41121d705f | |||
| cc2faa2c90 | |||
| ae4f9b31f7 | |||
| 01f3495a46 | |||
| c4c328565d | |||
| 7961a36b76 | |||
| eda7eb44b3 | |||
| 6589c3cdd6 | |||
| eeb76ddd3f | |||
| 14ea69c07c | |||
| 9f0400b74d | |||
| f68a64d50b | |||
| d82acd309b | |||
| 22f34e807d | |||
| 31d3a656d6 | |||
| 3d41104fab | |||
| 523f308a9a | |||
| e4d03a9257 | |||
| cedf141cc3 | |||
| b4d51d67a9 | |||
| 9763c99f87 | |||
| 56be61d4f6 | |||
| 6de6fe9590 | |||
| 754440d796 | |||
| 1ddaad70ad | |||
| c954e18a99 | |||
| e0a366fa0a | |||
| 2e5daf9108 | |||
| 5281e10e77 | |||
| aae7a2b206 | |||
| 419ff44b78 | |||
| c4a8787574 | |||
| 834a1d64c5 | |||
| 496e6ac804 | |||
| 7eb0f97113 | |||
| 9f1df0f2e4 | |||
| 8fc60dd25d | |||
| 558a30dc7d | |||
| 9773b4b079 | |||
| d686ee8faa | |||
| 5cb1cf7695 | |||
| 465f3b477b | |||
| ad00d91851 | |||
| 1eed96dec7 | |||
| 2e21c78006 | |||
| aba9644393 | |||
| 4aa6b89ead | |||
| f8bce36ef2 | |||
| fce2b0008c | |||
| c2bd0a7d31 | |||
| 2d7a59d060 | |||
| 57e0a20dec | |||
| 3fe455fab4 | |||
| ca743b6a95 | |||
| 5ea37637b7 | |||
| 75bae1a55b | |||
| f3db3d30e0 | |||
| 7714ea42ec | |||
| 12c103b21a | |||
| ec157a3052 | |||
| 57bb2fdaac | |||
| e437183098 | |||
| 3af8f89e58 | |||
| 52b1184468 | |||
| 99d0308e4b | |||
| 3ae46b2a93 | |||
| 5fce0b3f5d | |||
| b3ebbb61f4 | |||
| 16689245e8 | |||
| 98280a4c8c | |||
| 171e55f304 | |||
| f1fed72bda | |||
| 07674eb12f | |||
| 2308a02324 | |||
| 1da6703d6d | |||
| 27a62292ad | |||
| 271b94ff4f | |||
| 315ec3bad6 | |||
| 4cb342ef34 | |||
| e7bb7aa9a2 | |||
| 7aca3ebb3c | |||
| 61bb2c70c2 | |||
| d84da184c5 | |||
| f612465b53 | |||
| 4727970abd | |||
| 88c700bd2c | |||
| b354694012 | |||
| a9bccc9dbc | |||
| 7c52a27375 | |||
| 300d9ca23d | |||
| 00b2219fd4 | |||
| 8b9d73dad8 | |||
| 9029eb0e71 | |||
| d54b3447e6 | |||
| 39d542eeb4 | |||
| 2cf6b7cc41 | |||
| 5651e88dc9 | |||
| 5a90580646 | |||
| c98428aab3 | |||
| dc780e3eb1 | |||
| d75844ea12 | |||
| 11e5003bd6 | |||
| a348c5f420 | |||
| 4b651226d5 | |||
| e33c554aaa | |||
| 31937c4b0e | |||
| 141216f94c | |||
| fba96f096b | |||
| 7148c9d35e | |||
| 8193ba9da9 | |||
| 6bf314500a | |||
| ee034c39e5 | |||
| 46aea33b69 | |||
| e0f9ca6787 | |||
| 976f8f0c52 | |||
| 767be5d8c1 | |||
| 94140b11e8 | |||
| a45e6838ef | |||
| ae17678a1c | |||
| 3ae39301b9 | |||
| 422fcb70ca | |||
| 29a396fe4e | |||
| af777f92a0 | |||
| 6f35d6536d | |||
| 36cde1e295 | |||
| 05a1532e88 | |||
| 91ae4ab5db | |||
| 2cc3588b77 | |||
| 222aefb456 | |||
| 94160c9ad0 | |||
| 9c7ab777d9 | |||
| 7818990010 | |||
| 17a035ac59 | |||
| abe11a9ace | |||
| bfab0d603c | |||
| 070454489f | |||
| e32a114998 | |||
| 3db28cdebd | |||
| 51b0a67bd2 | |||
| 7156a01a0c | |||
| db22846df6 | |||
| ef6e3bfd77 | |||
| 36ee886a12 | |||
| 97e0452344 | |||
| 874bdf2035 | |||
| 6c39b7890e | |||
| 3e9dadc85e |
@@ -0,0 +1,71 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Template configuraton file for github actions CI/CD.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2020 onwards Eloy Lafuente (stronk7) {@link https://stronk7.com}
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// This cannot be used out from a github actions workflow, so just exit.
|
||||
getenv('GITHUB_WORKFLOW') || die; // phpcs:ignore moodle.Files.MoodleInternal.MoodleInternalGlobalState
|
||||
|
||||
unset($CFG);
|
||||
global $CFG;
|
||||
$CFG = new stdClass();
|
||||
|
||||
$CFG->dbtype = getenv('dbtype');
|
||||
$CFG->dblibrary = 'native';
|
||||
$CFG->dbhost = '127.0.0.1';
|
||||
$CFG->dbname = 'test';
|
||||
$CFG->dbuser = 'test';
|
||||
$CFG->dbpass = 'test';
|
||||
$CFG->prefix = 'm_';
|
||||
$CFG->dboptions = ['dbcollation' => 'utf8mb4_bin'];
|
||||
|
||||
$host = 'localhost';
|
||||
$CFG->wwwroot = "http://{$host}";
|
||||
$CFG->dataroot = realpath(dirname(__DIR__)) . '/moodledata';
|
||||
$CFG->admin = 'admin';
|
||||
$CFG->directorypermissions = 0777;
|
||||
|
||||
// Debug options - possible to be controlled by flag in future.
|
||||
$CFG->debug = (E_ALL | E_STRICT); // DEBUG_DEVELOPER.
|
||||
$CFG->debugdisplay = 1;
|
||||
$CFG->debugstringids = 1; // Add strings=1 to url to get string ids.
|
||||
$CFG->perfdebug = 15;
|
||||
$CFG->debugpageinfo = 1;
|
||||
$CFG->allowthemechangeonurl = 1;
|
||||
$CFG->passwordpolicy = 0;
|
||||
$CFG->cronclionly = 0;
|
||||
$CFG->pathtophp = getenv('pathtophp');
|
||||
|
||||
$CFG->phpunit_dataroot = realpath(dirname(__DIR__)) . '/phpunitdata';
|
||||
$CFG->phpunit_prefix = 't_';
|
||||
|
||||
define('TEST_EXTERNAL_FILES_HTTP_URL', 'http://localhost:8080');
|
||||
define('TEST_EXTERNAL_FILES_HTTPS_URL', 'http://localhost:8080');
|
||||
|
||||
define('TEST_SESSION_REDIS_HOST', 'localhost');
|
||||
define('TEST_CACHESTORE_REDIS_TESTSERVERS', 'localhost');
|
||||
|
||||
// TODO: add others (solr, mongodb, memcached, ldap...).
|
||||
|
||||
// Too much for now: define('PHPUNIT_LONGTEST', true); // Only leaves a few tests out and they are run later by CI.
|
||||
|
||||
require_once(__DIR__ . '/lib/setup.php');
|
||||
@@ -0,0 +1,103 @@
|
||||
name: Core
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
php: 7.4
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Configuring node & npm
|
||||
shell: bash -l {0}
|
||||
run: nvm install
|
||||
|
||||
- name: Installing node stuff
|
||||
run: npm install
|
||||
|
||||
- name: Running grunt
|
||||
run: npx grunt
|
||||
|
||||
- name: Looking for uncommitted changes
|
||||
# Add all files to the git index and then run diff --cached to see all changes.
|
||||
# This ensures that we get the status of all files, including new files.
|
||||
# We ignore npm-shrinkwrap.json to make the tasks immune to npm changes.
|
||||
run: |
|
||||
git add .
|
||||
git reset -- npm-shrinkwrap.json
|
||||
git diff --cached --exit-code
|
||||
|
||||
PHPUnit:
|
||||
runs-on: ${{ matrix.os }}
|
||||
services:
|
||||
exttests:
|
||||
image: moodlehq/moodle-exttests
|
||||
ports:
|
||||
- 8080:80
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-18.04
|
||||
php: 7.2
|
||||
db: mysqli
|
||||
- os: ubuntu-18.04
|
||||
php: 7.4
|
||||
db: pgsql
|
||||
|
||||
steps:
|
||||
- name: Setting up DB mysql
|
||||
if: ${{ matrix.db == 'mysqli' }}
|
||||
uses: johanmeiring/mysql-action@tmpfs-patch
|
||||
with:
|
||||
collation server: utf8mb4_bin
|
||||
mysql version: 5.7
|
||||
mysql database: test
|
||||
mysql user: test
|
||||
mysql password: test
|
||||
use tmpfs: true
|
||||
|
||||
- name: Setting up DB pgsql
|
||||
if: ${{ matrix.db == 'pgsql' }}
|
||||
uses: m4nu56/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: 9.6
|
||||
postgresql db: test
|
||||
postgresql user: test
|
||||
postgresql password: test
|
||||
|
||||
- name: Configuring git vars
|
||||
uses: rlespinasse/github-slug-action@v3.x
|
||||
|
||||
- name: Setting up PHP ${{ matrix.php }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
dbtype: ${{ matrix.db }}
|
||||
run: |
|
||||
echo "pathtophp=$(which php)" >> $GITHUB_ENV # Inject installed pathtophp to env. The template config needs it.
|
||||
cp .github/workflows/config-template.php config.php
|
||||
mkdir ../moodledata
|
||||
sudo locale-gen en_AU.UTF-8
|
||||
php admin/tool/phpunit/cli/init.php --no-composer-self-update
|
||||
|
||||
- name: Running PHPUnit tests
|
||||
env:
|
||||
dbtype: ${{ matrix.db }}
|
||||
run: vendor/bin/phpunit -v
|
||||
+31
-74
@@ -2,10 +2,9 @@
|
||||
# process (which uses our internal CI system) this file is here for the benefit
|
||||
# of community developers git clones - see MDL-51458.
|
||||
|
||||
# We currently disable Travis notifications entirely until https://github.com/travis-ci/travis-ci/issues/4976
|
||||
# is fixed.
|
||||
notifications:
|
||||
email: false
|
||||
email:
|
||||
if: env(MOODLE_EMAIL) != no
|
||||
|
||||
language: php
|
||||
|
||||
@@ -17,31 +16,9 @@ services:
|
||||
- mysql
|
||||
- docker
|
||||
|
||||
php:
|
||||
# We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
|
||||
- 7.4
|
||||
- 7.2
|
||||
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
|
||||
env:
|
||||
# Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
|
||||
# start first so that the total run time is not too high.
|
||||
#
|
||||
# We only run MySQL on PHP 7.2, so run that first.
|
||||
# CI Tests should be second-highest in priority as these only take <= 60 seconds to run under normal circumstances.
|
||||
# Postgres is significantly is pretty reasonable in its run-time.
|
||||
|
||||
# Run CI Tests without running PHPUnit.
|
||||
- DB=none TASK=CITEST
|
||||
|
||||
# Run unit tests on Postgres
|
||||
- DB=pgsql TASK=PHPUNIT
|
||||
|
||||
# Perform an upgrade test too.
|
||||
- DB=pgsql TASK=UPGRADE
|
||||
|
||||
jobs:
|
||||
# Enable fast finish.
|
||||
# This will fail the build if a single job fails (except those in allow_failures).
|
||||
@@ -49,12 +26,35 @@ jobs:
|
||||
fast_finish: true
|
||||
|
||||
include:
|
||||
# Run mysql only on highest - it's just too slow
|
||||
- php: 7.4
|
||||
# First all the lowest php ones (7.2)
|
||||
- php: 7.2
|
||||
env: DB=none TASK=CITEST
|
||||
- php: 7.2
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
|
||||
|
||||
- if: env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present
|
||||
php: 7.2
|
||||
env: DB=pgsql TASK=PHPUNIT
|
||||
|
||||
- if: env(MOODLE_DATABASE) = "mysqli" OR env(MOODLE_DATABASE) = "all"
|
||||
php: 7.2
|
||||
env: DB=mysqli TASK=PHPUNIT
|
||||
|
||||
# Then, conditionally, all the highest php ones (7.4)
|
||||
- if: env(MOODLE_PHP) = "all"
|
||||
php: 7.4
|
||||
env: DB=none TASK=CITEST
|
||||
- if: env(MOODLE_PHP) = "all"
|
||||
php: 7.4
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
|
||||
|
||||
- if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present)
|
||||
php: 7.4
|
||||
env: DB=pgsql TASK=PHPUNIT
|
||||
|
||||
- if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "mysqli" OR env(MOODLE_DATABASE) = "all")
|
||||
php: 7.4
|
||||
env: DB=mysqli TASK=PHPUNIT
|
||||
# Run grunt/npm install on highest version too ('node' is an alias for the latest node.js version.)
|
||||
- php: 7.4
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
|
||||
|
||||
cache:
|
||||
directories:
|
||||
@@ -114,7 +114,7 @@ install:
|
||||
before_script:
|
||||
- phpenv config-rm xdebug.ini
|
||||
- >
|
||||
if [ "$TASK" = 'PHPUNIT' -o "$TASK" = 'UPGRADE' ];
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
then
|
||||
# Copy generic configuration in place.
|
||||
cp config-dist.php config.php ;
|
||||
@@ -225,32 +225,6 @@ before_script:
|
||||
export phpcmd=`which php`;
|
||||
fi
|
||||
|
||||
########################################################################
|
||||
# Upgrade test
|
||||
########################################################################
|
||||
- >
|
||||
if [ "$TASK" = 'UPGRADE' ];
|
||||
then
|
||||
# We need the official upstream.
|
||||
git remote add upstream https://github.com/moodle/moodle.git;
|
||||
|
||||
# Checkout 30 STABLE branch (the first version compatible with PHP 7.x)
|
||||
git fetch upstream MOODLE_30_STABLE;
|
||||
git checkout MOODLE_30_STABLE;
|
||||
|
||||
# Perform the upgrade
|
||||
php admin/cli/install_database.php --agree-license --adminpass=Password --adminemail=admin@example.com --fullname="Upgrade test" --shortname=Upgrade;
|
||||
|
||||
# Return to the previous commit
|
||||
git checkout -;
|
||||
|
||||
# Perform the upgrade
|
||||
php admin/cli/upgrade.php --non-interactive --allow-unstable ;
|
||||
|
||||
# The local_ci repository can be used to check upgrade savepoints.
|
||||
git clone https://github.com/moodlehq/moodle-local_ci.git local/ci ;
|
||||
fi
|
||||
|
||||
script:
|
||||
- >
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
@@ -276,23 +250,6 @@ script:
|
||||
git diff --cached --exit-code ;
|
||||
fi
|
||||
|
||||
########################################################################
|
||||
# Upgrade test
|
||||
########################################################################
|
||||
- >
|
||||
if [ "$TASK" = 'UPGRADE' ];
|
||||
then
|
||||
cp local/ci/check_upgrade_savepoints/check_upgrade_savepoints.php ./check_upgrade_savepoints.php
|
||||
result=`php check_upgrade_savepoints.php`;
|
||||
# Check if there are problems
|
||||
count=`echo "$result" | grep -P "ERROR|WARN" | wc -l` ;
|
||||
if (($count > 0));
|
||||
then
|
||||
echo "$result"
|
||||
exit 1 ;
|
||||
fi
|
||||
fi
|
||||
|
||||
after_script:
|
||||
- >
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
|
||||
@@ -178,6 +178,7 @@ class filesize extends \admin_setting {
|
||||
'id' => $this->get_id(),
|
||||
'name' => $this->get_full_name(),
|
||||
'value' => $data['v'],
|
||||
'readonly' => $this->is_readonly(),
|
||||
'options' => array_map(function($unit, $title) use ($data, $defaultunit) {
|
||||
return [
|
||||
'value' => $unit,
|
||||
|
||||
@@ -215,6 +215,7 @@ define('SITEID', 1);
|
||||
|
||||
//Database types
|
||||
$databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'native'),
|
||||
'auroramysql' => moodle_database::get_driver_instance('auroramysql', 'native'),
|
||||
'mariadb'=> moodle_database::get_driver_instance('mariadb', 'native'),
|
||||
'pgsql' => moodle_database::get_driver_instance('pgsql', 'native'),
|
||||
'oci' => moodle_database::get_driver_instance('oci', 'native'),
|
||||
|
||||
@@ -34,6 +34,7 @@ $help = "Command line tool to uninstall plugins.
|
||||
Options:
|
||||
-h --help Print this help.
|
||||
--show-all Displays a list of all installed plugins.
|
||||
--show-contrib Displays a list of all third-party installed plugins.
|
||||
--show-missing Displays a list of plugins missing from disk.
|
||||
--purge-missing Uninstall all missing from disk plugins.
|
||||
--plugins=<plugin name> A comma separated list of plugins to be uninstalled. E.g. mod_assign,mod_forum
|
||||
@@ -44,6 +45,9 @@ Examples:
|
||||
# php uninstall_plugins.php --show-all
|
||||
Prints tab-separated list of all installed plugins.
|
||||
|
||||
# php uninstall_plugins.php --show-contrib
|
||||
Prints tab-separated list of all third-party installed plugins.
|
||||
|
||||
# php uninstall_plugins.php --show-missing
|
||||
Prints tab-separated list of all missing from disk plugins.
|
||||
|
||||
@@ -63,6 +67,7 @@ Examples:
|
||||
list($options, $unrecognised) = cli_get_params([
|
||||
'help' => false,
|
||||
'show-all' => false,
|
||||
'show-contrib' => false,
|
||||
'show-missing' => false,
|
||||
'purge-missing' => false,
|
||||
'plugins' => false,
|
||||
@@ -84,12 +89,15 @@ if ($options['help']) {
|
||||
$pluginman = core_plugin_manager::instance();
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
|
||||
if ($options['show-all'] || $options['show-missing']) {
|
||||
if ($options['show-all'] || $options['show-missing'] || $options['show-contrib']) {
|
||||
foreach ($plugininfo as $type => $plugins) {
|
||||
foreach ($plugins as $name => $plugin) {
|
||||
if ($options['show-contrib'] && $plugin->is_standard()) {
|
||||
continue;
|
||||
}
|
||||
$pluginstring = $plugin->component . "\t" . $plugin->displayname;
|
||||
|
||||
if ($options['show-all']) {
|
||||
if ($options['show-all'] || $options['show-contrib']) {
|
||||
cli_writeln($pluginstring);
|
||||
} else {
|
||||
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
|
||||
|
||||
@@ -3344,4 +3344,186 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.11" 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="9.6" />
|
||||
<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="required"/>
|
||||
</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_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
+4
-2
@@ -49,6 +49,7 @@
|
||||
array($module->id));
|
||||
core_plugin_manager::reset_caches();
|
||||
admin_get_root(true, false); // settings not required - only pages
|
||||
redirect(new moodle_url('/admin/modules.php'));
|
||||
}
|
||||
|
||||
if (!empty($show) and confirm_sesskey()) {
|
||||
@@ -66,6 +67,7 @@
|
||||
array($module->id));
|
||||
core_plugin_manager::reset_caches();
|
||||
admin_get_root(true, false); // settings not required - only pages
|
||||
redirect(new moodle_url('/admin/modules.php'));
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
@@ -121,8 +123,8 @@
|
||||
$count = -1;
|
||||
}
|
||||
if ($count>0) {
|
||||
$countlink = "<a href=\"{$CFG->wwwroot}/course/search.php?modulelist=$module->name" .
|
||||
"&sesskey=".sesskey()."\" title=\"$strshowmodulecourse\">$count</a>";
|
||||
$countlink = $OUTPUT->action_link(new moodle_url('/course/search.php', ['modulelist' => $module->name]),
|
||||
$count, null, ['title' => $strshowmodulecourse]);
|
||||
} else if ($count < 0) {
|
||||
$countlink = get_string('error');
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -53,7 +53,6 @@ $pageurl = new moodle_url('/admin/plugins.php', $pageparams);
|
||||
$pluginman = core_plugin_manager::instance();
|
||||
|
||||
if ($uninstall) {
|
||||
require_sesskey();
|
||||
|
||||
if (!$confirmed) {
|
||||
admin_externalpage_setup('pluginsoverview', '', $pageparams);
|
||||
@@ -92,6 +91,7 @@ if ($uninstall) {
|
||||
exit();
|
||||
|
||||
} else {
|
||||
require_sesskey();
|
||||
$SESSION->pluginuninstallreturn = $pluginfo->get_return_url_after_uninstall($return);
|
||||
$progress = new progress_trace_buffer(new text_progress_trace(), false);
|
||||
$pluginman->uninstall_plugin($pluginfo->component, $progress);
|
||||
|
||||
@@ -131,15 +131,10 @@ if ($hassiteconfig && \core_analytics\manager::is_analytics_enabled()) {
|
||||
$timesplittingdefaults, $timesplittingoptions)
|
||||
);
|
||||
|
||||
// Predictions processor output dir.
|
||||
$defaultmodeloutputdir = rtrim($CFG->dataroot, '/') . DIRECTORY_SEPARATOR . 'models';
|
||||
if (empty(get_config('analytics', 'modeloutputdir')) && !file_exists($defaultmodeloutputdir) &&
|
||||
is_writable($defaultmodeloutputdir)) {
|
||||
// Automatically create the dir for them so users don't see the invalid value red cross.
|
||||
mkdir($defaultmodeloutputdir, $CFG->directorypermissions, true);
|
||||
}
|
||||
// Predictions processor output dir - specify default in setting description (used if left blank).
|
||||
$defaultmodeloutputdir = \core_analytics\model::default_output_dir();
|
||||
$settings->add(new admin_setting_configdirectory('analytics/modeloutputdir', new lang_string('modeloutputdir', 'analytics'),
|
||||
new lang_string('modeloutputdirinfo', 'analytics'), $defaultmodeloutputdir));
|
||||
new lang_string('modeloutputdirwithdefaultinfo', 'analytics', $defaultmodeloutputdir), ''));
|
||||
|
||||
// Disable web interface evaluation and get predictions.
|
||||
$settings->add(new admin_setting_configcheckbox('analytics/onlycli', new lang_string('onlycli', 'analytics'),
|
||||
|
||||
+50
-14
@@ -1,21 +1,57 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
/**
|
||||
* Define administration settings on the Location settings page.
|
||||
*
|
||||
* @package core
|
||||
* @category admin
|
||||
* @copyright 2006 Martin Dougiamas <martin@moodle.com>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// "locations" settingpage
|
||||
$temp = new admin_settingpage('locationsettings', new lang_string('locationsettings', 'admin'));
|
||||
$temp->add(new admin_setting_servertimezone());
|
||||
$temp->add(new admin_setting_forcetimezone());
|
||||
$temp->add(new admin_settings_country_select('country', new lang_string('country', 'admin'), new lang_string('configcountry', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configtext('defaultcity', new lang_string('defaultcity', 'admin'), new lang_string('defaultcity_help', 'admin'), ''));
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$temp->add(new admin_setting_heading('iplookup', new lang_string('iplookup', 'admin'), new lang_string('iplookupinfo', 'admin')));
|
||||
$temp->add(new admin_setting_configfile('geoip2file', new lang_string('geoipfile', 'admin'),
|
||||
new lang_string('configgeoipfile', 'admin', $CFG->dataroot.'/geoip/'), $CFG->dataroot.'/geoip/GeoLite2-City.mmdb'));
|
||||
$temp->add(new admin_setting_configtext('googlemapkey3', new lang_string('googlemapkey3', 'admin'), new lang_string('googlemapkey3_help', 'admin'), '', PARAM_RAW, 60));
|
||||
if ($hassiteconfig) {
|
||||
$temp = new admin_settingpage('locationsettings', new lang_string('locationsettings', 'core_admin'));
|
||||
|
||||
$temp->add(new admin_setting_configtext('allcountrycodes', new lang_string('allcountrycodes', 'admin'), new lang_string('configallcountrycodes', 'admin'), '', '/^(?:\w+(?:,\w+)*)?$/'));
|
||||
if ($ADMIN->fulltree) {
|
||||
$temp->add(new admin_setting_servertimezone());
|
||||
|
||||
$temp->add(new admin_setting_forcetimezone());
|
||||
|
||||
$temp->add(new admin_settings_country_select('country', new lang_string('country', 'core_admin'),
|
||||
new lang_string('configcountry', 'core_admin'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configtext('defaultcity', new lang_string('defaultcity', 'core_admin'),
|
||||
new lang_string('defaultcity_help', 'core_admin'), ''));
|
||||
|
||||
$temp->add(new admin_setting_heading('iplookup', new lang_string('iplookup', 'core_admin'),
|
||||
new lang_string('iplookupinfo', 'core_admin')));
|
||||
|
||||
$temp->add(new admin_setting_configfile('geoip2file', new lang_string('geoipfile', 'core_admin'),
|
||||
new lang_string('configgeoipfile', 'core_admin', $CFG->dataroot . '/geoip/'),
|
||||
$CFG->dataroot . '/geoip/GeoLite2-City.mmdb'));
|
||||
|
||||
$temp->add(new admin_setting_configtext('googlemapkey3', new lang_string('googlemapkey3', 'core_admin'),
|
||||
new lang_string('googlemapkey3_help', 'core_admin'), '', PARAM_RAW, 60));
|
||||
|
||||
$temp->add(new admin_setting_countrycodes('allcountrycodes', new lang_string('allcountrycodes', 'core_admin'),
|
||||
new lang_string('configallcountrycodes', 'core_admin')));
|
||||
}
|
||||
|
||||
$ADMIN->add('location', $temp);
|
||||
|
||||
} // end of speedup
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ $result = optional_param('result', null, PARAM_INT);
|
||||
|
||||
$pageurl = new \moodle_url('/admin/tasklogs.php');
|
||||
$pageurl->param('filter', $filter);
|
||||
$pageurl->param('result', $result);
|
||||
|
||||
$PAGE->set_url($pageurl);
|
||||
$PAGE->set_context(context_system::instance());
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* value - yes value
|
||||
* id - element id
|
||||
* checked - boole
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
@@ -32,10 +33,11 @@
|
||||
"no": "False",
|
||||
"value": "True",
|
||||
"id": "test0",
|
||||
"checked": "checked"
|
||||
"checked": "checked",
|
||||
"readonly": false
|
||||
}
|
||||
}}
|
||||
<div class="form-checkbox defaultsnext">
|
||||
<input type="hidden" name="{{name}}" value="{{no}}">
|
||||
<input type="checkbox" name="{{name}}" value="{{value}}" id="{{id}}" {{#checked}}checked{{/checked}}>
|
||||
<input {{#readonly}}disabled{{/readonly}} type="checkbox" name="{{name}}" value="{{value}}" id="{{id}}" {{#checked}}checked{{/checked}}>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* id - element id
|
||||
* value - element value
|
||||
* haspreviewconfig - show preview of selected color
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
@@ -32,6 +33,7 @@
|
||||
"name": "name0",
|
||||
"id": "id0",
|
||||
"value": "#555655",
|
||||
"readonly": false,
|
||||
"haspreviewconfig": false
|
||||
}
|
||||
}}
|
||||
@@ -44,7 +46,7 @@
|
||||
{{>core/pix_icon}}
|
||||
{{/icon}}
|
||||
</div>
|
||||
<input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="form-control text-ltr">
|
||||
<input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
|
||||
{{#haspreviewconfig}}
|
||||
<input type="button" id="{{id}}_preview" value={{#quote}}{{#str}}preview{{/str}}{{/quote}} class="admin_colourpicker_preview">
|
||||
{{/haspreviewconfig}}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"name": "test",
|
||||
"value": "/my-super-secret-path/",
|
||||
"id": "test0",
|
||||
"readonly": true,
|
||||
"readonly": false,
|
||||
"showvalidity": true,
|
||||
"valid": false
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
* name - form element name
|
||||
* options - list of options for units containing name, value, selected
|
||||
* value - yes
|
||||
* readonly - bool
|
||||
* id - element id
|
||||
|
||||
Example context (json):
|
||||
@@ -30,6 +31,7 @@
|
||||
"name": "test",
|
||||
"value": "5",
|
||||
"id": "test0",
|
||||
"readonly": false,
|
||||
"options": [ { "name": "Minutes", "value": "mins", "selected": true } ]
|
||||
}
|
||||
}}
|
||||
@@ -38,9 +40,9 @@
|
||||
}}
|
||||
<div class="form-duration defaultsnext">
|
||||
<div class="form-inline">
|
||||
<input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
|
||||
<input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
|
||||
<label class="sr-only" for="{{id}}u">{{#str}}durationunits, admin{{/str}}</label>
|
||||
<select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select">
|
||||
<select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select" {{#readonly}}disabled{{/readonly}}>
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/options}}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"name": "test",
|
||||
"value": "/usr/bin/cowsay",
|
||||
"id": "test0",
|
||||
"readonly": true,
|
||||
"readonly": false,
|
||||
"showvalidity": true,
|
||||
"valid": false
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
* readonly - Make the field readonly
|
||||
* value - value
|
||||
* showvalidity - Show a green check if the path is readable
|
||||
* readonly - bool
|
||||
* valid - True if the path is readable
|
||||
|
||||
Example context (json):
|
||||
@@ -33,8 +34,9 @@
|
||||
"name": "test",
|
||||
"value": "/my-super-secret-path/file",
|
||||
"id": "test0",
|
||||
"readonly": true,
|
||||
"readonly": false,
|
||||
"showvalidity": true,
|
||||
"readonly": false,
|
||||
"valid": false
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -24,12 +24,14 @@
|
||||
* options - list of options for units containing name, value, selected
|
||||
* value - yes
|
||||
* id - element id
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "test",
|
||||
"value": "5",
|
||||
"id": "test0",
|
||||
"readonly": false,
|
||||
"options": [ { "name": "KB", "value": "1024", "selected": true } ]
|
||||
}
|
||||
}}
|
||||
@@ -38,9 +40,9 @@
|
||||
}}
|
||||
<div class="form-filesize defaultsnext">
|
||||
<div class="form-inline">
|
||||
<input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
|
||||
<input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
|
||||
<label class="sr-only" for="{{id}}u">{{#str}}filesizeunits, admin{{/str}}</label>
|
||||
<select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select">
|
||||
<select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select" {{#readonly}}disabled{{/readonly}}>
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/options}}
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
* id - element id
|
||||
* size - element size
|
||||
* options - list of options containing name, value, selected
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "test",
|
||||
"id": "test0",
|
||||
"readonly": false,
|
||||
"size": "3",
|
||||
"options": [ { "name": "Option 1", "value": "V", "selected": true },
|
||||
{ "name": "Option 2", "value": "V", "selected": true } ]
|
||||
@@ -39,7 +41,7 @@
|
||||
}}
|
||||
<div class="form-select">
|
||||
<input type="hidden" name="{{name}}[xxxxx]" value="1">
|
||||
<select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
|
||||
<select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/options}}
|
||||
|
||||
@@ -25,11 +25,13 @@
|
||||
* size - element size
|
||||
* options - list of options not grouped
|
||||
* optgroups - list of options grouped containing the group label and for each option: name, value, selected
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "test",
|
||||
"id": "test0",
|
||||
"readonly": false,
|
||||
"size": "3",
|
||||
"options": [
|
||||
{ "name": "Option 1", "value": "V", "selected": false },
|
||||
@@ -58,7 +60,7 @@
|
||||
}}
|
||||
<div class="form-select">
|
||||
<input type="hidden" name="{{name}}[xxxxx]" value="1">
|
||||
<select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
|
||||
<select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/options}}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
* size - form element size
|
||||
* value - form element value
|
||||
* id - element id
|
||||
* forced - has value been defined in config.php
|
||||
* readonly - has value been defined in config.php
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
@@ -32,10 +32,10 @@
|
||||
"id": "test0",
|
||||
"size": "8",
|
||||
"value": "secret",
|
||||
"forced": false
|
||||
"readonly": false
|
||||
}
|
||||
}}
|
||||
{{#forced}}
|
||||
{{#readonly}}
|
||||
<div class="form-password">
|
||||
<input type="text"
|
||||
name = "{{ name }}"
|
||||
@@ -46,8 +46,8 @@
|
||||
disabled
|
||||
>
|
||||
</div>
|
||||
{{/forced}}
|
||||
{{^forced}}
|
||||
{{/readonly}}
|
||||
{{^readonly}}
|
||||
<div class="form-password">
|
||||
<span data-passwordunmask="wrapper" data-passwordunmaskid="{{ id }}">
|
||||
<span data-passwordunmask="editor">
|
||||
@@ -76,4 +76,4 @@ require(['core_form/passwordunmask'], function(PasswordUnmask) {
|
||||
new PasswordUnmask("{{ id }}");
|
||||
});
|
||||
{{/js}}
|
||||
{{/forced}}
|
||||
{{/readonly}}
|
||||
|
||||
@@ -23,11 +23,13 @@
|
||||
* name - form element name
|
||||
* id - element id
|
||||
* options - list of options containing name, value, selected
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "test",
|
||||
"id": "test0",
|
||||
"readonly": false,
|
||||
"options": [
|
||||
{ "name": "Option 1", "value": "V", "selected": true },
|
||||
{ "name": "Option 2", "value": "V", "selected": true }
|
||||
@@ -38,7 +40,7 @@
|
||||
Setting configselect.
|
||||
}}
|
||||
<div class="form-select defaultsnext">
|
||||
<select id="{{id}}" name="{{name}}" class="custom-select">
|
||||
<select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}" class="custom-select">
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/options}}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
Context variables required for this template:
|
||||
* name - form element name
|
||||
* id - element id
|
||||
* readonly - bool
|
||||
* options - list of options (not grouped)
|
||||
* optgroups - list of options grouped containing the group label and for each option: name, value, selected
|
||||
|
||||
@@ -29,6 +30,7 @@
|
||||
{
|
||||
"name": "test",
|
||||
"id": "test0",
|
||||
"readonly": false,
|
||||
"options": [
|
||||
{ "name": "Option 1", "value": "V", "selected": false },
|
||||
{ "name": "Option 2", "value": "V", "selected": false }
|
||||
@@ -55,7 +57,7 @@
|
||||
Setting configselect with optgroup support.
|
||||
}}
|
||||
<div class="form-select defaultsnext">
|
||||
<select id="{{id}}" name="{{name}}" class="custom-select">
|
||||
<select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}" class="custom-select">
|
||||
{{#options}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/options}}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
* size - element size
|
||||
* forceltr - always display as ltr
|
||||
* attributes - list of additional attributes containing name, value
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
@@ -34,6 +35,7 @@
|
||||
"value": "A tall, dark stranger will have more fun than you.",
|
||||
"size": "21",
|
||||
"forceltr": false,
|
||||
"readonly": false,
|
||||
"attributes": [ { "name": "readonly", "value": "readonly" } ]
|
||||
}
|
||||
}}
|
||||
@@ -41,5 +43,5 @@
|
||||
Setting configtext.
|
||||
}}
|
||||
<div class="form-text defaultsnext">
|
||||
<input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">
|
||||
<input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="form-control {{#forceltr}}text-ltr{{/forceltr}}" {{#readonly}}disabled{{/readonly}}>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"cols": "30",
|
||||
"rows": "3",
|
||||
"value": "Excellent day for putting Slinkies on an escalator.",
|
||||
"readonly": false,
|
||||
"id": "test0"
|
||||
}
|
||||
}}
|
||||
@@ -40,5 +41,5 @@
|
||||
Setting configtextarea.
|
||||
}}
|
||||
<div class="form-textarea">
|
||||
<textarea rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
|
||||
<textarea {{#readonly}}disabled{{/readonly}} rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
|
||||
</div>
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
* id - element id
|
||||
* hours - list of valid hour options containing name, value, selected
|
||||
* minutes - list of valid minute options containing name, value, selected
|
||||
* readonly - bool
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "test",
|
||||
"id": "test0",
|
||||
"readonly": false,
|
||||
"minutes": [
|
||||
{ "name": "00", "value": "0", "selected": true },
|
||||
{ "name": "01", "value": "1", "selected": false }
|
||||
@@ -45,13 +47,13 @@
|
||||
<div class="form-time defaultsnext">
|
||||
<div class="form-inline text-ltr">
|
||||
<label class="sr-only" for="{{id}}h">{{#str}}hours{{/str}}</label>
|
||||
<select id="{{id}}h" name="{{name}}[h]" class="custom-select">
|
||||
<select id="{{id}}h" name="{{name}}[h]" class="custom-select" {{#readonly}}disabled{{/readonly}}>
|
||||
{{#hours}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/hours}}
|
||||
</select>:
|
||||
<label class="sr-only" for="{{id}}m">{{#str}}minutes{{/str}}</label>
|
||||
<select id="{{id}}m" name="{{name}}[m]" class="custom-select">
|
||||
<select id="{{id}}m" name="{{name}}[m]" class="custom-select" {{#readonly}}disabled{{/readonly}}>
|
||||
{{#minutes}}
|
||||
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/minutes}}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
@core @core_admin
|
||||
Feature: Administrator is warned and when trying to set invalid allcountrycodes value.
|
||||
In order to avoid misconfiguration of the country selector fields
|
||||
As an admin
|
||||
I want to be warned when I try to set an invalid country code in the allcountrycodes field
|
||||
|
||||
Scenario: Attempting to set allcountrycodes field with valid country codes
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Location > Location settings" in site administration
|
||||
When I set the following administration settings values:
|
||||
| All country codes | CZ,BE,GB,ES |
|
||||
Then I should not see "Invalid country code"
|
||||
|
||||
Scenario: Attempting to set allcountrycodes field with invalid country code
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Location > Location settings" in site administration
|
||||
When I set the following administration settings values:
|
||||
| All country codes | CZ,BE,FOOBAR,GB,ES |
|
||||
Then I should see "Invalid country code: FOOBAR"
|
||||
|
||||
Scenario: Attempting to unset allcountrycodes field
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Location > Location settings" in site administration
|
||||
And I set the following administration settings values:
|
||||
| All country codes | CZ,BE,GB,ES |
|
||||
And I navigate to "Location > Location settings" in site administration
|
||||
When I set the following administration settings values:
|
||||
| All country codes | |
|
||||
Then I should not see "Invalid country code"
|
||||
@@ -0,0 +1,34 @@
|
||||
@core @core_admin
|
||||
Feature: Configure language settings for the site
|
||||
In order to configure language settings for the site
|
||||
As an admin
|
||||
I want to set language settings relevant to my site users
|
||||
|
||||
Scenario: Set languages on language menu
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Language > Language settings" in site administration
|
||||
When I set the field "Languages on language menu" to "en"
|
||||
And I press "Save changes"
|
||||
Then I should not see "Invalid language code"
|
||||
|
||||
Scenario: Reset languages on language menu
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Language > Language settings" in site administration
|
||||
When I set the field "Languages on language menu" to ""
|
||||
And I press "Save changes"
|
||||
Then I should not see "Invalid language code"
|
||||
|
||||
Scenario Outline: Set languages on language menu with invalid language
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Language > Language settings" in site administration
|
||||
When I set the field "Languages on language menu" to "<fieldvalue>"
|
||||
And I press "Save changes"
|
||||
Then I should see "Invalid language code: <invalidlang>"
|
||||
Examples:
|
||||
| fieldvalue | invalidlang |
|
||||
| xx | xx |
|
||||
| xx\|Bad | xx |
|
||||
| en,qq | qq |
|
||||
| en,qq\|Bad | qq |
|
||||
| en$$ | en$$ |
|
||||
| en$$\|Bad | en$$ |
|
||||
@@ -6,14 +6,18 @@ Feature: Edit capabilities
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| username | firstname | lastname |
|
||||
| teacher1 | Teacher | 1 |
|
||||
| tutor | Teaching | Assistant |
|
||||
| student | Student | One |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| tutor | C1 | teacher |
|
||||
| student | C1 | student |
|
||||
|
||||
Scenario: Default system capabilities modification
|
||||
Given I log in as "admin"
|
||||
@@ -60,3 +64,25 @@ Feature: Edit capabilities
|
||||
Then "mod/forum:deleteanypost" capability has "Prohibit" permission
|
||||
And "mod/forum:editanypost" capability has "Prevent" permission
|
||||
And "mod/forum:addquestion" capability has "Allow" permission
|
||||
|
||||
@javascript
|
||||
Scenario: Edit permissions escapes role names correctly
|
||||
When I am on the "C1" "Course" page logged in as "admin"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Your word for 'Teacher' | Teacher >= editing |
|
||||
| Your word for 'Non-editing teacher' | Teacher < "editing" |
|
||||
| Your word for 'Student' | Studier & 'learner' |
|
||||
And I press "Save and display"
|
||||
And I navigate to course participants
|
||||
Then I should see "Teacher >= editing" in the "Teacher 1" "table_row"
|
||||
And I should see "Teacher < \"editing\"" in the "Teaching Assistant" "table_row"
|
||||
And I should see "Studier & 'learner'" in the "Student One" "table_row"
|
||||
And I navigate to "Users > Permissions" in current page administration
|
||||
And I should see "Teacher >= editing" in the "mod/forum:replypost" "table_row"
|
||||
And I should see "Teacher < \"editing\"" in the "mod/forum:replypost" "table_row"
|
||||
And I should see "Studier & 'learner'" in the "mod/forum:replypost" "table_row"
|
||||
And I follow "Prohibit"
|
||||
And "Teacher >= editing" "button" in the "Prohibit role" "dialogue" should be visible
|
||||
And "Teacher < \"editing\"" "button" in the "Prohibit role" "dialogue" should be visible
|
||||
And "Studier & 'learner'" "button" in the "Prohibit role" "dialogue" should be visible
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
@tool_behat
|
||||
Feature: Verify that the inplace editable field works as expected
|
||||
In order to use behat step definitions
|
||||
As a test write
|
||||
I need to ensure that the inplace editable works in forms
|
||||
|
||||
Background:
|
||||
Given the following "course" exists:
|
||||
| fullname | Course 1 |
|
||||
| shortname | C1 |
|
||||
And the following "activities" exist:
|
||||
| activity | course | name | idnumber |
|
||||
| forum | C1 | My first forum | forum1 |
|
||||
| assign | C1 | My first assignment | assign1 |
|
||||
| quiz | C1 | My first quiz | quiz1 |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
|
||||
@javascript
|
||||
Scenario: Using an inplace editable updates the name of an activity
|
||||
When I set the field "Edit title" in the "My first assignment" "activity" to "Coursework submission"
|
||||
Then I should see "Coursework submission"
|
||||
And I should not see "My first assignment"
|
||||
But I should see "My first forum"
|
||||
And I should see "My first quiz"
|
||||
And I set the field "Edit title" in the "Coursework submission" "activity" to "My first assignment"
|
||||
And I should not see "Coursework submission"
|
||||
But I should see "My first assignment"
|
||||
And I should see "My first forum"
|
||||
And I should see "My first quiz"
|
||||
@@ -0,0 +1,52 @@
|
||||
@tool_behat
|
||||
Feature: Verify that keyboard steps work as expected
|
||||
In order to use behat step definitions
|
||||
As a test writer
|
||||
I need to verify that the keyboard steps work as expected
|
||||
|
||||
@javascript
|
||||
Scenario: Typing keys into a field causes them to be input
|
||||
Given the following "users" exist:
|
||||
| username | email | firstname | lastname | password |
|
||||
| saffronr | saffron.rutledge@example.com | Saffron | Rutledge | flowerpower |
|
||||
Given I click on "Log in" "link"
|
||||
And I click on "Username" "field"
|
||||
When I type "saffronr"
|
||||
And I press the tab key
|
||||
And I type "flowerpower"
|
||||
And I press enter
|
||||
Then I should see "You are logged in as Saffron Rutledge"
|
||||
|
||||
@javascript
|
||||
Scenario: Using tab changes focus to the next or previous field
|
||||
Given I click on "Log in" "link"
|
||||
And I click on "Username" "field"
|
||||
And the focused element is "Username" "field"
|
||||
When I press the tab key
|
||||
Then the focused element is "Password" "field"
|
||||
|
||||
And I press the shift tab key
|
||||
And the focused element is "Username" "field"
|
||||
|
||||
@javascript
|
||||
Scenario: Using the arrow keys allows me to navigate through menus
|
||||
Given the following "users" exist:
|
||||
| username | email | firstname | lastname |
|
||||
| saffronr | saffron.rutledge@example.com | Saffron | Rutledge |
|
||||
And I log in as "saffronr"
|
||||
And I click on "Saffron Rutledge" "link" in the ".usermenu" "css_element"
|
||||
When I press the up key
|
||||
Then the focused element is "Log out" "link"
|
||||
|
||||
@javascript
|
||||
Scenario: The escape key can be used to close a dialogue
|
||||
Given the following "course" exists:
|
||||
| fullname | C1|
|
||||
| shortname | C1 |
|
||||
And I log in as "admin"
|
||||
And I am on "C1" course homepage
|
||||
And I navigate to course participants
|
||||
And I press "Enrol users"
|
||||
And "Enrol users" "dialogue" should be visible
|
||||
When I press the escape key
|
||||
Then "Enrol users" "dialogue" should not be visible
|
||||
@@ -1613,7 +1613,7 @@ class external extends external_api {
|
||||
*/
|
||||
private static function get_tree_node_structure($allowchildbranches = true) {
|
||||
$fields = [
|
||||
'text' => new external_value(PARAM_TEXT, 'The node text', VALUE_REQUIRED),
|
||||
'text' => new external_value(PARAM_RAW, 'The node text', VALUE_REQUIRED),
|
||||
'expandcontextid' => new external_value(PARAM_INT, 'The contextid this node expands', VALUE_REQUIRED),
|
||||
'expandelement' => new external_value(PARAM_ALPHA, 'What element is this node expanded to', VALUE_REQUIRED),
|
||||
'contextid' => new external_value(PARAM_INT, 'The node contextid', VALUE_REQUIRED),
|
||||
|
||||
@@ -63,7 +63,6 @@ class renderer extends plugin_renderer_base {
|
||||
$params = [
|
||||
'data-action' => 'contactdpo',
|
||||
'data-replytoemail' => $replytoemail,
|
||||
'class' => 'contactdpo'
|
||||
];
|
||||
return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ Feature: Manage data requests
|
||||
And I open the action menu in "John Doe" "table_row"
|
||||
And I should see "View the request"
|
||||
But I should not see "Mark as complete"
|
||||
And I press key "27" in ".moodle-actionmenu" "css_element"
|
||||
And I press the escape key
|
||||
And I open the action menu in "Jane Doe" "table_row"
|
||||
And I choose "Mark as complete" in the open action menu
|
||||
And I should see "Do you really want to mark this user enquiry as complete?"
|
||||
|
||||
@@ -10,16 +10,12 @@ Feature: Manage data storage purposes
|
||||
And I open the action menu in "region-main" "region"
|
||||
And I choose "Purposes" in the open action menu
|
||||
And I press "Add purpose"
|
||||
And I set the field "Name" to "Purpose 1"
|
||||
And I set the field "Description" to "Purpose 1 description"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Lawful bases" "form_row"
|
||||
And I click on "Contract (GDPR Art. 6.1(b))" "list_item"
|
||||
And I click on "Legal obligation (GDPR Art 6.1(c))" "list_item"
|
||||
And I press key "27" in the field "Lawful bases"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Sensitive personal data processing reasons" "form_row"
|
||||
And I click on "Explicit consent (GDPR Art. 9.2(a))" "list_item"
|
||||
And I press key "27" in the field "Sensitive personal data processing reasons"
|
||||
And I set the field "retentionperiodnumber" to "2"
|
||||
And I set the following fields to these values:
|
||||
| Name | Purpose 1 |
|
||||
| Description | Purpose 1 description |
|
||||
| Lawful bases | Contract (GDPR Art. 6.1(b)),Legal obligation (GDPR Art 6.1(c)) |
|
||||
| Sensitive personal data processing reasons | Explicit consent (GDPR Art. 9.2(a)) |
|
||||
| retentionperiodnumber | 2 |
|
||||
When I press "Save"
|
||||
Then I should see "Purpose 1" in the "List of data purposes" "table"
|
||||
And I should see "Contract (GDPR Art. 6.1(b))" in the "Purpose 1" "table_row"
|
||||
@@ -31,14 +27,13 @@ Feature: Manage data storage purposes
|
||||
Scenario: Update a data storage purpose
|
||||
Given I open the action menu in "Purpose 1" "table_row"
|
||||
And I choose "Edit" in the open action menu
|
||||
And I set the field "Name" to "Purpose 1 edited"
|
||||
And I set the field "Description" to "Purpose 1 description edited"
|
||||
And I click on "Legal obligation (GDPR Art 6.1(c))" "text" in the ".form-autocomplete-selection" "css_element"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Lawful bases" "form_row"
|
||||
And I click on "Vital interests (GDPR Art. 6.1(d))" "list_item"
|
||||
And I press key "27" in the field "Lawful bases"
|
||||
And I set the field "retentionperiodnumber" to "3"
|
||||
And I click on "protected" "checkbox"
|
||||
And I set the following fields to these values:
|
||||
| Name | Purpose 1 edited |
|
||||
| Description | Purpose 1 description edited |
|
||||
| Lawful bases | Contract (GDPR Art. 6.1(b)), Vital interests (GDPR Art. 6.1(d)) |
|
||||
| Sensitive personal data processing reasons | Explicit consent (GDPR Art. 9.2(a)) |
|
||||
| retentionperiodnumber | 3 |
|
||||
| protected | 1 |
|
||||
When I press "Save changes"
|
||||
Then I should see "Purpose 1 edited" in the "List of data purposes" "table"
|
||||
And I should see "Purpose 1 description edited" in the "Purpose 1 edited" "table_row"
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_langimport\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_collator;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
@@ -95,6 +95,8 @@ class langimport_page implements renderable, templatable {
|
||||
|
||||
if (!empty($this->availablelanguages)) {
|
||||
$data->toinstalloptions = [];
|
||||
|
||||
core_collator::asort($this->availablelanguages);
|
||||
foreach ($this->availablelanguages as $code => $language) {
|
||||
$option = new stdClass();
|
||||
$option->value = $code;
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace tool_lp\form;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use moodleform;
|
||||
use core\form\persistent;
|
||||
|
||||
require_once($CFG->libdir . '/formslib.php');
|
||||
|
||||
@@ -39,17 +38,20 @@ require_once($CFG->libdir . '/formslib.php');
|
||||
*/
|
||||
class template_cohorts extends moodleform {
|
||||
|
||||
/**
|
||||
* Form definition
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$options = array(
|
||||
'ajax' => 'tool_lp/form-cohort-selector',
|
||||
'multiple' => true,
|
||||
'data-contextid' => $this->_customdata['pagecontextid'],
|
||||
'data-includes' => 'parents'
|
||||
'exclude' => implode(',', $this->_customdata['excludecohorts']),
|
||||
'contextid' => $this->_customdata['pagecontextid'],
|
||||
);
|
||||
$mform->addElement('autocomplete', 'cohorts', get_string('selectcohortstosync', 'tool_lp'), array(), $options);
|
||||
$mform->addElement('cohort', 'cohorts', get_string('selectcohortstosync', 'tool_lp'), $options);
|
||||
$mform->addElement('submit', 'submit', get_string('addcohorts', 'tool_lp'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,19 @@ if ($canmanagetemplate && ($removecohort = optional_param('removecohort', false,
|
||||
}
|
||||
|
||||
// Capture the form submission.
|
||||
$form = new \tool_lp\form\template_cohorts($url->out(false), array('pagecontextid' => $pagecontextid));
|
||||
$existingcohortsql =
|
||||
'SELECT c.id
|
||||
FROM {' . \core_competency\template_cohort::TABLE . '} tc
|
||||
JOIN {cohort} c ON c.id = tc.cohortid
|
||||
WHERE tc.templateid = :templateid';
|
||||
|
||||
$existingcohorts = $DB->get_records_sql_menu($existingcohortsql, ['templateid' => $template->get('id')]);
|
||||
|
||||
$form = new \tool_lp\form\template_cohorts($url->out(false), [
|
||||
'pagecontextid' => $pagecontextid,
|
||||
'excludecohorts' => array_keys($existingcohorts),
|
||||
]);
|
||||
|
||||
if ($canmanagetemplate && ($data = $form->get_data()) && !empty($data->cohorts)) {
|
||||
$maxtocreate = 50;
|
||||
$maxreached = false;
|
||||
|
||||
@@ -53,11 +53,11 @@ Feature: See the competencies for an activity on the course competencies page.
|
||||
Then I should see "Test-Comp1"
|
||||
And I should see "Test-Comp2"
|
||||
And I set the field "Filter competencies by resource or activity" to "PageName1"
|
||||
And I press key "13" in the field "Filter competencies by resource or activity"
|
||||
And I press the enter key
|
||||
And I should see "Test-Comp1"
|
||||
And I should not see "Test-Comp2"
|
||||
And I set the field "Filter competencies by resource or activity" to "PageName2"
|
||||
And I press key "13" in the field "Filter competencies by resource or activity"
|
||||
And I press the enter key
|
||||
And I should not see "Test-Comp1"
|
||||
And I should not see "Test-Comp2"
|
||||
And I should see "No competencies have been linked to this activity or resource."
|
||||
|
||||
@@ -30,7 +30,7 @@ Feature: Manage plearning plan
|
||||
And I click on ".template-userplans" "css_element" in the "Science template" "table_row"
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Admin User" item in the autocomplete list
|
||||
And I press key "27" in the field "Select users to create learning plans for"
|
||||
And I press the escape key
|
||||
When I click on "Create learning plans" "button"
|
||||
Then I should see "A learning plan was created"
|
||||
And I should see "Admin User" in the "Science template" "table_row"
|
||||
@@ -53,9 +53,7 @@ Feature: Manage plearning plan
|
||||
And I follow "Home"
|
||||
And I navigate to "Competencies > Learning plan templates" in site administration
|
||||
And I click on ".template-cohorts" "css_element" in the "Science template cohort" "table_row"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element"
|
||||
And I click on "cohort plan" item in the autocomplete list
|
||||
And I press key "27" in the field "Select cohorts to sync"
|
||||
And I set the field "Select cohorts to sync" to "cohort plan"
|
||||
When I click on "Add cohorts" "button"
|
||||
Then I should see "2 learning plans were created."
|
||||
And I follow "Learning plan templates"
|
||||
|
||||
@@ -322,6 +322,12 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($section) or $section == 'supportcontact') {
|
||||
$settings->supportname = $CFG->supportname;
|
||||
$settings->supportemail = $CFG->supportemail;
|
||||
$settings->supportpage = $CFG->supportpage;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
@@ -487,6 +493,7 @@ class api {
|
||||
'$mmSideMenuDelegate_mmaCompetency' => new lang_string('myplans', 'tool_lp'),
|
||||
'CoreMainMenuDelegate_AddonBlog' => new lang_string('blog', 'blog'),
|
||||
'$mmSideMenuDelegate_mmaFiles' => new lang_string('files'),
|
||||
'CoreMainMenuDelegate_CoreTag' => new lang_string('tags'),
|
||||
'$mmSideMenuDelegate_website' => new lang_string('webpage'),
|
||||
'$mmSideMenuDelegate_help' => new lang_string('help'),
|
||||
'CoreMainMenuDelegate_QrReader' => new lang_string('scanqrcode', 'tool_mobile'),
|
||||
@@ -595,8 +602,21 @@ class api {
|
||||
$timenow = time();
|
||||
$expectedissuer = null;
|
||||
foreach ($info['certinfo'] as $cert) {
|
||||
|
||||
// Due to a bug in certain curl/openssl versions the signature algorithm isn't always correctly parsed.
|
||||
// See https://github.com/curl/curl/issues/3706 for reference.
|
||||
if (!array_key_exists('Signature Algorithm', $cert)) {
|
||||
// The malformed field that does contain the algorithm we're looking for looks like the following:
|
||||
// <WHITESPACE>Signature Algorithm: <ALGORITHM><CRLF><ALGORITHM>.
|
||||
preg_match('/\s+Signature Algorithm: (?<algorithm>[^\s]+)/', $cert['Public Key Algorithm'], $matches);
|
||||
|
||||
$signaturealgorithm = $matches['algorithm'] ?? '';
|
||||
} else {
|
||||
$signaturealgorithm = $cert['Signature Algorithm'];
|
||||
}
|
||||
|
||||
// Check if the signature algorithm is weak (Android won't work with SHA-1).
|
||||
if ($cert['Signature Algorithm'] == 'sha1WithRSAEncryption' || $cert['Signature Algorithm'] == 'sha1WithRSA') {
|
||||
if ($signaturealgorithm == 'sha1WithRSAEncryption' || $signaturealgorithm == 'sha1WithRSA') {
|
||||
$warnings[] = ['insecurealgorithmwarning', 'tool_mobile'];
|
||||
}
|
||||
// Check certificate start date.
|
||||
|
||||
@@ -139,7 +139,7 @@ class external extends external_api {
|
||||
array(
|
||||
'wwwroot' => new external_value(PARAM_RAW, 'Site URL.'),
|
||||
'httpswwwroot' => new external_value(PARAM_RAW, 'Site https URL (if httpslogin is enabled).'),
|
||||
'sitename' => new external_value(PARAM_TEXT, 'Site name.'),
|
||||
'sitename' => new external_value(PARAM_RAW, 'Site name.'),
|
||||
'guestlogin' => new external_value(PARAM_INT, 'Whether guest login is enabled.'),
|
||||
'rememberusername' => new external_value(PARAM_INT, 'Values: 0 for No, 1 for Yes, 2 for optional.'),
|
||||
'authloginviaemail' => new external_value(PARAM_INT, 'Whether log in via email is enabled.'),
|
||||
|
||||
@@ -229,6 +229,10 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
|
||||
'value' => get_config('core_admin', 'coursecolor' . $number)
|
||||
];
|
||||
}
|
||||
$expected[] = ['name' => 'supportname', 'value' => $CFG->supportname];
|
||||
$expected[] = ['name' => 'supportemail', 'value' => $CFG->supportemail];
|
||||
$expected[] = ['name' => 'supportpage', 'value' => $CFG->supportpage];
|
||||
|
||||
$this->assertCount(0, $result['warnings']);
|
||||
$this->assertEquals($expected, $result['settings']);
|
||||
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("tool_templatelibrary/search",["jquery","core/ajax","core/log","core/notification","core/templates","core/config"],function(a,b,c,d,e,f){var g=function(b){e.render("tool_templatelibrary/search_results",{templates:b}).done(function(b,c){e.replaceNode(a("[data-region=\"searchresults\"]"),b,c)}).fail(d.exception)},h=function(c){var e=a("[data-field=\"component\"]").val(),f=a("[data-field=\"search\"]").val();document.location.hash=f;b.call([{methodname:"tool_templatelibrary_list_templates",args:{component:e,search:f,themename:c},done:g,fail:d.exception}],!0,!1)},i=null,j=function(a,b){if(null!==i){window.clearTimeout(i)}i=window.setTimeout(function(){a();i=null},b)},k=function(){j(h.bind(this,f.theme),400)};a("[data-region=\"list-templates\"]").on("change","[data-field=\"component\"]",k);a("[data-region=\"list-templates\"]").on("input","[data-field=\"search\"]",k);a("[data-field=\"search\"]").val(document.location.hash.replace("#",""));h(f.theme);return{}});
|
||||
define ("tool_templatelibrary/search",["jquery","core/ajax","core/log","core/notification","core/templates","core/config"],function(a,b,c,d,e,f){var g=function(b){e.render("tool_templatelibrary/search_results",{templates:b}).done(function(b,c){e.replaceNode(a("[data-region=\"searchresults\"]"),b,c)}).fail(d.exception)},h=function(c){var e=a("[data-field=\"component\"]").val(),f=a("[data-field=\"search\"]").val();b.call([{methodname:"tool_templatelibrary_list_templates",args:{component:e,search:f,themename:c},done:g,fail:d.exception}],!0,!1)},i=null,j=function(a,b){if(null!==i){window.clearTimeout(i)}i=window.setTimeout(function(){a();i=null},b)},k=function(){j(h.bind(this,f.theme),400)};a("[data-region=\"list-templates\"]").on("change","[data-field=\"component\"]",k);a("[data-region=\"list-templates\"]").on("input","[data-field=\"search\"]",k);h(f.theme);return{}});
|
||||
//# sourceMappingURL=search.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -48,8 +48,6 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
|
||||
var searchStr = $('[data-field="search"]').val();
|
||||
|
||||
// Trigger the search.
|
||||
document.location.hash = searchStr;
|
||||
|
||||
ajax.call([
|
||||
{methodname: 'tool_templatelibrary_list_templates',
|
||||
args: {component: componentStr, search: searchStr, themename: themename},
|
||||
@@ -86,7 +84,6 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
|
||||
$('[data-region="list-templates"]').on('change', '[data-field="component"]', changeHandler);
|
||||
$('[data-region="list-templates"]').on('input', '[data-field="search"]', changeHandler);
|
||||
|
||||
$('[data-field="search"]').val(document.location.hash.replace('#', ''));
|
||||
refreshSearch(config.theme);
|
||||
return {};
|
||||
});
|
||||
|
||||
@@ -38,6 +38,22 @@ use tool_templatelibrary\api;
|
||||
*/
|
||||
class list_templates_page implements renderable, templatable {
|
||||
|
||||
/** @var string $component The currently selected component */
|
||||
protected $component;
|
||||
/** @var string $search The current search */
|
||||
protected $search;
|
||||
|
||||
/**
|
||||
* Template page constructor
|
||||
*
|
||||
* @param string $component
|
||||
* @param string $search
|
||||
*/
|
||||
public function __construct(string $component = '', string $search = '') {
|
||||
$this->component = $component;
|
||||
$this->search = $search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
@@ -46,6 +62,7 @@ class list_templates_page implements renderable, templatable {
|
||||
public function export_for_template(renderer_base $output) {
|
||||
$data = new stdClass();
|
||||
$data->allcomponents = array();
|
||||
$data->search = $this->search;
|
||||
$fulltemplatenames = api::list_templates();
|
||||
$pluginmanager = core_plugin_manager::instance();
|
||||
$components = array();
|
||||
@@ -59,6 +76,7 @@ class list_templates_page implements renderable, templatable {
|
||||
foreach ($components as $component) {
|
||||
$info = new stdClass();
|
||||
$info->component = $component;
|
||||
$info->selected = ($component === $this->component);
|
||||
if (strpos($component, 'core') === 0) {
|
||||
$info->name = get_string('coresubsystem', 'tool_templatelibrary', $component);
|
||||
} else {
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<div class="control-group">
|
||||
<label for="selectcomponent" class="control-label">{{#str}}component, tool_templatelibrary{{/str}}</label>
|
||||
<div class="controls">
|
||||
<select id="selectcomponent" data-field="component">
|
||||
<select id="selectcomponent" name="component" data-field="component">
|
||||
<option value="">{{#str}}all, tool_templatelibrary{{/str}}</option>
|
||||
{{#allcomponents}}
|
||||
<option value="{{component}}">{{name}}</option>
|
||||
<option value="{{component}}" {{#selected}}selected{{/selected}}>{{name}}</option>
|
||||
{{/allcomponents}}
|
||||
</select>
|
||||
</div>
|
||||
@@ -47,7 +47,7 @@
|
||||
<div class="control-group">
|
||||
<label for="search" class="control-label">{{#str}}search, tool_templatelibrary{{/str}}</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="search" data-field="search"/>
|
||||
<input type="text" id="search" name="search" value="{{ search }}" data-field="search"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -902,36 +902,31 @@ class tool_uploadcourse_course {
|
||||
unset($method['delete']);
|
||||
unset($method['disable']);
|
||||
|
||||
if (!empty($instance) && $todelete) {
|
||||
if ($todelete) {
|
||||
// Remove the enrolment method.
|
||||
foreach ($instances as $instance) {
|
||||
if ($instance->enrol == $enrolmethod) {
|
||||
$plugin = $enrolmentplugins[$instance->enrol];
|
||||
$plugin->delete_instance($instance);
|
||||
break;
|
||||
}
|
||||
if ($instance) {
|
||||
$plugin = $enrolmentplugins[$instance->enrol];
|
||||
$plugin->delete_instance($instance);
|
||||
}
|
||||
} else if (!empty($instance) && $todisable) {
|
||||
// Disable the enrolment.
|
||||
foreach ($instances as $instance) {
|
||||
if ($instance->enrol == $enrolmethod) {
|
||||
$plugin = $enrolmentplugins[$instance->enrol];
|
||||
$plugin->update_status($instance, ENROL_INSTANCE_DISABLED);
|
||||
$enrol_updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$plugin = $enrolmentplugins[$instance->enrol];
|
||||
$plugin->update_status($instance, ENROL_INSTANCE_DISABLED);
|
||||
$enrol_updated = true;
|
||||
} else {
|
||||
$plugin = null;
|
||||
|
||||
$status = ($todisable) ? ENROL_INSTANCE_DISABLED : ENROL_INSTANCE_ENABLED;
|
||||
|
||||
if (empty($instance)) {
|
||||
$plugin = $enrolmentplugins[$enrolmethod];
|
||||
$instance = new stdClass();
|
||||
$instance->id = $plugin->add_default_instance($course);
|
||||
$instanceid = $plugin->add_default_instance($course);
|
||||
$instance = $DB->get_record('enrol', ['id' => $instanceid]);
|
||||
$instance->roleid = $plugin->get_config('roleid');
|
||||
$instance->status = ENROL_INSTANCE_ENABLED;
|
||||
$plugin->update_status($instance, $status);
|
||||
} else {
|
||||
$plugin = $enrolmentplugins[$instance->enrol];
|
||||
$plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
|
||||
$plugin->update_status($instance, $status);
|
||||
}
|
||||
|
||||
// Now update values.
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
@tool @tool_uploadcourse @_file_upload
|
||||
Feature: An admin can update courses enrolments using a CSV file
|
||||
In order to update courses enrolments using a CSV file
|
||||
As an admin
|
||||
I need to be able to upload a CSV file with enrolment methods for the courses
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And I log in as "admin"
|
||||
|
||||
@javascript
|
||||
Scenario: Creating enrolment method by enable it
|
||||
Given I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I click on "Continue" "button"
|
||||
And I should not see "Guest access" in the "generaltable" "table"
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_enable.csv" file to "File" filemanager
|
||||
And I set the field "Upload mode" to "Only update existing courses"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I set the field "Allow deletes" to "Yes"
|
||||
And I click on "Preview" "button"
|
||||
When I click on "Upload courses" "button"
|
||||
Then I should see "Course updated"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And "Disable" "icon" should exist in the "Guest access" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Creating enrolment method by disabling it
|
||||
Given I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I click on "Continue" "button"
|
||||
And I should not see "Guest access" in the "generaltable" "table"
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_disable.csv" file to "File" filemanager
|
||||
And I set the field "Upload mode" to "Only update existing courses"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I set the field "Allow deletes" to "Yes"
|
||||
And I click on "Preview" "button"
|
||||
When I click on "Upload courses" "button"
|
||||
Then I should see "Course updated"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And "Enable" "icon" should exist in the "Guest access" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Enabling enrolment method
|
||||
Given I navigate to "Courses > Upload courses" in site administration
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_enable.csv" file to "File" filemanager
|
||||
And I set the field "Upload mode" to "Only update existing courses"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I set the field "Allow deletes" to "Yes"
|
||||
And I click on "Preview" "button"
|
||||
When I click on "Upload courses" "button"
|
||||
Then I should see "Course updated"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And "Disable" "icon" should exist in the "Guest access" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Disable an enrolment method
|
||||
Given I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And I click on "Enable" "link" in the "Guest access" "table_row"
|
||||
And "Disable" "icon" should exist in the "Guest access" "table_row"
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_disable.csv" file to "File" filemanager
|
||||
And I set the field "Upload mode" to "Only update existing courses"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I set the field "Allow deletes" to "Yes"
|
||||
And I click on "Preview" "button"
|
||||
When I click on "Upload courses" "button"
|
||||
Then I should see "Course updated"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And "Enable" "icon" should exist in the "Guest access" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Delete an enrolment method
|
||||
Given I navigate to "Courses > Upload courses" in site administration
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_delete.csv" file to "File" filemanager
|
||||
And I set the field "Upload mode" to "Only update existing courses"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I set the field "Allow deletes" to "Yes"
|
||||
And I click on "Preview" "button"
|
||||
When I click on "Upload courses" "button"
|
||||
Then I should see "Course updated"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And I should not see "Guest access" in the "generaltable" "table"
|
||||
|
||||
@javascript
|
||||
Scenario: Delete an unexistent enrolment method (nothing should change)
|
||||
Given I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And I click on "Delete" "link" in the "Guest access" "table_row"
|
||||
And I click on "Continue" "button"
|
||||
And I should not see "Guest access" in the "generaltable" "table"
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_delete.csv" file to "File" filemanager
|
||||
And I set the field "Upload mode" to "Only update existing courses"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I set the field "Allow deletes" to "Yes"
|
||||
And I click on "Preview" "button"
|
||||
When I click on "Upload courses" "button"
|
||||
Then I should see "Course updated"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Enrolment methods" in current page administration
|
||||
And I should not see "Guest access" in the "generaltable" "table"
|
||||
@@ -0,0 +1,2 @@
|
||||
shortname,category,enrolment_1,enrolment_1_delete
|
||||
C1,1,guest,1
|
||||
|
@@ -0,0 +1,2 @@
|
||||
shortname,category,enrolment_1,enrolment_1_disable
|
||||
C1,1,guest,1
|
||||
|
@@ -0,0 +1,2 @@
|
||||
shortname,category,enrolment_1,enrolment_1_disable
|
||||
C1,1,guest,0
|
||||
|
@@ -47,7 +47,7 @@ class provider implements
|
||||
/**
|
||||
* Returns meta data about this system.
|
||||
*
|
||||
* @param collection $itemcollection The initialised item collection to add items to.
|
||||
* @param collection $items The initialised item collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $items) : collection {
|
||||
@@ -64,7 +64,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) {
|
||||
$descriptionidentifier = null;
|
||||
$tourid = null;
|
||||
|
||||
@@ -557,9 +557,10 @@ class tour {
|
||||
|
||||
// Remove the configuration for the tour.
|
||||
$DB->delete_records('tool_usertours_tours', array('id' => $this->id));
|
||||
|
||||
helper::reset_tour_sortorder();
|
||||
|
||||
$this->remove_user_preferences();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -585,6 +586,16 @@ class tour {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove stored user preferences for the tour
|
||||
*/
|
||||
protected function remove_user_preferences(): void {
|
||||
global $DB;
|
||||
|
||||
$DB->delete_records('user_preferences', ['name' => self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id()]);
|
||||
$DB->delete_records('user_preferences', ['name' => self::TOUR_REQUESTED_BY_USER . $this->get_id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this tour should be displayed to the user.
|
||||
*
|
||||
@@ -665,11 +676,9 @@ class tour {
|
||||
* @return $this
|
||||
*/
|
||||
public function mark_major_change() {
|
||||
global $DB;
|
||||
|
||||
// Clear old reset and completion notes.
|
||||
$DB->delete_records('user_preferences', ['name' => self::TOUR_LAST_COMPLETED_BY_USER . $this->get_id()]);
|
||||
$DB->delete_records('user_preferences', ['name' => self::TOUR_REQUESTED_BY_USER . $this->get_id()]);
|
||||
$this->remove_user_preferences();
|
||||
|
||||
$this->set_config('majorupdatetime', time());
|
||||
$this->persist();
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use tool_usertours\manager;
|
||||
use tool_usertours\tour;
|
||||
|
||||
/**
|
||||
* Upgrade the user tours plugin.
|
||||
@@ -57,5 +58,26 @@ function xmldb_tool_usertours_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.9.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2020061502) {
|
||||
// Clean up user preferences of deleted tours.
|
||||
$select = $DB->sql_like('name', ':lastcompleted') . ' OR ' . $DB->sql_like('name', ':requested');
|
||||
$params = [
|
||||
'lastcompleted' => tour::TOUR_LAST_COMPLETED_BY_USER . '%',
|
||||
'requested' => tour::TOUR_REQUESTED_BY_USER . '%',
|
||||
];
|
||||
|
||||
$preferences = $DB->get_records_select('user_preferences', $select, $params, '', 'DISTINCT name');
|
||||
foreach ($preferences as $preference) {
|
||||
// Match tour ID at the end of the preference name, remove all of that preference type if tour ID doesn't exist.
|
||||
if (preg_match('/(?<tourid>\d+)$/', $preference->name, $matches) &&
|
||||
!$DB->record_exists('tool_usertours_tours', ['id' => $matches['tourid']])) {
|
||||
|
||||
$DB->delete_records('user_preferences', ['name' => $preference->name]);
|
||||
}
|
||||
}
|
||||
|
||||
upgrade_plugin_savepoint(true, 2020061502, 'tool', 'usertours');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the block_html implementation of the privacy API.
|
||||
* Unit tests for the tool_usertours implementation of the privacy API.
|
||||
*
|
||||
* @package block_html
|
||||
* @package tool_usertours
|
||||
* @category test
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
@@ -27,19 +27,22 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_privacy\local\metadata\collection;
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\deletion_criteria;
|
||||
use \tool_usertours\tour;
|
||||
use \tool_usertours\privacy\provider;
|
||||
|
||||
/**
|
||||
* Unit tests for the block_html implementation of the privacy API.
|
||||
* Unit tests for the tool_usertours implementation of the privacy API.
|
||||
*
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_usertours_privacy_testcase extends \core_privacy\tests\provider_testcase {
|
||||
class tool_usertours_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
|
||||
|
||||
/**
|
||||
* Helper method for creating a tour
|
||||
*
|
||||
* @return tour
|
||||
*/
|
||||
protected function create_test_tour(): tour {
|
||||
return (new tour())
|
||||
->set_name('test_tour')
|
||||
@@ -118,6 +121,37 @@ class tool_usertours_privacy_testcase extends \core_privacy\tests\provider_testc
|
||||
$this->assertCount(2, (array) $prefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we are exporting preferences for the correct user
|
||||
*/
|
||||
public function test_export_user_preferences_correct_user(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$tour = $this->create_test_tour();
|
||||
|
||||
// Create test user, mark them as having completed the tour.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$tour->mark_user_completed();
|
||||
|
||||
// Switch to admin user, mark them as having reset the tour.
|
||||
$this->setAdminUser();
|
||||
$tour->request_user_reset();
|
||||
|
||||
// Export test users preferences.
|
||||
provider::export_user_preferences($user->id);
|
||||
|
||||
$writer = writer::with_context(\context_system::instance());
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
|
||||
$prefs = $writer->get_user_preferences('tool_usertours');
|
||||
$this->assertCount(1, (array) $prefs);
|
||||
|
||||
// We should have received back the "completed tour" preference of the test user.
|
||||
$this->assertStringStartsWith('You last marked the "' . $tour->get_name() . '" user tour as completed on',
|
||||
reset($prefs)->description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that export_user_preferences excludes deleted tours.
|
||||
*/
|
||||
|
||||
@@ -64,7 +64,7 @@ class tour_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Helper to mock the database.
|
||||
*
|
||||
* @return moodle_database
|
||||
* @return \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
public function mock_database() {
|
||||
global $DB;
|
||||
@@ -569,9 +569,14 @@ class tour_testcase extends advanced_testcase {
|
||||
|
||||
// Mock the database.
|
||||
$DB = $this->mock_database();
|
||||
$DB->expects($this->once())
|
||||
|
||||
$DB->expects($this->exactly(3))
|
||||
->method('delete_records')
|
||||
->with($this->equalTo('tool_usertours_tours'), $this->equalTo(['id' => $id]))
|
||||
->withConsecutive(
|
||||
[$this->equalTo('tool_usertours_tours'), $this->equalTo(['id' => $id])],
|
||||
[$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_LAST_COMPLETED_BY_USER . $id])],
|
||||
[$this->equalTo('user_preferences'), $this->equalTo(['name' => tour::TOUR_REQUESTED_BY_USER . $id])]
|
||||
)
|
||||
->willReturn(null)
|
||||
;
|
||||
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2020061501; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2020061502; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2020060900; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_usertours'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -149,7 +149,7 @@ abstract class XMLDBCheckAction extends XMLDBAction {
|
||||
continue;
|
||||
}
|
||||
// Fetch metadata from physical DB. All the columns info.
|
||||
if (!$metacolumns = $DB->get_columns($xmldb_table->getName())) {
|
||||
if (!$metacolumns = $DB->get_columns($xmldb_table->getName(), false)) {
|
||||
// / Skip table if no metacolumns is available for it
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1492,6 +1492,17 @@ class model {
|
||||
$prediction->get_prediction_data()->contextid, $prediction->get_sample_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default output directory for prediction processors
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function default_output_dir(): string {
|
||||
global $CFG;
|
||||
|
||||
return $CFG->dataroot . DIRECTORY_SEPARATOR . 'models';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output directory for prediction processors.
|
||||
*
|
||||
@@ -1506,8 +1517,6 @@ class model {
|
||||
* @return string
|
||||
*/
|
||||
public function get_output_dir($subdirs = array(), $onlymodelid = false) {
|
||||
global $CFG;
|
||||
|
||||
$subdirstr = '';
|
||||
foreach ($subdirs as $subdir) {
|
||||
$subdirstr .= DIRECTORY_SEPARATOR . $subdir;
|
||||
@@ -1516,7 +1525,7 @@ class model {
|
||||
$outputdir = get_config('analytics', 'modeloutputdir');
|
||||
if (empty($outputdir)) {
|
||||
// Apply default value.
|
||||
$outputdir = rtrim($CFG->dataroot, '/') . DIRECTORY_SEPARATOR . 'models';
|
||||
$outputdir = self::default_output_dir();
|
||||
}
|
||||
|
||||
// Append model id.
|
||||
|
||||
@@ -55,10 +55,7 @@ Feature: Manage analytics models
|
||||
And I choose "Create model" in the open action menu
|
||||
And I set the field "Enabled" to "Enable"
|
||||
And I select "__core_course__analytics__target__course_completion" from the "target" singleselect
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Read actions amount" item in the autocomplete list
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Any write action in the course" item in the autocomplete list
|
||||
And I set the field "Indicators" to "Read actions amount, Any write action in the course"
|
||||
And I select "__core__analytics__time_splitting__single_range" from the "timesplitting" singleselect
|
||||
And I press "Save changes"
|
||||
Then I should see "No predictions available yet" in the "Students at risk of not meeting the course completion conditions" "table_row"
|
||||
|
||||
+3
-1
@@ -462,7 +462,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$id = user_create_user($user, false); // It is truly a new user.
|
||||
$id = user_create_user($user, false, false); // It is truly a new user.
|
||||
$trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
|
||||
} catch (moodle_exception $e) {
|
||||
$trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
|
||||
@@ -481,6 +481,8 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
|
||||
// Make sure user context is present.
|
||||
context_user::instance($id);
|
||||
|
||||
\core\event\user_created::create_from_userid($id)->trigger();
|
||||
}
|
||||
unset($add_users);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ class auth_db_testcase extends advanced_testcase {
|
||||
$table->add_field('email', XMLDB_TYPE_CHAR, '255', null, null, null);
|
||||
$table->add_field('firstname', XMLDB_TYPE_CHAR, '255', null, null, null);
|
||||
$table->add_field('lastname', XMLDB_TYPE_CHAR, '255', null, null, null);
|
||||
$table->add_field('animal', XMLDB_TYPE_CHAR, '255', null, null, null);
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
if ($dbman->table_exists($table)) {
|
||||
$dbman->drop_table($table);
|
||||
@@ -137,6 +138,15 @@ class auth_db_testcase extends advanced_testcase {
|
||||
set_config('field_updateremote_email', '0', 'auth_db');
|
||||
set_config('field_lock_email', 'unlocked', 'auth_db');
|
||||
|
||||
// Create a user profile field and add mapping to it.
|
||||
$DB->insert_record('user_info_field', ['shortname' => 'pet', 'name' => 'Pet', 'required' => 0,
|
||||
'visible' => 1, 'locked' => 0, 'categoryid' => 1, 'datatype' => 'text']);
|
||||
|
||||
set_config('field_map_profile_field_pet', 'animal', 'auth_db');
|
||||
set_config('field_updatelocal_profile_field_pet', 'oncreate', 'auth_db');
|
||||
set_config('field_updateremote_profile_field_pet', '0', 'auth_db');
|
||||
set_config('field_lock_profile_field_pet', 'unlocked', 'auth_db');
|
||||
|
||||
// Init the rest of settings.
|
||||
set_config('passtype', 'plaintext', 'auth_db');
|
||||
set_config('changepasswordurl', '', 'auth_db');
|
||||
@@ -156,6 +166,7 @@ class auth_db_testcase extends advanced_testcase {
|
||||
|
||||
public function test_plugin() {
|
||||
global $DB, $CFG;
|
||||
require_once($CFG->dirroot . '/user/profile/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
@@ -193,7 +204,7 @@ class auth_db_testcase extends advanced_testcase {
|
||||
|
||||
// Test bulk user account creation.
|
||||
|
||||
$user2 = (object)array('name'=>'u2', 'pass'=>'heslo', 'email'=>'u2@example.com');
|
||||
$user2 = (object)['name' => 'u2', 'pass' => 'heslo', 'email' => 'u2@example.com', 'animal' => 'cat'];
|
||||
$user2->id = $DB->insert_record('auth_db_users', $user2);
|
||||
|
||||
$user3 = (object)array('name'=>'admin', 'pass'=>'heslo', 'email'=>'admin@example.com'); // Should be skipped.
|
||||
@@ -202,13 +213,24 @@ class auth_db_testcase extends advanced_testcase {
|
||||
$this->assertCount(2, $DB->get_records('user'));
|
||||
|
||||
$trace = new null_progress_trace();
|
||||
$auth->sync_users($trace, false);
|
||||
|
||||
// Sync users and make sure that two events user_created werer triggered.
|
||||
$sink = $this->redirectEvents();
|
||||
$auth->sync_users($trace, false);
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
$this->assertCount(2, $events);
|
||||
$this->assertTrue($events[0] instanceof \core\event\user_created);
|
||||
$this->assertTrue($events[1] instanceof \core\event\user_created);
|
||||
|
||||
// Assert the two users were created.
|
||||
$this->assertEquals(4, $DB->count_records('user'));
|
||||
$u1 = $DB->get_record('user', array('username'=>$user1->name, 'auth'=>'db'));
|
||||
$this->assertSame($user1->email, $u1->email);
|
||||
$this->assertEmpty(profile_user_record($u1->id)->pet);
|
||||
$u2 = $DB->get_record('user', array('username'=>$user2->name, 'auth'=>'db'));
|
||||
$this->assertSame($user2->email, $u2->email);
|
||||
$this->assertSame($user2->animal, profile_user_record($u2->id)->pet);
|
||||
$admin = $DB->get_record('user', array('username'=>'admin', 'auth'=>'manual'));
|
||||
$this->assertNotEmpty($admin);
|
||||
|
||||
@@ -217,12 +239,14 @@ class auth_db_testcase extends advanced_testcase {
|
||||
|
||||
$user2b = clone($user2);
|
||||
$user2b->email = 'u2b@example.com';
|
||||
$user2b->animal = 'dog';
|
||||
$DB->update_record('auth_db_users', $user2b);
|
||||
|
||||
$auth->sync_users($trace, false);
|
||||
$this->assertEquals(4, $DB->count_records('user'));
|
||||
$u2 = $DB->get_record('user', array('username'=>$user2->name));
|
||||
$this->assertSame($user2->email, $u2->email);
|
||||
$this->assertSame($user2->animal, profile_user_record($u2->id)->pet);
|
||||
|
||||
$auth->sync_users($trace, true);
|
||||
$this->assertEquals(4, $DB->count_records('user'));
|
||||
@@ -231,6 +255,8 @@ class auth_db_testcase extends advanced_testcase {
|
||||
|
||||
set_config('field_updatelocal_email', 'onlogin', 'auth_db');
|
||||
$auth->config->field_updatelocal_email = 'onlogin';
|
||||
set_config('field_updatelocal_profile_field_pet', 'onlogin', 'auth_db');
|
||||
$auth->config->field_updatelocal_profile_field_pet = 'onlogin';
|
||||
|
||||
$auth->sync_users($trace, false);
|
||||
$this->assertEquals(4, $DB->count_records('user'));
|
||||
@@ -241,6 +267,7 @@ class auth_db_testcase extends advanced_testcase {
|
||||
$this->assertEquals(4, $DB->count_records('user'));
|
||||
$u2 = $DB->get_record('user', array('username'=>$user2->name));
|
||||
$this->assertSame($user2b->email, $u2->email);
|
||||
$this->assertSame($user2b->animal, profile_user_record($u2->id)->pet);
|
||||
|
||||
|
||||
// Test sync deletes and suspends.
|
||||
|
||||
@@ -147,12 +147,12 @@ class auth_email_external extends external_api {
|
||||
array(
|
||||
'id' => new external_value(PARAM_INT, 'Profile field id', VALUE_OPTIONAL),
|
||||
'shortname' => new external_value(PARAM_ALPHANUMEXT, 'Profile field shortname', VALUE_OPTIONAL),
|
||||
'name' => new external_value(PARAM_TEXT, 'Profield field name', VALUE_OPTIONAL),
|
||||
'name' => new external_value(PARAM_RAW, 'Profield field name', VALUE_OPTIONAL),
|
||||
'datatype' => new external_value(PARAM_ALPHANUMEXT, 'Profield field datatype', VALUE_OPTIONAL),
|
||||
'description' => new external_value(PARAM_RAW, 'Profield field description', VALUE_OPTIONAL),
|
||||
'descriptionformat' => new external_format_value('description'),
|
||||
'categoryid' => new external_value(PARAM_INT, 'Profield field category id', VALUE_OPTIONAL),
|
||||
'categoryname' => new external_value(PARAM_TEXT, 'Profield field category name', VALUE_OPTIONAL),
|
||||
'categoryname' => new external_value(PARAM_RAW, 'Profield field category name', VALUE_OPTIONAL),
|
||||
'sortorder' => new external_value(PARAM_INT, 'Profield field sort order', VALUE_OPTIONAL),
|
||||
'required' => new external_value(PARAM_INT, 'Profield field required', VALUE_OPTIONAL),
|
||||
'locked' => new external_value(PARAM_INT, 'Profield field locked', VALUE_OPTIONAL),
|
||||
|
||||
@@ -93,6 +93,51 @@ class auth_email_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals('textarea', $namedarray['sometext']['datatype']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_signup_settings with mathjax in a profile field.
|
||||
*/
|
||||
public function test_get_signup_settings_with_mathjax_in_profile_fields() {
|
||||
global $CFG, $DB;
|
||||
|
||||
require_once($CFG->dirroot . '/lib/externallib.php');
|
||||
|
||||
// Enable MathJax filter in content and headings.
|
||||
$this->configure_filters([
|
||||
['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
|
||||
]);
|
||||
|
||||
// Create category with MathJax and a new field with MathJax.
|
||||
$categoryname = 'Cat $$(a+b)=2$$';
|
||||
$fieldname = 'Some text $$(a+b)=2$$';
|
||||
$categoryid = $DB->insert_record('user_info_category', array('name' => $categoryname, 'sortorder' => 1));
|
||||
$field3 = $DB->insert_record('user_info_field', array(
|
||||
'shortname' => 'mathjaxname', 'name' => $fieldname, 'categoryid' => $categoryid,
|
||||
'datatype' => 'textarea', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 2));
|
||||
|
||||
$result = auth_email_external::get_signup_settings();
|
||||
$result = external_api::clean_returnvalue(auth_email_external::get_signup_settings_returns(), $result);
|
||||
|
||||
// Format the original data.
|
||||
$sitecontext = context_system::instance();
|
||||
$categoryname = external_format_string($categoryname, $sitecontext->id);
|
||||
$fieldname = external_format_string($fieldname, $sitecontext->id);
|
||||
|
||||
// Whip up a array with named entries to easily check against.
|
||||
$namedarray = array();
|
||||
foreach ($result['profilefields'] as $key => $value) {
|
||||
$namedarray[$value['shortname']] = $value;
|
||||
}
|
||||
|
||||
// Check the new profile field.
|
||||
$this->assertArrayHasKey('mathjaxname', $namedarray);
|
||||
$this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
|
||||
$namedarray['mathjaxname']['categoryname']);
|
||||
$this->assertStringContainsString('<span class="filter_mathjaxloader_equation">',
|
||||
$namedarray['mathjaxname']['name']);
|
||||
$this->assertEquals($categoryname, $namedarray['mathjaxname']['categoryname']);
|
||||
$this->assertEquals($fieldname, $namedarray['mathjaxname']['name']);
|
||||
}
|
||||
|
||||
public function test_signup_user() {
|
||||
global $DB;
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ require_once('../../config.php');
|
||||
$issuerid = required_param('id', PARAM_INT);
|
||||
$wantsurl = new moodle_url(optional_param('wantsurl', '', PARAM_URL));
|
||||
|
||||
$PAGE->set_context(context_system::instance());
|
||||
$PAGE->set_url(new moodle_url('/auth/oauth2/login.php', ['id' => $issuerid]));
|
||||
|
||||
require_sesskey();
|
||||
|
||||
if (!\auth_oauth2\api::is_enabled()) {
|
||||
|
||||
@@ -182,7 +182,8 @@ How to customize the way the Shibboleth user data is used in Moodle
|
||||
Among the Shibboleth settings in Moodle there is a field that should contain a
|
||||
path to a php file that can be used as data manipulation hook.
|
||||
You can use this if you want to further process the way your Shibboleth
|
||||
attributes are used in Moodle.
|
||||
attributes are used in Moodle. Due to security reasons this file cannot be
|
||||
located within the current site data directory ($CFG->dataroot).
|
||||
|
||||
Example 1: Your Shibboleth federation uses an attribute that specifies the
|
||||
user's preferred language, but the content of this attribute is not
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Special setting for auth_shibboleth convert_data.
|
||||
*
|
||||
* @package auth_shibboleth
|
||||
* @copyright 2020 Mihail Geshoski
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Admin settings class for the convert_data option.
|
||||
*
|
||||
* @package auth_shibboleth
|
||||
* @copyright 2020 Mihail Geshoski
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class auth_shibboleth_admin_setting_convert_data extends admin_setting_configfile {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $visiblename
|
||||
* @param string $description
|
||||
* @param mixed $defaultdirectory
|
||||
*/
|
||||
public function __construct($name, $visiblename, $description, $defaultdirectory) {
|
||||
parent::__construct($name, $visiblename, $description, $defaultdirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the file path (location).
|
||||
*
|
||||
* This method ensures that the file defined as a data modification API exists and is not located in the site
|
||||
* data directory ($CFG->dataroot). We should prohibit using files from the site data directory as this introduces
|
||||
* security vulnerabilities.
|
||||
*
|
||||
* @param string $filepath The path to the file.
|
||||
* @return mixed bool true for success or string:error on failure.
|
||||
*/
|
||||
public function validate($filepath) {
|
||||
global $CFG;
|
||||
|
||||
if (empty($filepath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fail if the file does not exist or it is not readable by the webserver process.
|
||||
if (!is_readable($filepath)) {
|
||||
return get_string('auth_shib_convert_data_warning', 'auth_shibboleth');
|
||||
}
|
||||
|
||||
// Fail if the absolute file path matches the currently defined dataroot path.
|
||||
if (preg_match('/' . preg_quote($CFG->dataroot, '/') . '/', realpath($filepath))) {
|
||||
return get_string('auth_shib_convert_data_filepath_warning', 'auth_shibboleth');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @return bool result
|
||||
*/
|
||||
function xmldb_auth_shibboleth_upgrade($oldversion) {
|
||||
global $CFG, $DB;
|
||||
global $CFG, $DB, $OUTPUT;
|
||||
|
||||
// Automatically generated Moodle v3.5.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
@@ -47,5 +47,27 @@ function xmldb_auth_shibboleth_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.9.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2020061501) {
|
||||
// The 'Data modification API' setting in the Shibboleth authentication plugin can no longer be configured
|
||||
// to use files located within the site data directory, as it exposes the site to security risks. Therefore,
|
||||
// we need to find every existing case and reset the 'Data modification API' setting to its default value.
|
||||
|
||||
$convertdataconfig = get_config('auth_shibboleth', 'convert_data');
|
||||
|
||||
if (preg_match('/' . preg_quote($CFG->dataroot, '/') . '/', realpath($convertdataconfig))) {
|
||||
set_config('convert_data', '', 'auth_shibboleth');
|
||||
|
||||
$warn = 'Your \'Data modification API\' setting in the Shibboleth authentication plugin is currently
|
||||
configured to use a file located within the current site data directory ($CFG->dataroot). You are no
|
||||
longer able to use files from within this directory for this purpose as it exposes your site to security
|
||||
risks. This setting has been reset to its default value. Please reconfigure it by providing a path
|
||||
to a file which is not located within the site data directory.';
|
||||
|
||||
echo $OUTPUT->notification($warn, 'notifyproblem');
|
||||
}
|
||||
|
||||
upgrade_plugin_savepoint(true, 2020061501, 'auth', 'shibboleth');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ $string['auth_shibboleth_select_organization'] = 'For authentication via Shibbol
|
||||
$string['auth_shib_convert_data'] = 'Data modification API';
|
||||
$string['auth_shib_convert_data_description'] = 'You can use this API to further modify the data provided by Shibboleth. Read the <a href="{$a}">README</a> for further instructions.';
|
||||
$string['auth_shib_convert_data_warning'] = 'The file does not exist or is not readable by the webserver process!';
|
||||
$string['auth_shib_convert_data_filepath_warning'] = 'You cannot use a file that is located within the current site data directory ($CFG->dataroot) as the data modification API.';
|
||||
$string['auth_shib_changepasswordurl'] = 'Password-change URL';
|
||||
$string['auth_shib_idp_list'] = 'Identity providers';
|
||||
$string['auth_shib_idp_list_description'] = 'Provide a list of Identity Provider entityIDs to let the user choose from on the login page.<br />On each line there must be a comma-separated tuple for entityID of the IdP (see the Shibboleth metadata file) and Name of IdP as it shall be displayed in the drop-down list.<br />As an optional third parameter you can add the location of a Shibboleth session initiator that shall be used in case your Moodle installation is part of a multi federation setup.';
|
||||
|
||||
@@ -28,6 +28,7 @@ if ($ADMIN->fulltree) {
|
||||
// We use a couple of custom admin settings since we need to massage the data before it is inserted into the DB.
|
||||
require_once($CFG->dirroot.'/auth/shibboleth/classes/admin_setting_special_wayf_select.php');
|
||||
require_once($CFG->dirroot.'/auth/shibboleth/classes/admin_setting_special_idp_configtextarea.php');
|
||||
require_once($CFG->dirroot.'/auth/shibboleth/classes/admin_setting_special_convert_data_configfile.php');
|
||||
|
||||
// Introductory explanation.
|
||||
$readmeurl = (new moodle_url('/auth/shibboleth/README.txt'))->out();
|
||||
@@ -38,8 +39,8 @@ if ($ADMIN->fulltree) {
|
||||
$settings->add(new admin_setting_configtext('auth_shibboleth/user_attribute', get_string('username'),
|
||||
get_string('auth_shib_username_description', 'auth_shibboleth'), '', PARAM_RAW));
|
||||
|
||||
// COnvert Data configuration file.
|
||||
$settings->add(new admin_setting_configfile('auth_shibboleth/convert_data',
|
||||
// Convert Data configuration file.
|
||||
$settings->add(new auth_shibboleth_admin_setting_convert_data('auth_shibboleth/convert_data',
|
||||
get_string('auth_shib_convert_data', 'auth_shibboleth'),
|
||||
get_string('auth_shib_convert_data_description', 'auth_shibboleth', $readmeurl), ''));
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
This files describes API changes in /auth/shibboleth/*,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.9.4 ===
|
||||
|
||||
* The 'Data modification API' (convert_data) setting can no longer be configured to use files located within the
|
||||
current site data directory ($CFG->dataroot), as it exposes the site to security risks.
|
||||
|
||||
=== 3.5.2 ===
|
||||
|
||||
* Moved the public function unserializesession in auth/shibboleth/logout.php to auth/shibboleth/classes/helper.php and
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2020061500; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2020061501; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2020060900; // Requires this Moodle version
|
||||
$plugin->component = 'auth_shibboleth'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -794,7 +794,7 @@ class cc2moodle {
|
||||
}
|
||||
}
|
||||
|
||||
protected function critical_error ($text) {
|
||||
protected static function critical_error ($text) {
|
||||
|
||||
$path_to_log = static::log_file();
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class entities {
|
||||
cc2moodle::log_action('Load the XML resource file: '.$path_to_file);
|
||||
|
||||
if (!$resource->load($path_to_file)) {
|
||||
cc2moodle::log_action('Cannot load the XML resource file: ' . $path_to_file, true);
|
||||
cc2moodle::log_action('Cannot load the XML resource file: ' . $path_to_file, false);
|
||||
}
|
||||
|
||||
return $resource;
|
||||
|
||||
@@ -369,6 +369,7 @@ class restore_controller extends base_controller {
|
||||
$options = array();
|
||||
$options['keep_roles_and_enrolments'] = $this->get_setting_value('keep_roles_and_enrolments');
|
||||
$options['keep_groups_and_groupings'] = $this->get_setting_value('keep_groups_and_groupings');
|
||||
$options['userid'] = $this->userid;
|
||||
restore_dbops::delete_course_content($this->get_courseid(), $options);
|
||||
}
|
||||
// If this is not a course restore or single activity restore (e.g. duplicate), inform the plan we are not
|
||||
|
||||
@@ -272,7 +272,7 @@ abstract class backup_activity_task extends backup_task {
|
||||
$settingname = $settingprefix . 'included';
|
||||
$activity_included = new backup_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true);
|
||||
$activity_included->get_ui()->set_icon(new image_icon('icon', get_string('pluginname', $this->modulename),
|
||||
$this->modulename, array('class' => 'iconlarge icon-post')));
|
||||
$this->modulename, array('class' => 'iconlarge icon-post ml-1')));
|
||||
$this->add_setting($activity_included);
|
||||
// Look for "activities" root setting
|
||||
$activities = $this->plan->get_setting('activities');
|
||||
|
||||
@@ -297,7 +297,7 @@ abstract class restore_activity_task extends restore_task {
|
||||
$settingname = $settingprefix . 'included';
|
||||
$activity_included = new restore_activity_generic_setting($settingname, base_setting::IS_BOOLEAN, true);
|
||||
$activity_included->get_ui()->set_icon(new image_icon('icon', get_string('pluginname', $this->modulename),
|
||||
$this->modulename, array('class' => 'iconlarge icon-post')));
|
||||
$this->modulename, array('class' => 'iconlarge icon-post ml-1')));
|
||||
$this->add_setting($activity_included);
|
||||
// Look for "activities" root setting
|
||||
$activities = $this->plan->get_setting('activities');
|
||||
|
||||
@@ -2112,14 +2112,29 @@ class restore_ras_and_caps_structure_step extends restore_structure_step {
|
||||
$data = (object)$data;
|
||||
|
||||
// Check roleid is one of the mapped ones
|
||||
$newroleid = $this->get_mappingid('role', $data->roleid);
|
||||
$newrole = $this->get_mapping('role', $data->roleid);
|
||||
$newroleid = $newrole->newitemid ?? false;
|
||||
$userid = $this->task->get_userid();
|
||||
|
||||
// If newroleid and context are valid assign it via API (it handles dupes and so on)
|
||||
if ($newroleid && $this->task->get_contextid()) {
|
||||
if (!get_capability_info($data->capability)) {
|
||||
if (!$capability = get_capability_info($data->capability)) {
|
||||
$this->log("Capability '{$data->capability}' was not found!", backup::LOG_WARNING);
|
||||
} else {
|
||||
// TODO: assign_capability() needs one userid param to be able to specify our restore userid.
|
||||
assign_capability($data->capability, $data->permission, $newroleid, $this->task->get_contextid());
|
||||
$context = context::instance_by_id($this->task->get_contextid());
|
||||
$overrideableroles = get_overridable_roles($context, ROLENAME_SHORT);
|
||||
$safecapability = is_safe_capability($capability);
|
||||
|
||||
// Check if the new role is an overrideable role AND if the user performing the restore has the
|
||||
// capability to assign the capability.
|
||||
if (in_array($newrole->info['shortname'], $overrideableroles) &&
|
||||
($safecapability && has_capability('moodle/role:safeoverride', $context, $userid) ||
|
||||
!$safecapability && has_capability('moodle/role:override', $context, $userid))
|
||||
) {
|
||||
assign_capability($data->capability, $data->permission, $newroleid, $this->task->get_contextid());
|
||||
} else {
|
||||
$this->log("Insufficient capability to assign capability '{$data->capability}' to role!", backup::LOG_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2139,11 +2154,22 @@ class restore_default_enrolments_step extends restore_execution_step {
|
||||
}
|
||||
|
||||
$course = $DB->get_record('course', array('id'=>$this->get_courseid()), '*', MUST_EXIST);
|
||||
// Return any existing course enrolment instances.
|
||||
$enrolinstances = enrol_get_instances($course->id, false);
|
||||
|
||||
if ($enrolinstances) {
|
||||
// Something already added instances.
|
||||
// Get the existing enrolment methods in the course.
|
||||
$enrolmethods = array_map(function($enrolinstance) {
|
||||
return $enrolinstance->enrol;
|
||||
}, $enrolinstances);
|
||||
|
||||
if ($DB->record_exists('enrol', array('courseid'=>$this->get_courseid(), 'enrol'=>'manual'))) {
|
||||
// Something already added instances, do not add default instances.
|
||||
$plugins = enrol_get_plugins(true);
|
||||
foreach ($plugins as $plugin) {
|
||||
foreach ($plugins as $pluginname => $plugin) {
|
||||
// Make sure all default enrolment methods exist in the course.
|
||||
if (!in_array($pluginname, $enrolmethods)) {
|
||||
$plugin->course_updated(true, $course, null);
|
||||
}
|
||||
$plugin->restore_sync_course($course);
|
||||
}
|
||||
|
||||
@@ -4702,10 +4728,28 @@ class restore_create_categories_and_questions extends restore_structure_step {
|
||||
}
|
||||
|
||||
$userid = $this->get_mappingid('user', $data->createdby);
|
||||
$data->createdby = $userid ? $userid : $this->task->get_userid();
|
||||
if ($userid) {
|
||||
// The question creator is included in the backup, so we can use their mapping id.
|
||||
$data->createdby = $userid;
|
||||
} else {
|
||||
// Leave the question creator unchanged when we are restoring the same site.
|
||||
// Otherwise use current user id.
|
||||
if (!$this->task->is_samesite()) {
|
||||
$data->createdby = $this->task->get_userid();
|
||||
}
|
||||
}
|
||||
|
||||
$userid = $this->get_mappingid('user', $data->modifiedby);
|
||||
$data->modifiedby = $userid ? $userid : $this->task->get_userid();
|
||||
if ($userid) {
|
||||
// The question modifier is included in the backup, so we can use their mapping id.
|
||||
$data->modifiedby = $userid;
|
||||
} else {
|
||||
// Leave the question modifier unchanged when we are restoring the same site.
|
||||
// Otherwise use current user id.
|
||||
if (!$this->task->is_samesite()) {
|
||||
$data->modifiedby = $this->task->get_userid();
|
||||
}
|
||||
}
|
||||
|
||||
// With newitemid = 0, let's create the question
|
||||
if (!$questionmapping->newitemid) {
|
||||
|
||||
@@ -82,16 +82,17 @@ class restore_quiz_decode_testcase extends \core_privacy\tests\provider_testcase
|
||||
|
||||
$newcm = duplicate_module($course, get_fast_modinfo($course)->get_cm($quiz->cmid));
|
||||
|
||||
$sql = "SELECT qa.answer
|
||||
$sql = "SELECT qa.id, qa.answer
|
||||
FROM {quiz} q
|
||||
LEFT JOIN {quiz_slots} qs ON qs.quizid = q.id
|
||||
LEFT JOIN {question_answers} qa ON qa.question = qs.questionid
|
||||
WHERE q.id = :quizid";
|
||||
$params = array('quizid' => $newcm->instance);
|
||||
$answers = $DB->get_fieldset_sql($sql, $params);
|
||||
$this->assertEquals($CFG->wwwroot . '/course/view.php?id=' . $course->id, $answers[0]);
|
||||
$this->assertEquals($CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid, $answers[1]);
|
||||
$this->assertEquals($CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid, $answers[2]);
|
||||
$this->assertEquals($CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid, $answers[3]);
|
||||
$answers = $DB->get_records_sql_menu($sql, $params);
|
||||
|
||||
$this->assertEquals($CFG->wwwroot . '/course/view.php?id=' . $course->id, $answers[$firstanswer->id]);
|
||||
$this->assertEquals($CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid, $answers[$secondanswer->id]);
|
||||
$this->assertEquals($CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid, $answers[$thirdanswer->id]);
|
||||
$this->assertEquals($CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid, $answers[$fourthanswer->id]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ abstract class base_moodleform extends moodleform {
|
||||
$label = format_string($settingui->get_label($task), true, array('context' => $context));
|
||||
$labelicon = $settingui->get_icon();
|
||||
if (!empty($labelicon)) {
|
||||
$label .= ' '.$OUTPUT->render($labelicon);
|
||||
$label .= $OUTPUT->render($labelicon);
|
||||
}
|
||||
$this->_form->addElement('static', 'static_'.$settingui->get_name(), $label, $settingui->get_static_value().$icon);
|
||||
$this->_form->addElement('html', html_writer::end_tag('div'));
|
||||
|
||||
@@ -152,13 +152,14 @@ class copy_form extends \moodleform {
|
||||
$mform->addHelpButton('relativedatesmodegroup', 'relativedatesmode');
|
||||
}
|
||||
|
||||
// Course id number.
|
||||
// Course ID number (default to the current course ID number; blank for users who can't change ID numbers).
|
||||
$mform->addElement('text', 'idnumber', get_string('idnumbercourse'), 'maxlength="100" size="10"');
|
||||
$mform->setDefault('idnumber', $course->idnumber);
|
||||
$mform->addHelpButton('idnumber', 'idnumbercourse');
|
||||
$mform->setType('idnumber', PARAM_RAW);
|
||||
if (!empty($course->id) and !has_capability('moodle/course:changeidnumber', $coursecontext)) {
|
||||
if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
|
||||
$mform->hardFreeze('idnumber');
|
||||
$mform->setConstants('idnumber', $course->idnumber);
|
||||
$mform->setConstant('idnumber', '');
|
||||
}
|
||||
|
||||
// Keep source course user data.
|
||||
@@ -172,7 +173,7 @@ class copy_form extends \moodleform {
|
||||
);
|
||||
if (!has_all_capabilities($requiredcapabilities, $coursecontext)) {
|
||||
$mform->hardFreeze('userdata');
|
||||
$mform->setConstants('userdata', 0);
|
||||
$mform->setConstant('userdata', 0);
|
||||
}
|
||||
|
||||
// Keep manual enrolments.
|
||||
|
||||
+73
-33
@@ -126,8 +126,9 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
|
||||
$html = html_writer::start_tag('div', array('class' => 'backup-restore'));
|
||||
|
||||
$html .= html_writer::start_tag('div', array('class' => 'backup-section'));
|
||||
$html .= $this->output->heading(get_string('backupdetails', 'backup'), 2, array('class' => 'header'));
|
||||
$html .= html_writer::start_tag('div', ['class' => 'backup-section',
|
||||
'role' => 'table', 'aria-labelledby' => 'backupdetailsheader']);
|
||||
$html .= $this->output->heading(get_string('backupdetails', 'backup'), 2, 'header', 'backupdetailsheader');
|
||||
$html .= $this->backup_detail_pair(get_string('backuptype', 'backup'), get_string('backuptype'.$details->type, 'backup'));
|
||||
$html .= $this->backup_detail_pair(get_string('backupformat', 'backup'), get_string('backupformat'.$details->format, 'backup'));
|
||||
$html .= $this->backup_detail_pair(get_string('backupmode', 'backup'), get_string('backupmode'.$details->mode, 'backup'));
|
||||
@@ -153,8 +154,9 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
|
||||
$html .= html_writer::end_tag('div');
|
||||
|
||||
$html .= html_writer::start_tag('div', array('class' => 'backup-section settings-section'));
|
||||
$html .= $this->output->heading(get_string('backupsettings', 'backup'), 2, array('class' => 'header'));
|
||||
$html .= html_writer::start_tag('div', ['class' => 'backup-section settings-section',
|
||||
'role' => 'table', 'aria-labelledby' => 'backupsettingsheader']);
|
||||
$html .= $this->output->heading(get_string('backupsettings', 'backup'), 2, 'header', 'backupsettingsheader');
|
||||
foreach ($details->root_settings as $label => $value) {
|
||||
if ($label == 'filename' or $label == 'user_files') {
|
||||
continue;
|
||||
@@ -164,8 +166,9 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$html .= html_writer::end_tag('div');
|
||||
|
||||
if ($details->type === 'course') {
|
||||
$html .= html_writer::start_tag('div', array('class' => 'backup-section'));
|
||||
$html .= $this->output->heading(get_string('backupcoursedetails', 'backup'), 2, array('class' => 'header'));
|
||||
$html .= html_writer::start_tag('div', ['class' => 'backup-section',
|
||||
'role' => 'table', 'aria-labelledby' => 'backupcoursedetailsheader']);
|
||||
$html .= $this->output->heading(get_string('backupcoursedetails', 'backup'), 2, 'header', 'backupcoursedetailsheader');
|
||||
$html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), $details->course->title);
|
||||
$html .= $this->backup_detail_pair(get_string('courseid', 'backup'), $details->course->courseid);
|
||||
|
||||
@@ -200,7 +203,7 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$table->data = array();
|
||||
}
|
||||
$name = get_string('pluginname', $activity->modulename);
|
||||
$icon = new image_icon('icon', $name, $activity->modulename, array('class' => 'iconlarge icon-pre'));
|
||||
$icon = new image_icon('icon', '', $activity->modulename, ['class' => 'iconlarge icon-pre']);
|
||||
$table->data[] = array(
|
||||
$this->output->render($icon).$name,
|
||||
$activity->title,
|
||||
@@ -424,13 +427,25 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
protected function backup_detail_pair($label, $value) {
|
||||
static $count = 0;
|
||||
$count ++;
|
||||
$html = html_writer::start_tag('div', array('class' => 'detail-pair'));
|
||||
$html .= html_writer::tag('label', $label, array('class' => 'detail-pair-label', 'for' => 'detail-pair-value-'.$count));
|
||||
$html .= html_writer::tag('div', $value, array('class' => 'detail-pair-value pl-2', 'name' => 'detail-pair-value-'.$count));
|
||||
$html = html_writer::start_tag('div', ['class' => 'detail-pair', 'role' => 'row']);
|
||||
$html .= html_writer::tag('div', $label, ['class' => 'detail-pair-label mb-2', 'role' => 'cell']);
|
||||
$html .= html_writer::tag('div', $value, ['class' => 'detail-pair-value pl-2', 'role' => 'cell']);
|
||||
$html .= html_writer::end_tag('div');
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique id string by appending an incremental number to the prefix.
|
||||
*
|
||||
* @param string $prefix To be used as the left part of the id string.
|
||||
* @return string
|
||||
*/
|
||||
protected function make_unique_id(string $prefix): string {
|
||||
static $count = 0;
|
||||
|
||||
return $prefix . '-' . $count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Created a detailed pairing with an input
|
||||
*
|
||||
@@ -448,9 +463,11 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
} else {
|
||||
$description = '';
|
||||
}
|
||||
$id = $this->make_unique_id('detail-pair-value');
|
||||
return $this->backup_detail_pair(
|
||||
$label,
|
||||
html_writer::empty_tag('input', $attributes + array('name' => $name, 'type' => $type, 'value' => $value)) . $description
|
||||
html_writer::label($label, $id),
|
||||
html_writer::empty_tag('input', $attributes + ['id' => $id, 'name' => $name, 'type' => $type, 'value' => $value]) .
|
||||
$description
|
||||
);
|
||||
}
|
||||
|
||||
@@ -718,8 +735,6 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function render_restore_course_search(restore_course_search $component) {
|
||||
$url = $component->get_url();
|
||||
|
||||
$output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline mb-1'));
|
||||
$output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75'));
|
||||
|
||||
@@ -733,11 +748,18 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
if (!$course->visible) {
|
||||
$row->attributes['class'] .= ' dimmed';
|
||||
}
|
||||
$row->cells = array(
|
||||
html_writer::empty_tag('input', array('type' => 'radio', 'name' => 'targetid', 'value' => $course->id)),
|
||||
format_string($course->shortname, true, array('context' => context_course::instance($course->id))),
|
||||
format_string($course->fullname, true, array('context' => context_course::instance($course->id)))
|
||||
);
|
||||
$id = $this->make_unique_id('restore-course');
|
||||
$row->cells = [
|
||||
html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'targetid', 'value' => $course->id,
|
||||
'id' => $id]),
|
||||
html_writer::label(
|
||||
format_string($course->shortname, true, ['context' => context_course::instance($course->id)]),
|
||||
$id,
|
||||
true,
|
||||
['class' => 'd-block']
|
||||
),
|
||||
format_string($course->fullname, true, ['context' => context_course::instance($course->id)])
|
||||
];
|
||||
$table->data[] = $row;
|
||||
}
|
||||
if ($component->has_more_results()) {
|
||||
@@ -764,6 +786,8 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
'type' => 'text',
|
||||
'name' => restore_course_search::$VAR_SEARCH,
|
||||
'value' => $component->get_search(),
|
||||
'aria-label' => get_string('searchcourses'),
|
||||
'placeholder' => get_string('searchcourses'),
|
||||
'class' => 'form-control'
|
||||
);
|
||||
$output .= html_writer::empty_tag('input', $attrs);
|
||||
@@ -787,8 +811,6 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function render_import_course_search(import_course_search $component) {
|
||||
$url = $component->get_url();
|
||||
|
||||
$output = html_writer::start_tag('div', array('class' => 'import-course-search'));
|
||||
if ($component->get_count() === 0) {
|
||||
$output .= $this->output->notification(get_string('nomatchingcourses', 'backup'));
|
||||
@@ -798,6 +820,8 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
'type' => 'text',
|
||||
'name' => restore_course_search::$VAR_SEARCH,
|
||||
'value' => $component->get_search(),
|
||||
'aria-label' => get_string('searchcourses'),
|
||||
'placeholder' => get_string('searchcourses'),
|
||||
'class' => 'form-control'
|
||||
);
|
||||
$output .= html_writer::empty_tag('input', $attrs);
|
||||
@@ -833,11 +857,18 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
if (!$course->visible) {
|
||||
$row->attributes['class'] .= ' dimmed';
|
||||
}
|
||||
$row->cells = array(
|
||||
html_writer::empty_tag('input', array('type' => 'radio', 'name' => 'importid', 'value' => $course->id)),
|
||||
format_string($course->shortname, true, array('context' => context_course::instance($course->id))),
|
||||
format_string($course->fullname, true, array('context' => context_course::instance($course->id)))
|
||||
);
|
||||
$id = $this->make_unique_id('import-course');
|
||||
$row->cells = [
|
||||
html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'importid', 'value' => $course->id,
|
||||
'id' => $id]),
|
||||
html_writer::label(
|
||||
format_string($course->shortname, true, ['context' => context_course::instance($course->id)]),
|
||||
$id,
|
||||
true,
|
||||
['class' => 'd-block']
|
||||
),
|
||||
format_string($course->fullname, true, ['context' => context_course::instance($course->id)])
|
||||
];
|
||||
$table->data[] = $row;
|
||||
}
|
||||
if ($component->has_more_results()) {
|
||||
@@ -856,6 +887,8 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
'type' => 'text',
|
||||
'name' => restore_course_search::$VAR_SEARCH,
|
||||
'value' => $component->get_search(),
|
||||
'aria-label' => get_string('searchcourses'),
|
||||
'placeholder' => get_string('searchcourses'),
|
||||
'class' => 'form-control');
|
||||
$output .= html_writer::empty_tag('input', $attrs);
|
||||
$attrs = array(
|
||||
@@ -878,8 +911,6 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function render_restore_category_search(restore_category_search $component) {
|
||||
$url = $component->get_url();
|
||||
|
||||
$output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline mb-1'));
|
||||
$output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75'));
|
||||
|
||||
@@ -895,12 +926,19 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$row->attributes['class'] .= ' dimmed';
|
||||
}
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$row->cells = array(
|
||||
html_writer::empty_tag('input', array('type' => 'radio', 'name' => 'targetid', 'value' => $category->id)),
|
||||
format_string($category->name, true, array('context' => context_coursecat::instance($category->id))),
|
||||
$id = $this->make_unique_id('restore-category');
|
||||
$row->cells = [
|
||||
html_writer::empty_tag('input', ['type' => 'radio', 'name' => 'targetid', 'value' => $category->id,
|
||||
'id' => $id]),
|
||||
html_writer::label(
|
||||
format_string($category->name, true, ['context' => context_coursecat::instance($category->id)]),
|
||||
$id,
|
||||
true,
|
||||
['class' => 'd-block']
|
||||
),
|
||||
format_text(file_rewrite_pluginfile_urls($category->description, 'pluginfile.php', $context->id,
|
||||
'coursecat', 'description', null), $category->descriptionformat, array('overflowdiv' => true))
|
||||
);
|
||||
'coursecat', 'description', null), $category->descriptionformat, ['overflowdiv' => true])
|
||||
];
|
||||
$table->data[] = $row;
|
||||
}
|
||||
if ($component->has_more_results()) {
|
||||
@@ -927,6 +965,8 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
'type' => 'text',
|
||||
'name' => restore_category_search::$VAR_SEARCH,
|
||||
'value' => $component->get_search(),
|
||||
'aria-label' => get_string('searchcoursecategories'),
|
||||
'placeholder' => get_string('searchcoursecategories'),
|
||||
'class' => 'form-control'
|
||||
);
|
||||
$output .= html_writer::empty_tag('input', $attrs);
|
||||
|
||||
+20
-6
@@ -39,6 +39,20 @@ use stdClass;
|
||||
*/
|
||||
class assertion_exporter extends exporter {
|
||||
|
||||
/**
|
||||
* Constructor - saves the persistent object, and the related objects.
|
||||
*
|
||||
* @param mixed $data - Either an stdClass or an array of values.
|
||||
* @param array $related - An optional list of pre-loaded objects related to this object.
|
||||
*/
|
||||
public function __construct($data, $related = array()) {
|
||||
// Having mixed $data is causing some issues. As this class is treating $data as an object everywhere, it can be converted
|
||||
// to object at this point, to avoid errors and get the expected behaviour always.
|
||||
// $data is an array when this class is a request exporter in backpack_api_mapping, but it is an object when this is
|
||||
// used as a response exporter.
|
||||
parent::__construct((object) $data, $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map from a request response data to the internal structure.
|
||||
*
|
||||
@@ -104,16 +118,16 @@ class assertion_exporter extends exporter {
|
||||
global $DB;
|
||||
$result = [];
|
||||
|
||||
if (array_key_exists('related_badge', $this->data)) {
|
||||
$exporter = new badgeclass_exporter($this->data['related_badge'], $this->related);
|
||||
if (property_exists($this->data, 'related_badge')) {
|
||||
$exporter = new badgeclass_exporter($this->data->related_badge, $this->related);
|
||||
$result['badge'] = $exporter->export($output);
|
||||
}
|
||||
if (array_key_exists('related_recipient', $this->data)) {
|
||||
$exporter = new recipient_exporter($this->data['related_recipient'], $this->related);
|
||||
if (property_exists($this->data, 'related_recipient')) {
|
||||
$exporter = new recipient_exporter($this->data->related_recipient, $this->related);
|
||||
$result['recipient'] = $exporter->export($output);
|
||||
}
|
||||
if (array_key_exists('related_verify', $this->data)) {
|
||||
$exporter = new verification_exporter($this->data['related_verify'], $this->related);
|
||||
if (property_exists($this->data, 'related_verify')) {
|
||||
$exporter = new verification_exporter($this->data->related_verify, $this->related);
|
||||
$result['verification'] = $exporter->export($output);
|
||||
}
|
||||
return $result;
|
||||
|
||||
+14
-8
@@ -45,6 +45,12 @@ class badgeclass_exporter extends exporter {
|
||||
* @param array $related - An optional list of pre-loaded objects related to this object.
|
||||
*/
|
||||
public function __construct($data, $related = array()) {
|
||||
// Having mixed $data is causing some issues. As this class is treating $data as an object everywhere, it can be converted
|
||||
// to object at this point, to avoid errors and get the expected behaviour always.
|
||||
// $data is an array when this class is a request exporter in backpack_api_mapping, but it is an object when this is
|
||||
// used as a response exporter.
|
||||
$data = (object) $data;
|
||||
|
||||
$pick = $this->pick_related();
|
||||
foreach ($pick as $one) {
|
||||
$isarray = false;
|
||||
@@ -54,28 +60,28 @@ class badgeclass_exporter extends exporter {
|
||||
$isarray = true;
|
||||
}
|
||||
$prefixed = 'related_' . $one;
|
||||
if (array_key_exists($one, $data) && !array_key_exists($one, $related)) {
|
||||
if (property_exists($data, $one) && !array_key_exists($one, $related)) {
|
||||
if ($isarray) {
|
||||
$newrelated = [];
|
||||
foreach ($data[$one] as $item) {
|
||||
foreach ($data->$one as $item) {
|
||||
$newrelated[] = (object) $item;
|
||||
}
|
||||
$related[$one] = $newrelated;
|
||||
} else {
|
||||
$related[$one] = (object) $data[$one];
|
||||
$related[$one] = (object) $data->$one;
|
||||
}
|
||||
unset($data[$one]);
|
||||
} else if (array_key_exists($prefixed, $data) && !array_key_exists($one, $related)) {
|
||||
unset($data->$one);
|
||||
} else if (property_exists($data, $prefixed) && !array_key_exists($one, $related)) {
|
||||
if ($isarray) {
|
||||
$newrelated = [];
|
||||
foreach ($data[$prefixed] as $item) {
|
||||
foreach ($data->$prefixed as $item) {
|
||||
$newrelated[] = (object) $item;
|
||||
}
|
||||
$related[$one] = $newrelated;
|
||||
} else {
|
||||
$related[$one] = (object) $data[$prefixed];
|
||||
$related[$one] = (object) $data->$prefixed;
|
||||
}
|
||||
unset($data[$prefixed]);
|
||||
unset($data->$prefixed);
|
||||
} else if (!array_key_exists($one, $related)) {
|
||||
$related[$one] = null;
|
||||
}
|
||||
|
||||
+2
-2
@@ -808,7 +808,7 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
);
|
||||
|
||||
if (has_capability('moodle/badges:configuredetails', $context)) {
|
||||
$row[] = new tabobject('details',
|
||||
$row[] = new tabobject('badge',
|
||||
new moodle_url('/badges/edit.php', array('id' => $badgeid, 'action' => 'badge')),
|
||||
get_string('bdetails', 'badges')
|
||||
);
|
||||
@@ -859,7 +859,7 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
if (has_capability('moodle/badges:configuredetails', $context)) {
|
||||
$alignments = $DB->count_records_sql("SELECT COUNT(bc.id)
|
||||
FROM {badge_alignment} bc WHERE bc.badgeid = :badgeid", array('badgeid' => $badgeid));
|
||||
$row[] = new tabobject('balignment',
|
||||
$row[] = new tabobject('alignment',
|
||||
new moodle_url('/badges/alignment.php', array('id' => $badgeid)),
|
||||
get_string('balignment', 'badges', $alignments)
|
||||
);
|
||||
|
||||
@@ -59,7 +59,7 @@ class core_block_external extends external_api {
|
||||
'visible' => new external_value(PARAM_BOOL, 'Whether the block is visible.', VALUE_OPTIONAL),
|
||||
'contents' => new external_single_structure(
|
||||
array(
|
||||
'title' => new external_value(PARAM_TEXT, 'Block title.'),
|
||||
'title' => new external_value(PARAM_RAW, 'Block title.'),
|
||||
'content' => new external_value(PARAM_RAW, 'Block contents.'),
|
||||
'contentformat' => new external_format_value('content'),
|
||||
'footer' => new external_value(PARAM_RAW, 'Block footer.'),
|
||||
@@ -92,6 +92,9 @@ class core_block_external extends external_api {
|
||||
private static function get_all_current_page_blocks($includeinvisible = false, $returncontents = false) {
|
||||
global $PAGE, $OUTPUT;
|
||||
|
||||
// Set page URL to a fake URL to avoid errors.
|
||||
$PAGE->set_url(new \moodle_url('/webservice/core_block_external/'));
|
||||
|
||||
// Load the block instances for all the regions.
|
||||
$PAGE->blocks->load_blocks($includeinvisible);
|
||||
$PAGE->blocks->create_all_block_instances();
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -537,11 +537,9 @@ function(
|
||||
|
||||
// Filter out all pagination options which are too large for the amount of courses user is enrolled in.
|
||||
var totalCourseCount = parseInt(root.find(Selectors.courseView.region).attr('data-totalcoursecount'), 10);
|
||||
if (totalCourseCount) {
|
||||
itemsPerPage = itemsPerPage.filter(function(pagingOption) {
|
||||
return pagingOption.value < totalCourseCount;
|
||||
});
|
||||
}
|
||||
itemsPerPage = itemsPerPage.filter(function(pagingOption) {
|
||||
return pagingOption.value < totalCourseCount || pagingOption.value === 0;
|
||||
});
|
||||
|
||||
var filters = getFilterValues(root);
|
||||
var config = $.extend({}, DEFAULT_PAGED_CONTENT_CONFIG);
|
||||
@@ -596,7 +594,8 @@ function(
|
||||
pageCourses = $.merge(loadedPages[currentPage].courses, courses.slice(0, nextPageStart));
|
||||
}
|
||||
} else {
|
||||
nextPageStart = pageData.limit;
|
||||
// When the page limit is zero, there is only one page of courses, no start for next page.
|
||||
nextPageStart = pageData.limit || false;
|
||||
pageCourses = (pageData.limit > 0) ? courses.slice(0, pageData.limit) : courses;
|
||||
}
|
||||
|
||||
@@ -605,8 +604,8 @@ function(
|
||||
courses: pageCourses
|
||||
};
|
||||
|
||||
// Set up the next page
|
||||
var remainingCourses = nextPageStart ? courses.slice(nextPageStart, courses.length) : [];
|
||||
// Set up the next page (if there is more than one page).
|
||||
var remainingCourses = nextPageStart !== false ? courses.slice(nextPageStart, courses.length) : [];
|
||||
if (remainingCourses.length) {
|
||||
loadedPages[currentPage + 1] = {
|
||||
courses: remainingCourses
|
||||
|
||||
@@ -202,8 +202,9 @@ class main implements renderable, templatable {
|
||||
// Check and remember the given view.
|
||||
$this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD;
|
||||
|
||||
// Check and remember the given page size.
|
||||
if ($paging == BLOCK_MYOVERVIEW_PAGING_ALL) {
|
||||
// Check and remember the given page size, `null` indicates no page size set
|
||||
// while a `0` indicates a paging size of `All`.
|
||||
if (!is_null($paging) && $paging == BLOCK_MYOVERVIEW_PAGING_ALL) {
|
||||
$this->paging = BLOCK_MYOVERVIEW_PAGING_ALL;
|
||||
} else {
|
||||
$this->paging = $paging ? $paging : BLOCK_MYOVERVIEW_PAGING_12;
|
||||
@@ -370,8 +371,9 @@ class main implements renderable, templatable {
|
||||
$select = "instanceid $csql AND fieldid = :fieldid";
|
||||
$params['fieldid'] = $fieldid;
|
||||
$distinctablevalue = $DB->sql_compare_text('value');
|
||||
$values = $DB->get_records_select_menu('customfield_data', $select, $params, $DB->sql_order_by_text('value'),
|
||||
$values = $DB->get_records_select_menu('customfield_data', $select, $params, '',
|
||||
"DISTINCT $distinctablevalue, $distinctablevalue AS value2");
|
||||
\core_collator::asort($values, \core_collator::SORT_NATURAL);
|
||||
$values = array_filter($values);
|
||||
if (!$values) {
|
||||
return [];
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
{{#summary}}{{#str}} summary, block_myoverview {{/str}}{{/summary}}
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" data-show-active-item aria-labelledby="displaydropdown">
|
||||
<ul class="dropdown-menu" role="menu" data-show-active-item data-skip-active-class="true" aria-labelledby="displaydropdown">
|
||||
{{#layouts}}
|
||||
<li>
|
||||
<a class="dropdown-item {{#active}}active{{/active}}" href="#" data-display-option="display" data-value="{{id}}" data-pref="{{id}}" aria-label="{{arialabel}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-display-option="display" data-value="{{id}}" data-pref="{{id}}" aria-label="{{arialabel}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#active}}aria-current="true"{{/active}}>
|
||||
{{name}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -53,10 +53,10 @@
|
||||
{{selectedcustomfield}}
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" data-show-active-item data-active-item-text aria-labelledby="groupingdropdown">
|
||||
<ul class="dropdown-menu" role="menu" data-show-active-item data-skip-active-class="true" data-active-item-text aria-labelledby="groupingdropdown">
|
||||
{{#displaygroupingallincludinghidden}}
|
||||
<li>
|
||||
<a class="dropdown-item {{#allincludinghidden}}active{{/allincludinghidden}}" href="#" data-filter="grouping" data-value="allincludinghidden" data-pref="allincludinghidden" aria-label="{{#str}} aria:allcoursesincludinghidden, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="allincludinghidden" data-pref="allincludinghidden" aria-label="{{#str}} aria:allcoursesincludinghidden, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#allincludinghidden}}aria-current="true"{{/allincludinghidden}}>
|
||||
{{#str}} allincludinghidden, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
@@ -66,7 +66,7 @@
|
||||
<span class="filler"> </span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {{#all}}active{{/all}}" href="#" data-filter="grouping" data-value="all" data-pref="all" aria-label="{{#str}} aria:allcourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="all" data-pref="all" aria-label="{{#str}} aria:allcourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#all}}aria-current="true"{{/all}}>
|
||||
{{#str}} all, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
@@ -76,7 +76,7 @@
|
||||
<span class="filler"> </span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {{#inprogress}}active{{/inprogress}}" href="#" data-filter="grouping" data-value="inprogress" data-pref="inprogress" aria-label="{{#str}} aria:inprogress, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="inprogress" data-pref="inprogress" aria-label="{{#str}} aria:inprogress, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#inprogress}}aria-current="true"{{/inprogress}}>
|
||||
{{#str}} inprogress, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
@@ -88,7 +88,7 @@
|
||||
</li>
|
||||
{{/displaygroupinginprogress}}
|
||||
<li>
|
||||
<a class="dropdown-item {{#future}}active{{/future}}" href="#" data-filter="grouping" data-value="future" data-pref="future" aria-label="{{#str}} aria:future, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="future" data-pref="future" aria-label="{{#str}} aria:future, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#future}}aria-current="true"{{/future}}>
|
||||
{{#str}} future, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
@@ -102,7 +102,7 @@
|
||||
{{/displaygroupingfuture}}
|
||||
{{/displaygroupinginprogress}}
|
||||
<li>
|
||||
<a class="dropdown-item {{#past}}active{{/past}}" href="#" data-filter="grouping" data-value="past" data-pref="past" aria-label="{{#str}} aria:past, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="past" data-pref="past" aria-label="{{#str}} aria:past, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#past}}aria-current="true"{{/past}}>
|
||||
{{#str}} past, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
@@ -113,10 +113,10 @@
|
||||
</li>
|
||||
{{#customfieldvalues}}
|
||||
<li>
|
||||
<a class="dropdown-item {{#active}}active{{/active}}" href="#" data-filter="grouping"
|
||||
<a class="dropdown-item" href="#" data-filter="grouping"
|
||||
data-value="customfield" data-pref="customfield" data-customfieldvalue="{{value}}"
|
||||
aria-label="{{#str}}aria:customfield, block_myoverview, {{name}}{{/str}}"
|
||||
aria-controls="courses-view-{{uniqid}}">
|
||||
aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#active}}aria-current="true"{{/active}}>
|
||||
{{name}}
|
||||
</a>
|
||||
</li>
|
||||
@@ -127,7 +127,7 @@
|
||||
<span class="filler"> </span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {{#favourites}}active{{/favourites}}" href="#" data-filter="grouping" data-value="favourites" data-pref="favourites" aria-label="{{#str}} aria:favourites, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="favourites" data-pref="favourites" aria-label="{{#str}} aria:favourites, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#favourites}}aria-current="true"{{/favourites}}>
|
||||
{{#str}} favourites, block_myoverview {{/str}}
|
||||
</a>
|
||||
{{/displaygroupingfavourites}}
|
||||
@@ -136,7 +136,7 @@
|
||||
<span class="filler"> </span>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {{#hidden}}active{{/hidden}}" href="#" data-filter="grouping" data-value="hidden" data-pref="hidden" aria-label="{{#str}} aria:hiddencourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="hidden" data-pref="hidden" aria-label="{{#str}} aria:hiddencourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#hidden}}aria-current="true"{{/hidden}}>
|
||||
{{#str}} hiddencourses, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -36,21 +36,21 @@
|
||||
{{#shortname}}{{#str}} shortname, block_myoverview {{/str}}{{/shortname}}
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" data-show-active-item aria-labelledby="sortingdropdown">
|
||||
<ul class="dropdown-menu" role="menu" data-show-active-item data-skip-active-class="true" aria-labelledby="sortingdropdown">
|
||||
<li>
|
||||
<a class="dropdown-item {{#title}}active{{/title}}" href="#" data-filter="sort" data-pref="title" data-value="fullname" aria-label="{{#str}} aria:title, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="sort" data-pref="title" data-value="fullname" aria-label="{{#str}} aria:title, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#title}}aria-current="true"{{/title}}>
|
||||
{{#str}} title, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
{{#showsortbyshortname}}
|
||||
<li>
|
||||
<a class="dropdown-item {{#shortname}}active{{/shortname}}" href="#" data-filter="sort" data-pref="shortname" data-value="shortname" aria-label="{{#str}} aria:shortname, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="sort" data-pref="shortname" data-value="shortname" aria-label="{{#str}} aria:shortname, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#shortname}}aria-current="true"{{/shortname}}>
|
||||
{{#str}} shortname, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
{{/showsortbyshortname}}
|
||||
<li>
|
||||
<a class="dropdown-item {{#lastaccessed}}active{{/lastaccessed}}" href="#" data-filter="sort" data-pref="lastaccessed" data-value="ul.timeaccess desc" aria-label="{{#str}} aria:lastaccessed, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item" href="#" data-filter="sort" data-pref="lastaccessed" data-value="ul.timeaccess desc" aria-label="{{#str}} aria:lastaccessed, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}" role="menuitem" {{#lastaccessed}}aria-current="true"{{/lastaccessed}}>
|
||||
{{#str}} lastaccessed, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -7,30 +7,30 @@ Feature: The my overview block allows users to persistence of their page limits
|
||||
| student1 | Student | X | student1@example.com | S1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| Course 2 | C2 | 0 |
|
||||
| Course 3 | C3 | 0 |
|
||||
| Course 4 | C4 | 0 |
|
||||
| Course 5 | C5 | 0 |
|
||||
| Course 6 | C6 | 0 |
|
||||
| Course 7 | C7 | 0 |
|
||||
| Course 8 | C8 | 0 |
|
||||
| Course 9 | C9 | 0 |
|
||||
| Course 1 | C01 | 0 |
|
||||
| Course 2 | C02 | 0 |
|
||||
| Course 3 | C03 | 0 |
|
||||
| Course 4 | C04 | 0 |
|
||||
| Course 5 | C05 | 0 |
|
||||
| Course 6 | C06 | 0 |
|
||||
| Course 7 | C07 | 0 |
|
||||
| Course 8 | C08 | 0 |
|
||||
| Course 9 | C09 | 0 |
|
||||
| Course 10 | C10 | 0 |
|
||||
| Course 11 | C11 | 0 |
|
||||
| Course 12 | C12 | 0 |
|
||||
| Course 13 | C13 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
| student1 | C6 | student |
|
||||
| student1 | C7 | student |
|
||||
| student1 | C8 | student |
|
||||
| student1 | C9 | student |
|
||||
| student1 | C01 | student |
|
||||
| student1 | C02 | student |
|
||||
| student1 | C03 | student |
|
||||
| student1 | C04 | student |
|
||||
| student1 | C05 | student |
|
||||
| student1 | C06 | student |
|
||||
| student1 | C07 | student |
|
||||
| student1 | C08 | student |
|
||||
| student1 | C09 | student |
|
||||
| student1 | C10 | student |
|
||||
| student1 | C11 | student |
|
||||
| student1 | C12 | student |
|
||||
@@ -38,8 +38,8 @@ Feature: The my overview block allows users to persistence of their page limits
|
||||
|
||||
Scenario: Toggle the page limit between page reloads
|
||||
Given I log in as "student1"
|
||||
When I click on "[data-toggle='dropdown']" "css_element" in the "Course overview" "block"
|
||||
And I click on "All" "link"
|
||||
When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
|
||||
And I click on "All" "link" in the ".dropdown-menu.show" "css_element"
|
||||
Then I should see "Course 13"
|
||||
And I reload the page
|
||||
Then I should see "Course 13"
|
||||
@@ -47,8 +47,8 @@ Feature: The my overview block allows users to persistence of their page limits
|
||||
|
||||
Scenario: Toggle the page limit between grouping changes
|
||||
Given I log in as "student1"
|
||||
When I click on "[data-toggle='dropdown']" "css_element" in the "Course overview" "block"
|
||||
And I click on "All" "link"
|
||||
When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
|
||||
And I click on "All" "link" in the ".dropdown-menu.show" "css_element"
|
||||
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
|
||||
And I click on "In progress" "link" in the "Course overview" "block"
|
||||
Then I should see "Course 13"
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
@block @block_myoverview @javascript
|
||||
Feature: My overview block pagination
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email | idnumber |
|
||||
| student1 | Student | X | student1@example.com | S1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 01 | C1 | 0 |
|
||||
| Course 02 | C2 | 0 |
|
||||
| Course 03 | C3 | 0 |
|
||||
| Course 04 | C4 | 0 |
|
||||
| Course 05 | C5 | 0 |
|
||||
| Course 06 | C6 | 0 |
|
||||
| Course 07 | C7 | 0 |
|
||||
| Course 08 | C8 | 0 |
|
||||
| Course 09 | C9 | 0 |
|
||||
| Course 10 | C10 | 0 |
|
||||
| Course 11 | C11 | 0 |
|
||||
| Course 12 | C12 | 0 |
|
||||
| Course 13 | C13 | 0 |
|
||||
| Course 14 | C14 | 0 |
|
||||
| Course 15 | C15 | 0 |
|
||||
| Course 16 | C16 | 0 |
|
||||
| Course 17 | C17 | 0 |
|
||||
| Course 18 | C18 | 0 |
|
||||
| Course 19 | C19 | 0 |
|
||||
| Course 20 | C20 | 0 |
|
||||
| Course 21 | C21 | 0 |
|
||||
| Course 22 | C22 | 0 |
|
||||
| Course 23 | C23 | 0 |
|
||||
| Course 24 | C24 | 0 |
|
||||
| Course 25 | C25 | 0 |
|
||||
|
||||
Scenario: The pagination controls should be hidden if I am not enrolled in any courses
|
||||
When I log in as "student1"
|
||||
Then I should see "No courses" in the "Course overview" "block"
|
||||
And I should not see "Show" in the "Course overview" "block"
|
||||
And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible
|
||||
And ".block_myoverview [data-control='next']" "css_element" should not be visible
|
||||
And ".block_myoverview [data-control='previous']" "css_element" should not be visible
|
||||
And I log out
|
||||
|
||||
Scenario: The pagination controls should be hidden if I am enrolled in 12 courses or less
|
||||
Given the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
| student1 | C6 | student |
|
||||
| student1 | C7 | student |
|
||||
| student1 | C8 | student |
|
||||
| student1 | C9 | student |
|
||||
| student1 | C10 | student |
|
||||
| student1 | C11 | student |
|
||||
| student1 | C12 | student |
|
||||
When I log in as "student1"
|
||||
Then I should not see "Show" in the "Course overview" "block"
|
||||
And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible
|
||||
And ".block_myoverview [data-control='next']" "css_element" should not be visible
|
||||
And ".block_myoverview [data-control='previous']" "css_element" should not be visible
|
||||
And I log out
|
||||
|
||||
Scenario: The default pagination should be 12 courses
|
||||
Given the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
| student1 | C6 | student |
|
||||
| student1 | C7 | student |
|
||||
| student1 | C8 | student |
|
||||
| student1 | C9 | student |
|
||||
| student1 | C10 | student |
|
||||
| student1 | C11 | student |
|
||||
| student1 | C12 | student |
|
||||
| student1 | C13 | student |
|
||||
When I log in as "student1"
|
||||
Then I should see "12" in the "[data-action='limit-toggle']" "css_element"
|
||||
And I log out
|
||||
|
||||
Scenario: I should only see pagination limit options less than total number of enrolled courses
|
||||
Given the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
| student1 | C6 | student |
|
||||
| student1 | C7 | student |
|
||||
| student1 | C8 | student |
|
||||
| student1 | C9 | student |
|
||||
| student1 | C10 | student |
|
||||
| student1 | C11 | student |
|
||||
| student1 | C12 | student |
|
||||
| student1 | C13 | student |
|
||||
And I log in as "student1"
|
||||
When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
|
||||
Then I should see "All" in the ".dropdown-menu.show" "css_element"
|
||||
And I should see "12" in the ".dropdown-menu.show" "css_element"
|
||||
And ".block_myoverview [data-control='next']" "css_element" should be visible
|
||||
And ".block_myoverview [data-control='previous']" "css_element" should be visible
|
||||
But I should not see "24" in the ".block_myoverview .dropdown-menu.show" "css_element"
|
||||
And I log out
|
||||
|
||||
Scenario: Previous page button should be disabled when on the first page of courses
|
||||
Given the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
| student1 | C6 | student |
|
||||
| student1 | C7 | student |
|
||||
| student1 | C8 | student |
|
||||
| student1 | C9 | student |
|
||||
| student1 | C10 | student |
|
||||
| student1 | C11 | student |
|
||||
| student1 | C12 | student |
|
||||
| student1 | C13 | student |
|
||||
When I log in as "student1"
|
||||
Then the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should contain "disabled"
|
||||
And I log out
|
||||
|
||||
Scenario: Next page button should be disabled when on the last page of courses
|
||||
Given the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
| student1 | C6 | student |
|
||||
| student1 | C7 | student |
|
||||
| student1 | C8 | student |
|
||||
| student1 | C9 | student |
|
||||
| student1 | C10 | student |
|
||||
| student1 | C11 | student |
|
||||
| student1 | C12 | student |
|
||||
| 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
|
||||
|
||||
Scenario: Next and previous page buttons should both be enabled when not on last or first page of courses
|
||||
Given the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
| student1 | C6 | student |
|
||||
| student1 | C7 | student |
|
||||
| student1 | C8 | student |
|
||||
| student1 | C9 | student |
|
||||
| student1 | C10 | student |
|
||||
| student1 | C11 | student |
|
||||
| student1 | C12 | student |
|
||||
| student1 | C13 | student |
|
||||
| student1 | C14 | student |
|
||||
| student1 | C15 | student |
|
||||
| student1 | C16 | student |
|
||||
| student1 | C17 | student |
|
||||
| student1 | C18 | student |
|
||||
| student1 | C19 | student |
|
||||
| student1 | C20 | student |
|
||||
| student1 | C21 | student |
|
||||
| student1 | C22 | student |
|
||||
| student1 | C23 | student |
|
||||
| student1 | C24 | student |
|
||||
| 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"
|
||||
And I should see "Course 24" in the "Course overview" "block"
|
||||
But I should not see "Course 12" in the "Course overview" "block"
|
||||
And I should not see "Course 25" in the "Course overview" "block"
|
||||
And I log out
|
||||
@@ -115,9 +115,13 @@ class block_news_items extends block_base {
|
||||
|
||||
$discussion->subject = format_string($discussion->subject, true, $forum->course);
|
||||
|
||||
$posttime = $discussion->modified;
|
||||
if (!empty($CFG->forum_enabletimedposts) && ($discussion->timestart > $posttime)) {
|
||||
$posttime = $discussion->timestart;
|
||||
}
|
||||
$text .= '<li class="post">'.
|
||||
'<div class="head clearfix">'.
|
||||
'<div class="date">'.userdate($discussion->modified, $strftimerecent).'</div>'.
|
||||
'<div class="date">'.userdate($posttime, $strftimerecent).'</div>'.
|
||||
'<div class="name">'.fullname($discussion).'</div></div>'.
|
||||
'<div class="info"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->discussion.'">'.$discussion->subject.'</a></div>'.
|
||||
"</li>\n";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user