Compare commits
233 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00821c744c | |||
| f420af1016 | |||
| 67150560b2 | |||
| 9fac7a8d59 | |||
| e1ca7e93d9 | |||
| 8bd6cfb4c4 | |||
| a3afe546fd | |||
| db30a623bf | |||
| 3afcb70b3d | |||
| 9ca6f8199e | |||
| 4fec7a276d | |||
| b70a1c71e1 | |||
| 90c450839d | |||
| b7a62b4286 | |||
| 33427c7c5d | |||
| 8fa9d159bc | |||
| f8f154236d | |||
| d54cc8fbd7 | |||
| 7e6fc779c7 | |||
| 3467d87c80 | |||
| 855957180a | |||
| 8862b80c56 | |||
| 8b8142d8d9 | |||
| f0260219fa | |||
| be36ead8b0 | |||
| f270bd8c8e | |||
| 357909b23b | |||
| 2c45443ad7 | |||
| 77d7480096 | |||
| b57b9b41cd | |||
| cfa2b28de6 | |||
| e22723680d | |||
| db2a9784ef | |||
| 06d7da192b | |||
| 586a6da356 | |||
| 2ab950039c | |||
| 3b3c246aaa | |||
| b3b476ce3c | |||
| 682ee99293 | |||
| b8069c3372 | |||
| 992d1b29b4 | |||
| ceab4136aa | |||
| 8241aa75d5 | |||
| f186074a54 | |||
| 8585fe1404 | |||
| d4dff59b82 | |||
| af4b97f57a | |||
| c3341dea30 | |||
| 2b0b250162 | |||
| dd17f6b1bf | |||
| 4e1786f27f | |||
| 247437ce25 | |||
| 8d306e640f | |||
| 17d296f1ea | |||
| c9dfaec749 | |||
| 304aaf2b7b | |||
| 34153bcc77 | |||
| 65ef2f90bf | |||
| b12ab80cf5 | |||
| 9ee975f3a9 | |||
| fb02551622 | |||
| 64db9dc75a | |||
| 9d5931b6e2 | |||
| 0369198637 | |||
| 62b9a44045 | |||
| 1251f26d67 | |||
| 77a6c383bf | |||
| 53e81c00fa | |||
| 9480706466 | |||
| bb48ca47ef | |||
| 93b2fe7d38 | |||
| 6ba4712549 | |||
| b192dc7c64 | |||
| 2d9922f388 | |||
| 639f6143e7 | |||
| 0a27966b67 | |||
| 79b89c2d42 | |||
| 591107c208 | |||
| ccfc9f7bc9 | |||
| 06bf0c89d6 | |||
| c25f0087f7 | |||
| 12830e4214 | |||
| 2e1b2cf30e | |||
| 0bfb0a58a0 | |||
| 50ba1375e8 | |||
| 5f57b10712 | |||
| ec6720a836 | |||
| e00b006d51 | |||
| 7ce3918e83 | |||
| aa1c7e0a64 | |||
| cc87756c10 | |||
| e7a53e9410 | |||
| a050d3f41b | |||
| bc103afb53 | |||
| 50d1003bb7 | |||
| 5c899c722c | |||
| 73ad678e73 | |||
| 22a5445346 | |||
| 3ca8535203 | |||
| 7697ac6538 | |||
| c97eb87506 | |||
| 66b3c79275 | |||
| 773db77081 | |||
| a07acd9464 | |||
| 12b20cbf3b | |||
| fa31950c6a | |||
| 6ef5f4ced0 | |||
| c537ed7fed | |||
| f3b2f604e2 | |||
| f987001a61 | |||
| f1ec340cca | |||
| 6998cbfad4 | |||
| 655f4d4f3f | |||
| d9e22f40b4 | |||
| df8a5aeab5 | |||
| 17cc12c381 | |||
| e76c5c764b | |||
| be45f4c47f | |||
| fa7e218546 | |||
| 5c0a5a531d | |||
| 200ec6b6ac | |||
| 0e7dc879de | |||
| 70fe644191 | |||
| 5178b349e8 | |||
| c8e335fbe3 | |||
| 567e587dbb | |||
| 37348f1c2e | |||
| e2feddadd9 | |||
| 0a62138795 | |||
| c24a16a819 | |||
| 8091ab8078 | |||
| 12b9c6051e | |||
| 92e6eb437c | |||
| 32866a1758 | |||
| 9ce415b207 | |||
| ec69ae5929 | |||
| f5545b5c0e | |||
| 7c22b71916 | |||
| b5414af214 | |||
| 686b5beb23 | |||
| af87a57052 | |||
| dec6e261e2 | |||
| 24058ace40 | |||
| d552427f61 | |||
| 0eeb391472 | |||
| 433556de1f | |||
| b24f13eca2 | |||
| 65a9b26791 | |||
| 2cc04e2b29 | |||
| 6be44e7d06 | |||
| 93b3efa5a7 | |||
| 2908ed5405 | |||
| 966c8a37cb | |||
| c8637d645e | |||
| b427f21cca | |||
| 25a0001b9f | |||
| 843cc0ee8d | |||
| 0172d13163 | |||
| 78bddace2c | |||
| 779c1687a4 | |||
| c99a83bc5c | |||
| 0b523e6031 | |||
| 8cd8daa285 | |||
| 1bd007e815 | |||
| 9c35064345 | |||
| 5945a1a926 | |||
| fac91d11d8 | |||
| 9fcd6522ce | |||
| 85673c859e | |||
| 4dba8e8359 | |||
| f03c875cf3 | |||
| 9fcb5ece12 | |||
| b07833ddf4 | |||
| 333f6cb753 | |||
| 1c594e729b | |||
| d8c8f544b3 | |||
| a284ced9a1 | |||
| cb82824f6a | |||
| 613c37d54b | |||
| 53f9417795 | |||
| 407807df6b | |||
| 7c80e15392 | |||
| e95c0a90ac | |||
| 48905f6b75 | |||
| b5b562b7ee | |||
| 4a82e72b00 | |||
| ef95f5262f | |||
| e2d911c07b | |||
| 71bae0da0f | |||
| e7bfbaf7b2 | |||
| 6882d7399b | |||
| 80bf03d583 | |||
| 1039537d83 | |||
| 0214c50b18 | |||
| e29e8373e6 | |||
| 9e11009eb1 | |||
| a01ca7a600 | |||
| 6a662825f7 | |||
| 03e3219884 | |||
| 681b7f4e78 | |||
| f098f28d01 | |||
| bf17d50cf6 | |||
| 8e0f036b29 | |||
| 51c6924206 | |||
| f589a25459 | |||
| dee389dff6 | |||
| a9b83b3c01 | |||
| 0ab0a293a8 | |||
| b76093e90c | |||
| 8f0d40a39c | |||
| 3a2d8a175e | |||
| ff72ca7340 | |||
| 4e346470a5 | |||
| 13c76cc295 | |||
| 2c4426bb10 | |||
| d3a9e2935d | |||
| 2adc010b85 | |||
| c330c9276c | |||
| e9b443b3a1 | |||
| 7bb692666b | |||
| 5e924f295a | |||
| 739cbcf7e9 | |||
| 22df02089a | |||
| be2afb52c1 | |||
| 09520f8e0d | |||
| d661fbfbc1 | |||
| e29069d45b | |||
| a2dcfd5bf6 | |||
| ebe19aa434 | |||
| 898dd7bf5b | |||
| 4423581f8d | |||
| 9597baa6e8 | |||
| 04073c7e31 |
@@ -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
|
||||
+29
-71
@@ -16,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).
|
||||
@@ -48,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:
|
||||
@@ -113,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 ;
|
||||
@@ -224,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' ];
|
||||
@@ -275,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' ];
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 (Teacher)" in the "Teacher 1" "table_row"
|
||||
And I should see "Teacher < \"editing\" (Non-editing teacher)" in the "Teaching Assistant" "table_row"
|
||||
And I should see "Studier & 'learner' (Student)" 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,53 @@
|
||||
@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"
|
||||
|
||||
# TODO: Uncomment the following when MDL-66979 is integrated.
|
||||
# @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
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
+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-region=\"list-templates\"] [data-region=\"input\"]").val();if(""!==f){a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").removeClass("d-none")}else{a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").addClass("d-none")}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-region=\"input\"]",k);a("[data-action=\"clearsearch\"]").on("click",function(){a("[data-region=\"input\"]").val("");h(f.theme);a(this).addClass("d-none")});a("[data-region=\"input\"]").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-region=\"list-templates\"] [data-region=\"input\"]").val();if(""!==f){a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").removeClass("d-none")}else{a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").addClass("d-none")}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-region=\"input\"]",k);a("[data-action=\"clearsearch\"]").on("click",function(){a("[data-region=\"input\"]").val("");h(f.theme);a(this).addClass("d-none")});h(f.theme);return{}});
|
||||
//# sourceMappingURL=search.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -52,9 +52,8 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
|
||||
} else {
|
||||
$('[data-region="list-templates"] [data-action="clearsearch"]').addClass('d-none');
|
||||
}
|
||||
// Trigger the search.
|
||||
document.location.hash = searchStr;
|
||||
|
||||
// Trigger the search.
|
||||
ajax.call([
|
||||
{methodname: 'tool_templatelibrary_list_templates',
|
||||
args: {component: componentStr, search: searchStr, themename: themename},
|
||||
@@ -96,7 +95,6 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
|
||||
$(this).addClass('d-none');
|
||||
});
|
||||
|
||||
$('[data-region="input"]').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 {
|
||||
|
||||
@@ -72,10 +72,10 @@
|
||||
{{/label}}
|
||||
|
||||
{{$element}}
|
||||
<select id="selectcomponent" class="form-control" data-field="component">
|
||||
<select id="selectcomponent" name="component" class="form-control" 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>
|
||||
{{/element}}
|
||||
@@ -84,10 +84,10 @@
|
||||
{{< core_form/element-template }}
|
||||
{{$element}}
|
||||
{{< core/search_input_auto }}
|
||||
{{$label}}{{{ searchstring }}}{{/label}}
|
||||
{{$placeholder}}{{#str}}
|
||||
search, core
|
||||
{{/str}}{{/placeholder}}
|
||||
{{$label}}
|
||||
{{#str}} search, tool_templatelibrary {{/str}}
|
||||
{{/label}}
|
||||
{{$value}}{{ search }}{{/value}}
|
||||
{{/ core/search_input_auto }}
|
||||
{{/element}}
|
||||
{{/ core_form/element-template }}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
@@ -50,5 +50,27 @@ function xmldb_auth_shibboleth_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.10.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2020110901) {
|
||||
// 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, 2020110901, '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.10.1 ===
|
||||
|
||||
* 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 = 2020110900; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2020110901; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2020110300; // Requires this Moodle version
|
||||
$plugin->component = 'auth_shibboleth'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -543,11 +543,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);
|
||||
@@ -602,7 +600,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;
|
||||
}
|
||||
|
||||
@@ -611,8 +610,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 [];
|
||||
|
||||
@@ -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
|
||||
@@ -150,4 +150,17 @@ class behat_block_site_main_menu extends behat_base {
|
||||
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., $activityname)]";
|
||||
$this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of partial named selectors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_partial_named_selectors(): array {
|
||||
return [
|
||||
new behat_component_named_selector('Activity', [
|
||||
"//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., %locator%)]"
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,16 @@ Feature: Edit activities in main menu block
|
||||
|
||||
@javascript
|
||||
Scenario: Edit name of acitivity in-place in site main menu block
|
||||
Given I log in as "admin"
|
||||
Given the following "activity" exists:
|
||||
| activity | forum |
|
||||
| course | Acceptance test site |
|
||||
| name | My forum name |
|
||||
| idnumber | forum |
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I navigate to "Turn editing on" in current page administration
|
||||
And I add the "Main menu" block
|
||||
When I add a "Forum" to section "0" and I fill the form with:
|
||||
| Forum name | My forum name |
|
||||
And I click on "Edit title" "link" in the "My forum name" activity in site main menu block
|
||||
And I set the field "New name for activity My forum name" to "New forum name"
|
||||
And I press key "13" in the field "New name for activity My forum name"
|
||||
When I set the field "Edit title" in the "My forum name" "block_site_main_menu > Activity" to "New forum name"
|
||||
Then I should not see "My forum name"
|
||||
And I should see "New forum name"
|
||||
And I follow "New forum name"
|
||||
|
||||
@@ -158,4 +158,17 @@ class behat_block_social_activities extends behat_base {
|
||||
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]";
|
||||
$this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of partial named selectors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_partial_named_selectors(): array {
|
||||
return [
|
||||
new behat_component_named_selector('Activity', [
|
||||
"//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., %locator%)]",
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ Feature: Edit activities in social activities block
|
||||
And I click on "Add a new Forum" "link" in the "Add an activity or resource" "dialogue"
|
||||
And I set the field "Forum name" to "My forum name"
|
||||
And I press "Save and return to course"
|
||||
And I click on "Edit title" "link" in the "My forum name" activity in social activities block
|
||||
And I set the field "New name for activity My forum name" to "New forum name"
|
||||
And I press key "13" in the field "New name for activity My forum name"
|
||||
When I set the field "Edit title" in the "My forum name" "block_social_activities > Activity" to "New forum name"
|
||||
Then I should not see "My forum name" in the "Social activities" "block"
|
||||
And I should see "New forum name"
|
||||
And I follow "New forum name"
|
||||
@@ -84,4 +82,3 @@ Feature: Edit activities in social activities block
|
||||
And I should not see "My forum name" in the "Social activities" "block"
|
||||
And I click on "My forum name" "link" in the "Recent activity" "block"
|
||||
And I should see "My forum name" in the ".breadcrumb" "css_element"
|
||||
And I log out
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
<a href="{{url}}"
|
||||
title={{#quote}}{{{name}}}{{/quote}}
|
||||
aria-label='{{#str}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{name}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/str}}'
|
||||
><h6 class="event-name text-truncate mb-0">{{#quote}}{{{name}}}{{/quote}}</h6></a>
|
||||
><h6 class="event-name text-truncate mb-0">{{{name}}}</h6></a>
|
||||
{{#course.fullnamedisplay}}
|
||||
<small class="text-muted text-truncate mb-0">{{#quote}}{{{course.fullnamedisplay}}}{{/quote}}</small>
|
||||
<small class="text-muted text-truncate mb-0">{{{course.fullnamedisplay}}}</small>
|
||||
{{/course.fullnamedisplay}}
|
||||
{{#action.actionable}}
|
||||
<h6 class="mb-0 pt-2">
|
||||
|
||||
+5
-1
@@ -116,7 +116,11 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
$values['islastday'] = false;
|
||||
$today = $this->related['type']->timestamp_to_date_array($this->related['today']);
|
||||
|
||||
$values['popupname'] = $this->event->get_name();
|
||||
if ($hascourse) {
|
||||
$values['popupname'] = external_format_string($this->event->get_name(), \context_course::instance($course->id), true);
|
||||
} else {
|
||||
$values['popupname'] = external_format_string($this->event->get_name(), \context_system::instance(), true);
|
||||
}
|
||||
|
||||
$times = $this->event->get_times();
|
||||
if ($duration = $times->get_duration()) {
|
||||
|
||||
+6
-3
@@ -115,11 +115,14 @@ if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
|
||||
</td>
|
||||
<td id="buttonscell">
|
||||
<div id="addcontrols">
|
||||
<input name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow().' '.s(get_string('add')); ?>" title="<?php p(get_string('add')); ?>" /><br />
|
||||
<input class="btn btn-secondary" name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow() . ' ' .
|
||||
s(get_string('add')); ?>" title="<?php p(get_string('add')); ?>" /><br />
|
||||
</div>
|
||||
|
||||
<div id="removecontrols">
|
||||
<input name="remove" id="remove" type="submit" value="<?php echo s(get_string('remove')).' '.$OUTPUT->rarrow(); ?>" title="<?php p(get_string('remove')); ?>" />
|
||||
<input class="btn btn-secondary" name="remove" id="remove" type="submit"
|
||||
value="<?php echo s(get_string('remove')) . ' ' . $OUTPUT->rarrow(); ?>"
|
||||
title="<?php p(get_string('remove')); ?>" />
|
||||
</div>
|
||||
</td>
|
||||
<td id="potentialcell">
|
||||
@@ -128,7 +131,7 @@ if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan="3" id='backcell'>
|
||||
<input type="submit" name="cancel" value="<?php p(get_string('backtocohorts', 'cohort')); ?>" />
|
||||
<input class="btn btn-secondary" type="submit" name="cancel" value="<?php p(get_string('backtocohorts', 'cohort')); ?>" />
|
||||
</td></tr>
|
||||
</table>
|
||||
</div></form>
|
||||
|
||||
@@ -62,9 +62,7 @@ Feature: Add cohorts of users
|
||||
@javascript
|
||||
Scenario: Edit cohort name in-place
|
||||
When I follow "Cohorts"
|
||||
And I click on "Edit cohort name" "link" in the "Test cohort name" "table_row"
|
||||
And I set the field "New name for cohort Test cohort name" to "Students cohort"
|
||||
And I press key "13" in the field "New name for cohort Test cohort name"
|
||||
And I set the field "Edit cohort name" to "Students cohort"
|
||||
Then I should not see "Test cohort name"
|
||||
And I should see "Students cohort"
|
||||
And I follow "Cohorts"
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),getSectionChooserOptions:function getSectionChooserOptions(a){return"".concat(a," ").concat(b("region","chooser-options-container"))},chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),header:b("region","summary-header"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),activityTabNav:b("region","activity-tab-nav"),resourceTabNav:b("region","resources-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),activityTab:b("region","activity"),resourceTab:b("region","resources"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")},searchResults:b("region","search-results-container"),searchResultItems:b("region","search-result-items-container")},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide"),search:b("action","search"),clearSearch:b("action","clearsearch")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic",tab:"a[data-toggle=\"tab\"]",activetab:"a[data-toggle=\"tab\"][aria-selected=\"true\"]",visibletabs:"a[data-toggle=\"tab\"]:not(.d-none)"}};a.default=c;return a.default});
|
||||
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),getSectionChooserOptions:function getSectionChooserOptions(a){return"".concat(a," ").concat(b("region","chooser-options-container"))},chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),header:b("region","summary-header"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),defaultTabNav:b("region","default-tab-nav"),activityTabNav:b("region","activity-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),activityTab:b("region","activity"),resourceTab:b("region","resources"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")},searchResults:b("region","search-results-container"),searchResultItems:b("region","search-result-items-container")},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide"),search:b("action","search"),clearSearch:b("action","clearsearch")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic",tab:"a[data-toggle=\"tab\"]",activetab:"a[data-toggle=\"tab\"][aria-selected=\"true\"]",visibletabs:"a[data-toggle=\"tab\"]:not(.d-none)"}};a.default=c;return a.default});
|
||||
//# sourceMappingURL=selectors.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -216,7 +216,6 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite, footerDa
|
||||
const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);
|
||||
|
||||
toggleFocusableChooserOption(firstChooserOption, true);
|
||||
initTabsKeyboardNavigation(body);
|
||||
initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions, modal);
|
||||
|
||||
return body;
|
||||
@@ -235,77 +234,6 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite, footerDa
|
||||
.catch();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise the keyboard navigation controls for the tab list items.
|
||||
*
|
||||
* @method initTabsKeyboardNavigation
|
||||
* @param {HTMLElement} body Our modal that we are working with
|
||||
*/
|
||||
const initTabsKeyboardNavigation = (body) => {
|
||||
// Set up the tab handlers.
|
||||
const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);
|
||||
const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);
|
||||
const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);
|
||||
const activityTabNav = body.querySelector(selectors.regions.activityTabNav);
|
||||
const resourceTabNav = body.querySelector(selectors.regions.resourceTabNav);
|
||||
const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav, activityTabNav, resourceTabNav];
|
||||
tabNavArray.forEach((element) => {
|
||||
return element.addEventListener('keydown', (e) => {
|
||||
// The first visible navigation tab link.
|
||||
const firstLink = e.target.parentElement.querySelector(selectors.elements.visibletabs);
|
||||
// The last navigation tab link. It would always be the default activities tab link.
|
||||
const lastLink = e.target.parentElement.lastElementChild;
|
||||
|
||||
if (e.keyCode === arrowRight) {
|
||||
const nextLink = e.target.nextElementSibling;
|
||||
if (nextLink === null) {
|
||||
e.target.tabIndex = -1;
|
||||
firstLink.tabIndex = 0;
|
||||
firstLink.focus();
|
||||
} else if (nextLink.classList.contains('d-none')) {
|
||||
e.target.tabIndex = -1;
|
||||
lastLink.tabIndex = 0;
|
||||
lastLink.focus();
|
||||
} else {
|
||||
e.target.tabIndex = -1;
|
||||
nextLink.tabIndex = 0;
|
||||
nextLink.focus();
|
||||
}
|
||||
}
|
||||
if (e.keyCode === arrowLeft) {
|
||||
const previousLink = e.target.previousElementSibling;
|
||||
if (previousLink === null) {
|
||||
e.target.tabIndex = -1;
|
||||
lastLink.tabIndex = 0;
|
||||
lastLink.focus();
|
||||
} else if (previousLink.classList.contains('d-none')) {
|
||||
e.target.tabIndex = -1;
|
||||
firstLink.tabIndex = 0;
|
||||
firstLink.focus();
|
||||
} else {
|
||||
e.target.tabIndex = -1;
|
||||
previousLink.tabIndex = 0;
|
||||
previousLink.focus();
|
||||
}
|
||||
}
|
||||
if (e.keyCode === home) {
|
||||
e.target.tabIndex = -1;
|
||||
firstLink.tabIndex = 0;
|
||||
firstLink.focus();
|
||||
}
|
||||
if (e.keyCode === end) {
|
||||
e.target.tabIndex = -1;
|
||||
lastLink.tabIndex = 0;
|
||||
lastLink.focus();
|
||||
}
|
||||
if (e.keyCode === space) {
|
||||
e.preventDefault();
|
||||
e.target.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise the keyboard navigation controls for the chooser options.
|
||||
*
|
||||
|
||||
@@ -52,10 +52,8 @@ export default {
|
||||
help: getDataSelector('region', 'help'),
|
||||
modules: getDataSelector('region', 'modules'),
|
||||
favouriteTabNav: getDataSelector('region', 'favourite-tab-nav'),
|
||||
recommendedTabNav: getDataSelector('region', 'recommended-tab-nav'),
|
||||
defaultTabNav: getDataSelector('region', 'default-tab-nav'),
|
||||
activityTabNav: getDataSelector('region', 'activity-tab-nav'),
|
||||
resourceTabNav: getDataSelector('region', 'resources-tab-nav'),
|
||||
favouriteTab: getDataSelector('region', 'favourites'),
|
||||
recommendedTab: getDataSelector('region', 'recommended'),
|
||||
defaultTab: getDataSelector('region', 'default'),
|
||||
|
||||
@@ -102,8 +102,13 @@ class course_handler extends \core_customfield\handler {
|
||||
has_capability('moodle/course:changelockedcustomfields', $context));
|
||||
} else {
|
||||
$context = $this->get_parent_context();
|
||||
return (!$field->get_configdata_property('locked') ||
|
||||
guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context));
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
return (!$field->get_configdata_property('locked') ||
|
||||
has_capability('moodle/course:changelockedcustomfields', $context));
|
||||
} else {
|
||||
return (!$field->get_configdata_property('locked') ||
|
||||
guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,7 @@ Feature: Sections can be edited and deleted in topics format
|
||||
|
||||
@javascript
|
||||
Scenario: Inline edit section name in topics format
|
||||
When I click on "Edit topic name" "link" in the "li#section-1" "css_element"
|
||||
And I set the field "New name for topic Topic 1" to "Midterm evaluation"
|
||||
And I press key "13" in the field "New name for topic Topic 1"
|
||||
When I set the field "Edit topic name" in the "li#section-1" "css_element" to "Midterm evaluation"
|
||||
Then I should not see "Topic 1" in the "region-main" "region"
|
||||
And "New name for topic" "field" should not exist
|
||||
And I should see "Midterm evaluation" in the "li#section-1" "css_element"
|
||||
|
||||
@@ -54,9 +54,7 @@ Feature: Sections can be edited and deleted in weeks format
|
||||
|
||||
@javascript
|
||||
Scenario: Inline edit section name in weeks format
|
||||
When I click on "Edit week name" "link" in the "li#section-1" "css_element"
|
||||
And I set the field "New name for week 1 May - 7 May" to "Midterm evaluation"
|
||||
And I press key "13" in the field "New name for week 1 May - 7 May"
|
||||
When I set the field "Edit week name" in the "li#section-1" "css_element" to "Midterm evaluation"
|
||||
Then I should not see "1 May - 7 May" in the "region-main" "region"
|
||||
And "New name for week" "field" should not exist
|
||||
And I should see "Midterm evaluation" in the "li#section-1" "css_element"
|
||||
|
||||
@@ -60,6 +60,15 @@ if (!empty($add)) {
|
||||
// will be the closest match we have.
|
||||
navigation_node::override_active_url(course_get_url($course, $section));
|
||||
|
||||
// MDL-69431 Validate that $section (url param) does not exceed the maximum for this course / format.
|
||||
// If too high (e.g. section *id* not number) non-sequential sections inserted in course_sections table.
|
||||
// Then on import, backup fills 'gap' with empty sections (see restore_rebuild_course_cache). Avoid this.
|
||||
$courseformat = course_get_format($course);
|
||||
$maxsections = $courseformat->get_max_sections();
|
||||
if ($section > $maxsections) {
|
||||
print_error('maxsectionslimit', 'moodle', '', $maxsections);
|
||||
}
|
||||
|
||||
list($module, $context, $cw, $cm, $data) = prepare_new_moduleinfo_data($course, $add, $section);
|
||||
$data->return = 0;
|
||||
$data->sr = $sectionreturn;
|
||||
|
||||
@@ -15,15 +15,16 @@ Feature: Edit activity name in-place
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activity" exists:
|
||||
| course | C1 |
|
||||
| activity | forum |
|
||||
| name | Test forum name |
|
||||
| description | Test forum description |
|
||||
| idnumber | forum1 |
|
||||
When I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Test forum name |
|
||||
| Description | Test forum description |
|
||||
# Rename activity
|
||||
And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Test forum name')]" "xpath_element"
|
||||
And I set the field "New name for activity Test forum name" to "Good news"
|
||||
And I press key "13" in the field "New name for activity Test forum name"
|
||||
And I set the field "Edit title" in the "Test forum name" "activity" to "Good news"
|
||||
Then I should not see "Test forum name" in the ".course-content" "css_element"
|
||||
And "New name for activity Test forum name" "field" should not exist
|
||||
And I should see "Good news"
|
||||
@@ -32,12 +33,11 @@ Feature: Edit activity name in-place
|
||||
And I should not see "Test forum name"
|
||||
# Cancel renaming
|
||||
And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Good news')]" "xpath_element"
|
||||
And I set the field "New name for activity Good news" to "Terrible news"
|
||||
And I press key "27" in the field "New name for activity Good news"
|
||||
And I type "Terrible news"
|
||||
And I press the escape key
|
||||
And "New name for activity Good news" "field" should not exist
|
||||
And I should see "Good news"
|
||||
And I should not see "Terrible news"
|
||||
And I am on "Course 1" course homepage
|
||||
And I should see "Good news"
|
||||
And I should not see "Terrible news"
|
||||
And I log out
|
||||
|
||||
@@ -852,22 +852,12 @@ class behat_course extends behat_base {
|
||||
* @param string $newactivityname
|
||||
*/
|
||||
public function i_change_activity_name_to($activityname, $newactivityname) {
|
||||
|
||||
if (!$this->running_javascript()) {
|
||||
throw new DriverException('Change activity name step is not available with Javascript disabled');
|
||||
}
|
||||
|
||||
$activity = $this->escape($activityname);
|
||||
|
||||
$this->execute('behat_course::i_click_on_in_the_activity',
|
||||
array(get_string('edittitle'), "link", $activity)
|
||||
);
|
||||
|
||||
// Adding chr(10) to save changes.
|
||||
$this->execute('behat_forms::i_set_the_field_to',
|
||||
array('title', $this->escape($newactivityname) . chr(10))
|
||||
);
|
||||
|
||||
$this->execute('behat_forms::i_set_the_field_in_container_to', [
|
||||
get_string('edittitle'),
|
||||
$activityname,
|
||||
'activity',
|
||||
$newactivityname
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ Feature: Tagging courses
|
||||
And I expand all fieldsets
|
||||
Then "Mathematics" "autocomplete_suggestions" should exist
|
||||
And I set the following fields to these values:
|
||||
| Tags | Algebra |
|
||||
| Tags | Mathematics, Algebra |
|
||||
And I press "Save and display"
|
||||
And I am on "Course 2" course homepage
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
@@ -75,7 +75,7 @@ Feature: Tagging courses
|
||||
And I navigate to "Course tags" in current page administration
|
||||
Then I should see "Mathematics" in the ".form-autocomplete-selection" "css_element"
|
||||
And I set the following fields to these values:
|
||||
| Tags | Algebra |
|
||||
| Tags | Mathematics, Algebra |
|
||||
And I press "Save changes"
|
||||
And I am on "Course 2" course homepage
|
||||
And I navigate to "Course tags" in current page administration
|
||||
|
||||
@@ -31,9 +31,11 @@ Feature: Rename roles within a course
|
||||
And "Learner" "button" should exist
|
||||
And I navigate to course participants
|
||||
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
|
||||
And I should see "Tutor (Non-editing teacher)" in the ".form-autocomplete-suggestions" "css_element"
|
||||
And I open the autocomplete suggestions list in the "Filter 1" "fieldset"
|
||||
And I should see "Learner (Student)" in the ".form-autocomplete-suggestions" "css_element"
|
||||
And I press the escape key
|
||||
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Tutor (Non-editing teacher)"
|
||||
|
||||
And I click on "Student 1's role assignments" "link"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Student 1" "table_row"
|
||||
And "Tutor (Non-editing teacher)" "autocomplete_suggestions" should exist
|
||||
@@ -53,6 +55,6 @@ Feature: Rename roles within a course
|
||||
And "Learner" "button" should not exist
|
||||
And I navigate to course participants
|
||||
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
|
||||
And I open the autocomplete suggestions list in the "Filter 1" "fieldset"
|
||||
And I should see "Non-editing teacher" in the ".form-autocomplete-suggestions" "css_element"
|
||||
And I should see "Student" in the ".form-autocomplete-suggestions" "css_element"
|
||||
|
||||
@@ -121,7 +121,8 @@ class services_content_item_service_testcase extends \advanced_testcase {
|
||||
|
||||
// The call to get_all_content_items() should return the same items as for the course,
|
||||
// given the user in an editing teacher and can add manual lti instances.
|
||||
$this->assertEquals(array_column($allcontentitems, 'name'), array_column($coursecontentitems, 'name'));
|
||||
$this->assertContains('lti', array_column($coursecontentitems, 'name'));
|
||||
$this->assertContains('lti', array_column($allcontentitems, 'name'));
|
||||
|
||||
// Now removing the cap 'mod/lti:addinstance'. This will restrict those items returned by the course-specific method.
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
|
||||
@@ -118,7 +118,7 @@ class core_customfield_external extends external_api {
|
||||
'component' => new external_value(PARAM_COMPONENT, 'component'),
|
||||
'area' => new external_value(PARAM_ALPHANUMEXT, 'area'),
|
||||
'itemid' => new external_value(PARAM_INT, 'itemid'),
|
||||
'usescategories' => new external_value(PARAM_INT, 'view has categories'),
|
||||
'usescategories' => new external_value(PARAM_BOOL, 'view has categories'),
|
||||
'categories' => new external_multiple_structure(
|
||||
new external_single_structure(
|
||||
array(
|
||||
|
||||
@@ -12,7 +12,6 @@ Feature: Managers can manage categories for course custom fields
|
||||
Then I should see "Other fields" in the "#customfield_catlist" "css_element"
|
||||
And I navigate to "Reports > Logs" in site administration
|
||||
And I press "Get these logs"
|
||||
And I log out
|
||||
|
||||
Scenario: Edit a category name for custom course fields
|
||||
Given the following "custom field categories" exist:
|
||||
@@ -20,15 +19,12 @@ Feature: Managers can manage categories for course custom fields
|
||||
| Category for test | core_course | course | 0 |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Course custom fields" in site administration
|
||||
And I click on "Edit category name" "link" in the "//div[contains(@class,'categoryinstance') and contains(.,'Category for test')]" "xpath_element"
|
||||
And I set the field "New value for Category for test" to "Good fields"
|
||||
And I press key "13" in the field "New value for Category for test"
|
||||
And I set the field "Edit category name" in the "//div[contains(@class,'categoryinstance') and contains(.,'Category for test')]" "xpath_element" to "Good fields"
|
||||
Then I should not see "Category for test" in the "#customfield_catlist" "css_element"
|
||||
And "New value for Category for test" "field" should not exist
|
||||
And I should see "Good fields" in the "#customfield_catlist" "css_element"
|
||||
And I navigate to "Reports > Logs" in site administration
|
||||
And I press "Get these logs"
|
||||
And I log out
|
||||
|
||||
Scenario: Delete a category for custom course fields
|
||||
Given the following "custom field categories" exist:
|
||||
@@ -46,7 +42,6 @@ Feature: Managers can manage categories for course custom fields
|
||||
Then I should not see "Test category" in the "#customfield_catlist" "css_element"
|
||||
And I navigate to "Reports > Logs" in site administration
|
||||
And I press "Get these logs"
|
||||
And I log out
|
||||
|
||||
Scenario: Move field in the course custom fields to another category
|
||||
Given the following "custom field categories" exist:
|
||||
@@ -78,7 +73,6 @@ Feature: Managers can manage categories for course custom fields
|
||||
And I press "Move \"Field1\""
|
||||
And I follow "After field Field2"
|
||||
And "Field1" "text" should appear after "Field2" "text"
|
||||
And I log out
|
||||
|
||||
Scenario: Reorder course custom field categories
|
||||
Given the following "custom field categories" exist:
|
||||
@@ -108,4 +102,3 @@ Feature: Managers can manage categories for course custom fields
|
||||
And "Field1" "text" should appear after "Category1" "text"
|
||||
And "Category2" "text" should appear after "Field1" "text"
|
||||
And "Category3" "text" should appear after "Category2" "text"
|
||||
And I log out
|
||||
|
||||
@@ -36,7 +36,7 @@ if ($ADMIN->fulltree) {
|
||||
$student = get_archetype_roles('student');
|
||||
$student = reset($student);
|
||||
$settings->add(new admin_setting_configselect('enrol_cohort/roleid',
|
||||
get_string('defaultrole', 'role'), '', $student->id, $options));
|
||||
get_string('defaultrole', 'role'), '', $student->id ?? null, $options));
|
||||
|
||||
$options = array(
|
||||
ENROL_EXT_REMOVED_UNENROL => get_string('extremovedunenrol', 'enrol'),
|
||||
|
||||
@@ -86,7 +86,11 @@ if ($ADMIN->fulltree) {
|
||||
$options = get_default_enrol_roles(context_system::instance());
|
||||
$student = get_archetype_roles('student');
|
||||
$student = reset($student);
|
||||
$settings->add(new admin_setting_configselect('enrol_database/defaultrole', get_string('defaultrole', 'enrol_database'), get_string('defaultrole_desc', 'enrol_database'), $student->id, $options));
|
||||
$settings->add(new admin_setting_configselect('enrol_database/defaultrole',
|
||||
get_string('defaultrole', 'enrol_database'),
|
||||
get_string('defaultrole_desc', 'enrol_database'),
|
||||
$student->id ?? null,
|
||||
$options));
|
||||
}
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('enrol_database/ignorehiddencourses', get_string('ignorehiddencourses', 'enrol_database'), get_string('ignorehiddencourses_desc', 'enrol_database'), 0));
|
||||
|
||||
@@ -45,6 +45,10 @@ class enrol_fee_plugin extends enrol_plugin {
|
||||
$currencies[$c] = new lang_string($c, 'core_currencies');
|
||||
}
|
||||
|
||||
uasort($currencies, function($a, $b) {
|
||||
return strcmp($a, $b);
|
||||
});
|
||||
|
||||
return $currencies;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ if ($ADMIN->fulltree) {
|
||||
$student = get_archetype_roles('student');
|
||||
$student = reset($student);
|
||||
$settings->add(new admin_setting_configselect('enrol_fee/roleid',
|
||||
get_string('defaultrole', 'enrol_fee'), get_string('defaultrole_desc', 'enrol_fee'), $student->id, $options));
|
||||
get_string('defaultrole', 'enrol_fee'), get_string('defaultrole_desc', 'enrol_fee'), $student->id ?? null, $options));
|
||||
}
|
||||
|
||||
$settings->add(new admin_setting_configduration('enrol_fee/enrolperiod',
|
||||
|
||||
@@ -63,7 +63,7 @@ if ($ADMIN->fulltree) {
|
||||
$student = get_archetype_roles('student');
|
||||
$student = reset($student);
|
||||
$settings->add(new admin_setting_configselect('enrol_manual/roleid',
|
||||
get_string('defaultrole', 'role'), '', $student->id, $options));
|
||||
get_string('defaultrole', 'role'), '', $student->id ?? null, $options));
|
||||
}
|
||||
|
||||
$options = array(2 => get_string('coursestart'), 3 => get_string('today'), 4 => get_string('now', 'enrol_manual'));
|
||||
|
||||
@@ -130,18 +130,17 @@ Feature: Teacher can search and enrol users one by one into the course
|
||||
Scenario: Searching for a non-existing user
|
||||
Given I navigate to course participants
|
||||
And I press "Enrol users"
|
||||
And I set the field "Select users" to "qwertyuiop"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
|
||||
And I click on "Select users" "field"
|
||||
And I type "qwertyuiop"
|
||||
Then I should see "No suggestions"
|
||||
|
||||
@javascript
|
||||
Scenario: If there are less than 100 matching users, all are displayed for selection
|
||||
Given I navigate to course participants
|
||||
And I press "Enrol users"
|
||||
When I set the field "Select users" to "example.com"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
|
||||
And I click on "Student 099" item in the autocomplete list
|
||||
Then I should see "Student 099"
|
||||
When I click on "Select users" "field"
|
||||
And I type "example.com"
|
||||
Then "Student 099" "autocomplete_suggestions" should exist
|
||||
|
||||
@javascript
|
||||
Scenario: If there are more than 100 matching users, inform there are too many.
|
||||
@@ -151,8 +150,8 @@ Feature: Teacher can search and enrol users one by one into the course
|
||||
| student101 | Student | 101 | student101@example.com |
|
||||
And I navigate to course participants
|
||||
And I press "Enrol users"
|
||||
When I set the field "Select users" to "example.com"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
|
||||
When I click on "Select users" "field"
|
||||
And I type "example.com"
|
||||
Then I should see "Too many users (>100) to show"
|
||||
|
||||
@javascript
|
||||
@@ -161,8 +160,8 @@ Feature: Teacher can search and enrol users one by one into the course
|
||||
| maxusersperpage | 5 |
|
||||
And I navigate to course participants
|
||||
And I press "Enrol users"
|
||||
When I set the field "Select users" to "student00"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
|
||||
When I click on "Select users" "field"
|
||||
And I type "student00"
|
||||
Then I should see "Too many users (>5) to show"
|
||||
|
||||
@javascript
|
||||
@@ -177,18 +176,18 @@ Feature: Teacher can search and enrol users one by one into the course
|
||||
When I am on "Course 001" course homepage
|
||||
Then I navigate to course participants
|
||||
And I press "Enrol users"
|
||||
When I set the field "Select users" to "student100@example.com"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
|
||||
And I click on "Select users" "field"
|
||||
And I type "student100@example.com"
|
||||
Then I should see "student100@example.com, CITY1, GB, 1234567892, 1234567893, ABC1, ABC2"
|
||||
# Remove identity field in setting User policies
|
||||
And the following config values are set as admin:
|
||||
| showuseridentity | idnumber,email,phone1,phone2,department,institution |
|
||||
When I am on "Course 001" course homepage
|
||||
And I am on "Course 001" course homepage
|
||||
And I navigate to course participants
|
||||
And I press "Enrol users"
|
||||
When I set the field "Select users" to "student100@example.com"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
|
||||
Then I should see "student100@example.com, 1234567892, 1234567893, ABC1, ABC2"
|
||||
And I click on "Select users" "field"
|
||||
And I type "student100@example.com"
|
||||
And I should see "student100@example.com, 1234567892, 1234567893, ABC1, ABC2"
|
||||
|
||||
# The following tests are commented out as a result of MDL-66339.
|
||||
# @javascript
|
||||
|
||||
+31
-17
@@ -328,31 +328,45 @@ class enrol_meta_plugin extends enrol_plugin {
|
||||
*/
|
||||
public function edit_instance_validation($data, $files, $instance, $context) {
|
||||
global $DB;
|
||||
|
||||
$errors = array();
|
||||
$thiscourseid = $context->instanceid;
|
||||
$c = false;
|
||||
|
||||
if (!empty($data['customint1'])) {
|
||||
$courses = is_array($data['customint1']) ? $data['customint1'] : [$data['customint1']];
|
||||
foreach ($courses as $courseid) {
|
||||
$c = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$coursecontext = context_course::instance($c->id);
|
||||
$coursesidarr = is_array($data['customint1']) ? $data['customint1'] : [$data['customint1']];
|
||||
list($coursesinsql, $coursesinparams) = $DB->get_in_or_equal($coursesidarr, SQL_PARAMS_NAMED, 'metacourseid');
|
||||
if ($coursesrecords = $DB->get_records_select('course', "id {$coursesinsql}",
|
||||
$coursesinparams, '', 'id,visible')) {
|
||||
// Cast NULL to 0 to avoid possible mess with the SQL.
|
||||
$instanceid = $instance->id ?? 0;
|
||||
|
||||
$sqlexists = 'enrol = :meta AND courseid = :currentcourseid AND customint1 = :courseid AND id != :id';
|
||||
$existing = $DB->record_exists_select('enrol', $sqlexists, [
|
||||
$existssql = "enrol = :meta AND courseid = :currentcourseid AND id != :id AND customint1 {$coursesinsql}";
|
||||
$existsparams = [
|
||||
'meta' => 'meta',
|
||||
'currentcourseid' => $thiscourseid,
|
||||
'courseid' => $c->id,
|
||||
'id' => $instance->id
|
||||
]);
|
||||
|
||||
if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
$errors['customint1'] = get_string('error');
|
||||
} else if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
|
||||
$errors['customint1'] = get_string('error');
|
||||
} else if ($c->id == SITEID or $c->id == $thiscourseid or $existing) {
|
||||
$errors['customint1'] = get_string('error');
|
||||
'id' => $instanceid
|
||||
];
|
||||
$existsparams += $coursesinparams;
|
||||
if ($DB->record_exists_select('enrol', $existssql, $existsparams)) {
|
||||
// We may leave right here as further checks do not make sense in case we have existing enrol records
|
||||
// with the parameters from above.
|
||||
$errors['customint1'] = get_string('invalidcourseid', 'error');
|
||||
} else {
|
||||
foreach ($coursesrecords as $coursesrecord) {
|
||||
$coursecontext = context_course::instance($coursesrecord->id);
|
||||
if (!$coursesrecord->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
$errors['customint1'] = get_string('nopermissions', 'error',
|
||||
'moodle/course:viewhiddencourses');
|
||||
} else if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
|
||||
$errors['customint1'] = get_string('nopermissions', 'error',
|
||||
'enrol/meta:selectaslinked');
|
||||
} else if ($coursesrecord->id == SITEID or $coursesrecord->id == $thiscourseid) {
|
||||
$errors['customint1'] = get_string('invalidcourseid', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$errors['customint1'] = get_string('invalidcourseid', 'error');
|
||||
}
|
||||
} else {
|
||||
$errors['customint1'] = get_string('required');
|
||||
|
||||
@@ -922,4 +922,146 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
|
||||
// Meta-link enrolment has enrol actions for suspended students -- unenrol.
|
||||
$this->assertCount(1, $actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test how data for instance editing is validated.
|
||||
*/
|
||||
public function test_edit_instance_validation() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$metaplugin = enrol_get_plugin('meta');
|
||||
|
||||
// A course with meta enrolment.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
|
||||
// Create a meta enrolment instance.
|
||||
$instance = (object)$metaplugin->get_instance_defaults();
|
||||
$instance->id = null;
|
||||
$instance->courseid = $course->id;
|
||||
$instance->status = ENROL_INSTANCE_ENABLED;
|
||||
// Emulate the form data.
|
||||
$data = [
|
||||
'customint1' => 0,
|
||||
'customint2' => 0
|
||||
];
|
||||
// Test when no valid 'customint1' field (meta courses links) is provided.
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
// We're going to check the string contents of the errors returned as this is the only way
|
||||
// to differentiate the errors produced by the 'edit_instance_validation()' method somehow.
|
||||
// The method always returns what the edit instance form expects and this is an array of form fields
|
||||
// with the corresponding errors messages.
|
||||
$this->assertEquals('Required', $errors['customint1']);
|
||||
|
||||
// Test when 'customint1' contains an unknown course.
|
||||
// Fetch the max course id from the courses table and increment it to get
|
||||
// the course id which surely doesn't exist.
|
||||
$maxid = $DB->get_field_sql('SELECT MAX(id) FROM {course}');
|
||||
// Use the same instance as before but set another data.
|
||||
$data = [
|
||||
'customint1' => [$maxid + 1],
|
||||
'customint2' => 0
|
||||
];
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertEquals('You are trying to use an invalid course ID', $errors['customint1']);
|
||||
|
||||
// Test when 'customint1' field already contains courses meta linked with the current one.
|
||||
$metacourse1 = $this->getDataGenerator()->create_course();
|
||||
$metaplugin->add_instance($course, array('customint1' => $metacourse1->id));
|
||||
// Use the same instance as before but set another data.
|
||||
$data = [
|
||||
'customint1' => [$metacourse1->id],
|
||||
'customint2' => 0
|
||||
];
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertEquals('You are trying to use an invalid course ID', $errors['customint1']);
|
||||
|
||||
// Test when a course is set as a not visible and a user doesn't have the capability to use it here.
|
||||
$metacourse2record = new stdClass();
|
||||
$metacourse2record->visible = 0;
|
||||
$metacourse2 = $this->getDataGenerator()->create_course($metacourse2record);
|
||||
$metacourse2context = context_course::instance($metacourse2->id);
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
|
||||
role_assign($teacherrole->id, $user->id, $metacourse2context->id);
|
||||
unassign_capability('moodle/course:viewhiddencourses', $teacherrole->id);
|
||||
$this->setUser($user);
|
||||
|
||||
// Use the same instance as before but set another data.
|
||||
$data = [
|
||||
'customint1' => [$metacourse2->id],
|
||||
'customint2' => 0
|
||||
];
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertEquals('Sorry, but you do not currently have permissions to do that (moodle/course:viewhiddencourses).',
|
||||
$errors['customint1']);
|
||||
|
||||
// Revert some changes from the last assertion to reuse the course.
|
||||
$metacourse2->visible = 1;
|
||||
$DB->update_record('course', $metacourse2);
|
||||
assign_capability('moodle/course:viewhiddencourses', CAP_ALLOW,
|
||||
$teacherrole->id, context_course::instance($metacourse2->id));
|
||||
|
||||
// Test with no 'enrol/meta:selectaslinked' capability.
|
||||
unassign_capability('enrol/meta:selectaslinked', $teacherrole->id);
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertEquals('Sorry, but you do not currently have permissions to do that (enrol/meta:selectaslinked).',
|
||||
$errors['customint1']);
|
||||
|
||||
// Back to admin user to regain the capabilities quickly.
|
||||
$this->setAdminUser();
|
||||
|
||||
// Test when meta course id is the site id.
|
||||
$site = $DB->get_record('course', ['id' => SITEID]);
|
||||
// Use the same instance as before but set another data.
|
||||
$data = [
|
||||
'customint1' => [$site->id],
|
||||
'customint2' => 0
|
||||
];
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertEquals('You are trying to use an invalid course ID', $errors['customint1']);
|
||||
|
||||
// Test when meta course id is id of the current course.
|
||||
// Use the same instance as before but set another data.
|
||||
$data = [
|
||||
'customint1' => [$course->id],
|
||||
'customint2' => 0
|
||||
];
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertEquals('You are trying to use an invalid course ID', $errors['customint1']);
|
||||
|
||||
// Test with the 'customint2' field set (which is groups).
|
||||
// Prepare some groups data.
|
||||
$this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
$this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
$groups = [];
|
||||
foreach (groups_get_all_groups($course->id) as $group) {
|
||||
$groups[$group->id] = format_string($group->name, true, array('context' => $coursecontext));
|
||||
}
|
||||
|
||||
// Use the same instance as before but set another data.
|
||||
// Use a non-existing group id.
|
||||
if (!$maxid = $DB->get_field_sql('SELECT MAX(id) FROM {groups}')) {
|
||||
$maxid = 0;
|
||||
}
|
||||
$data = [
|
||||
'customint1' => [$metacourse2->id],
|
||||
'customint2' => [$maxid + 1]
|
||||
];
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertArrayHasKey('customint2', $errors);
|
||||
|
||||
// Test with valid data.
|
||||
$validgroup = reset($groups);
|
||||
$data = [
|
||||
'customint1' => [$metacourse2->id],
|
||||
'customint2' => $validgroup
|
||||
];
|
||||
$errors = $metaplugin->edit_instance_validation($data, [], $instance, $coursecontext);
|
||||
$this->assertArrayNotHasKey('customint1', $errors);
|
||||
$this->assertArrayNotHasKey('customint2', $errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ if ($ADMIN->fulltree) {
|
||||
$student = reset($student);
|
||||
$settings->add(new admin_setting_configselect_with_advanced('enrol_mnet/roleid',
|
||||
get_string('defaultrole', 'role'), '',
|
||||
array('value'=>$student->id, 'adv'=>true), $options));
|
||||
array('value' => $student->id ?? null, 'adv' => true), $options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,10 @@ if ($ADMIN->fulltree) {
|
||||
$student = get_archetype_roles('student');
|
||||
$student = reset($student);
|
||||
$settings->add(new admin_setting_configselect('enrol_paypal/roleid',
|
||||
get_string('defaultrole', 'enrol_paypal'), get_string('defaultrole_desc', 'enrol_paypal'), $student->id, $options));
|
||||
get_string('defaultrole', 'enrol_paypal'),
|
||||
get_string('defaultrole_desc', 'enrol_paypal'),
|
||||
$student->id ?? null,
|
||||
$options));
|
||||
}
|
||||
|
||||
$settings->add(new admin_setting_configduration('enrol_paypal/enrolperiod',
|
||||
|
||||
@@ -79,7 +79,10 @@ if ($ADMIN->fulltree) {
|
||||
$student = get_archetype_roles('student');
|
||||
$student = reset($student);
|
||||
$settings->add(new admin_setting_configselect('enrol_self/roleid',
|
||||
get_string('defaultrole', 'enrol_self'), get_string('defaultrole_desc', 'enrol_self'), $student->id, $options));
|
||||
get_string('defaultrole', 'enrol_self'),
|
||||
get_string('defaultrole_desc', 'enrol_self'),
|
||||
$student->id ?? null,
|
||||
$options));
|
||||
}
|
||||
|
||||
$settings->add(new admin_setting_configduration('enrol_self/enrolperiod',
|
||||
|
||||
@@ -32,6 +32,6 @@ Feature: Users can be added to multiple groups at once
|
||||
And I click on "Group 1" item in the autocomplete list
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "student1" "table_row"
|
||||
And I click on "Group 2" item in the autocomplete list
|
||||
And I press key "27" in the field "Edit groups for \"Student 1\""
|
||||
And I press the escape key
|
||||
And I click on "Save changes" "link" in the "student1" "table_row"
|
||||
Then I should see "Group 1, Group 2"
|
||||
|
||||
@@ -189,6 +189,9 @@ class filter_tex extends moodle_text_filter {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanitize the decoded string, because filter_text_image() injects the final string between script tags.
|
||||
$texexp = clean_param($texexp, PARAM_TEXT);
|
||||
|
||||
$md5 = md5($texexp);
|
||||
if (!$DB->record_exists("cache_filters", array("filter"=>"tex", "md5key"=>$md5))) {
|
||||
$texcache = new stdClass();
|
||||
|
||||
@@ -128,7 +128,13 @@ class fetch extends external_api {
|
||||
}
|
||||
|
||||
// Fetch the actual data.
|
||||
$gradeduser = \core_user::get_user($gradeduserid);
|
||||
$gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST);
|
||||
|
||||
// One can access its own grades. Others just if they're graders.
|
||||
if ($gradeduserid != $USER->id) {
|
||||
$gradeitem->require_user_can_grade($gradeduser, $USER);
|
||||
}
|
||||
|
||||
$hasgrade = $gradeitem->user_has_grade($gradeduser);
|
||||
$grade = $gradeitem->get_grade_for_user($gradeduser, $USER);
|
||||
|
||||
|
||||
@@ -127,7 +127,12 @@ class fetch extends external_api {
|
||||
throw new moodle_exception("The {$itemname} item in {$component}/{$contextid} is not configured for grading with scales");
|
||||
}
|
||||
|
||||
$gradeduser = \core_user::get_user($gradeduserid);
|
||||
$gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST);
|
||||
|
||||
// One can access its own grades. Others just if they're graders.
|
||||
if ($gradeduserid != $USER->id) {
|
||||
$gradeitem->require_user_can_grade($gradeduser, $USER);
|
||||
}
|
||||
|
||||
// Set up some items we need to return on other interfaces.
|
||||
$gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->get_grade_item()->id, 'userid' => $gradeduser->id]);
|
||||
|
||||
+7
-2
@@ -100,7 +100,7 @@ class fetch extends external_api {
|
||||
* @since Moodle 3.8
|
||||
*/
|
||||
public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array {
|
||||
global $CFG;
|
||||
global $CFG, $USER;
|
||||
require_once("{$CFG->libdir}/gradelib.php");
|
||||
[
|
||||
'component' => $component,
|
||||
@@ -133,7 +133,12 @@ class fetch extends external_api {
|
||||
}
|
||||
|
||||
// Fetch the actual data.
|
||||
$gradeduser = core_user::get_user($gradeduserid);
|
||||
$gradeduser = core_user::get_user($gradeduserid, '*', MUST_EXIST);
|
||||
|
||||
// One can access its own grades. Others just if they're graders.
|
||||
if ($gradeduserid != $USER->id) {
|
||||
$gradeitem->require_user_can_grade($gradeduser, $USER);
|
||||
}
|
||||
|
||||
return self::get_fetch_data($gradeitem, $gradeduser);
|
||||
}
|
||||
|
||||
@@ -657,8 +657,9 @@ class gradingform_guide_controller extends gradingform_controller {
|
||||
}
|
||||
$returnvalue['maxscore'] = $maxscore;
|
||||
$returnvalue['minscore'] = 0;
|
||||
if (!empty($this->moduleinstance->grade)) {
|
||||
$graderange = make_grades_menu($this->moduleinstance->grade);
|
||||
$fieldname = \core_grades\component_gradeitems::get_field_name_for_itemname($this->component, $this->area, 'grade');
|
||||
if (!empty($this->moduleinstance->{$fieldname})) {
|
||||
$graderange = make_grades_menu($this->moduleinstance->{$fieldname});
|
||||
$returnvalue['modulegrade'] = count($graderange) - 1;
|
||||
}
|
||||
return $returnvalue;
|
||||
|
||||
+59
-9
@@ -178,8 +178,6 @@ class fetch_test extends advanced_testcase {
|
||||
*/
|
||||
public function test_execute_fetch_graded(): void {
|
||||
$this->resetAfterTest();
|
||||
$generator = \testing_util::get_data_generator();
|
||||
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
|
||||
|
||||
[
|
||||
'forum' => $forum,
|
||||
@@ -189,23 +187,74 @@ class fetch_test extends advanced_testcase {
|
||||
'teacher' => $teacher,
|
||||
] = $this->get_test_data();
|
||||
|
||||
$this->setUser($teacher);
|
||||
$this->execute_and_assert_fetch($forum, $controller, $definition, $teacher, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class mates should not get other's grades.
|
||||
*/
|
||||
public function test_execute_fetch_does_not_return_data_to_other_students(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
[
|
||||
'forum' => $forum,
|
||||
'controller' => $controller,
|
||||
'definition' => $definition,
|
||||
'student' => $student,
|
||||
'teacher' => $teacher,
|
||||
'course' => $course,
|
||||
] = $this->get_test_data();
|
||||
|
||||
$evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
$this->execute_and_assert_fetch($forum, $controller, $definition, $evilstudent, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grades can be returned to graded user.
|
||||
*/
|
||||
public function test_execute_fetch_return_data_to_graded_user(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
[
|
||||
'forum' => $forum,
|
||||
'controller' => $controller,
|
||||
'definition' => $definition,
|
||||
'student' => $student,
|
||||
'teacher' => $teacher,
|
||||
] = $this->get_test_data();
|
||||
|
||||
$this->execute_and_assert_fetch($forum, $controller, $definition, $student, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes and performs all the assertions of the fetch method with the given parameters.
|
||||
*/
|
||||
private function execute_and_assert_fetch ($forum, $controller, $definition, $fetcheruser, $grader, $gradeduser) {
|
||||
$generator = \testing_util::get_data_generator();
|
||||
$guidegenerator = $generator->get_plugin_generator('gradingform_guide');
|
||||
|
||||
$this->setUser($grader);
|
||||
|
||||
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
|
||||
$grade = $gradeitem->get_grade_for_user($student, $teacher);
|
||||
$instance = $gradeitem->get_advanced_grading_instance($teacher, $grade);
|
||||
$grade = $gradeitem->get_grade_for_user($gradeduser, $grader);
|
||||
$instance = $gradeitem->get_advanced_grading_instance($grader, $grade);
|
||||
|
||||
$submissiondata = $guidegenerator->get_test_form_data($controller, (int) $student->id,
|
||||
$submissiondata = $guidegenerator->get_test_form_data($controller, (int) $gradeduser->id,
|
||||
10, 'Propper good speling',
|
||||
0, 'ASCII art is not a picture'
|
||||
);
|
||||
|
||||
$gradeitem->store_grade_from_formdata($student, $teacher, (object) [
|
||||
$gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [
|
||||
'instanceid' => $instance->get_id(),
|
||||
'advancedgrading' => $submissiondata,
|
||||
]);
|
||||
|
||||
$this->setUser($fetcheruser);
|
||||
|
||||
// Set up some items we need to return on other interfaces.
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id);
|
||||
$result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
@@ -233,7 +282,7 @@ class fetch_test extends advanced_testcase {
|
||||
$this->assertEquals(100, $result['grade']['maxgrade']);
|
||||
|
||||
$this->assertArrayHasKey('gradedby', $result['grade']);
|
||||
$this->assertEquals(fullname($teacher), $result['grade']['gradedby']);
|
||||
$this->assertEquals(fullname($grader), $result['grade']['gradedby']);
|
||||
|
||||
$this->assertArrayHasKey('criterion', $result['grade']);
|
||||
$criteria = $result['grade']['criterion'];
|
||||
@@ -326,6 +375,7 @@ class fetch_test extends advanced_testcase {
|
||||
'definition' => $definition,
|
||||
'student' => $student,
|
||||
'teacher' => $teacher,
|
||||
'course' => $course,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -93,7 +93,7 @@ class fetch extends external_api {
|
||||
* @since Moodle 3.8
|
||||
*/
|
||||
public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid): array {
|
||||
global $CFG;
|
||||
global $CFG, $USER;
|
||||
require_once("{$CFG->libdir}/gradelib.php");
|
||||
[
|
||||
'component' => $component,
|
||||
@@ -126,7 +126,12 @@ class fetch extends external_api {
|
||||
}
|
||||
|
||||
// Fetch the actual data.
|
||||
$gradeduser = \core_user::get_user($gradeduserid);
|
||||
$gradeduser = \core_user::get_user($gradeduserid, '*', MUST_EXIST);
|
||||
|
||||
// One can access its own grades. Others just if they're graders.
|
||||
if ($gradeduserid != $USER->id) {
|
||||
$gradeitem->require_user_can_grade($gradeduser, $USER);
|
||||
}
|
||||
|
||||
return self::get_fetch_data($gradeitem, $gradeduser);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
|
||||
.gradingform_rubric .criterion .description {
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
+58
-9
@@ -186,8 +186,6 @@ class fetch_test extends advanced_testcase {
|
||||
*/
|
||||
public function test_execute_fetch_graded(): void {
|
||||
$this->resetAfterTest();
|
||||
$generator = \testing_util::get_data_generator();
|
||||
$rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
|
||||
|
||||
[
|
||||
'forum' => $forum,
|
||||
@@ -197,23 +195,73 @@ class fetch_test extends advanced_testcase {
|
||||
'teacher' => $teacher,
|
||||
] = $this->get_test_data();
|
||||
|
||||
$this->setUser($teacher);
|
||||
$this->execute_and_assert_fetch($forum, $controller, $definition, $teacher, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class mates should not get other's grades.
|
||||
*/
|
||||
public function test_execute_fetch_does_not_return_data_to_other_students(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
[
|
||||
'forum' => $forum,
|
||||
'controller' => $controller,
|
||||
'definition' => $definition,
|
||||
'student' => $student,
|
||||
'teacher' => $teacher,
|
||||
'course' => $course,
|
||||
] = $this->get_test_data();
|
||||
|
||||
$evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
$this->execute_and_assert_fetch($forum, $controller, $definition, $evilstudent, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grades can be returned to graded user.
|
||||
*/
|
||||
public function test_execute_fetch_return_data_to_graded_user(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
[
|
||||
'forum' => $forum,
|
||||
'controller' => $controller,
|
||||
'definition' => $definition,
|
||||
'student' => $student,
|
||||
'teacher' => $teacher,
|
||||
] = $this->get_test_data();
|
||||
|
||||
$this->execute_and_assert_fetch($forum, $controller, $definition, $student, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes and performs all the assertions of the fetch method with the given parameters.
|
||||
*/
|
||||
private function execute_and_assert_fetch ($forum, $controller, $definition, $fetcheruser, $grader, $gradeduser) {
|
||||
$generator = \testing_util::get_data_generator();
|
||||
$rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
|
||||
|
||||
$this->setUser($grader);
|
||||
|
||||
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
|
||||
$grade = $gradeitem->get_grade_for_user($student, $teacher);
|
||||
$instance = $gradeitem->get_advanced_grading_instance($teacher, $grade);
|
||||
$grade = $gradeitem->get_grade_for_user($gradeduser, $grader);
|
||||
$instance = $gradeitem->get_advanced_grading_instance($grader, $grade);
|
||||
|
||||
$submissiondata = $rubricgenerator->get_test_form_data($controller, (int) $student->id,
|
||||
$submissiondata = $rubricgenerator->get_test_form_data($controller, (int) $gradeduser->id,
|
||||
0, 'Too many mistakes. Please try again.',
|
||||
2, 'Great number of pictures. Well done.'
|
||||
);
|
||||
|
||||
$gradeitem->store_grade_from_formdata($student, $teacher, (object) [
|
||||
$gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [
|
||||
'instanceid' => $instance->get_id(),
|
||||
'advancedgrading' => $submissiondata,
|
||||
]);
|
||||
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
|
||||
$this->setUser($fetcheruser);
|
||||
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id);
|
||||
$result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
@@ -241,7 +289,7 @@ class fetch_test extends advanced_testcase {
|
||||
$this->assertEquals(100, $result['grade']['maxgrade']);
|
||||
|
||||
$this->assertArrayHasKey('gradedby', $result['grade']);
|
||||
$this->assertEquals(fullname($teacher), $result['grade']['gradedby']);
|
||||
$this->assertEquals(fullname($grader), $result['grade']['gradedby']);
|
||||
|
||||
$this->assertArrayHasKey('criteria', $result['grade']);
|
||||
$criteria = $result['grade']['criteria'];
|
||||
@@ -348,6 +396,7 @@ class fetch_test extends advanced_testcase {
|
||||
'definition' => $definition,
|
||||
'student' => $student,
|
||||
'teacher' => $teacher,
|
||||
'course' => $course,
|
||||
];
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -59,18 +59,18 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
|
||||
Then I should see a grade field for "Student 2" and grade item "Item VU"
|
||||
And I should not see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "33"
|
||||
And I press key "13" in the field "ajaxgrade"
|
||||
And I press the enter key
|
||||
And I should not see a grade field for "Student 2" and grade item "Item VU"
|
||||
And I should not see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I click on student "Student 3" for grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "50"
|
||||
And I press key "13" in the field "ajaxgrade"
|
||||
And I click on student "Student 3" for grade item "Item 1"
|
||||
And I set the field "ajaxgrade" to "80"
|
||||
And I press key "13" in the field "ajaxgrade"
|
||||
And I click on student "Student 3" for grade item "Item SU"
|
||||
And I set the field "ajaxgrade" to "Very good"
|
||||
And I press key "13" in the field "ajaxgrade"
|
||||
And I press the shift tab key
|
||||
And I click on student "Student 3" for grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "50"
|
||||
And I press the enter key
|
||||
And I click on student "Student 3" for grade item "Item 1"
|
||||
And I set the field "ajaxgrade" to "80"
|
||||
And I press the enter key
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -6- | -7- | -13- | -16- |
|
||||
| Student 2 | - | 33.00 | - | 33.00 |
|
||||
@@ -88,7 +88,7 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
|
||||
And I should see a grade field for "Student 1" and grade item "Course total"
|
||||
And I should not see a feedback field for "Student 1" and grade item "Course total"
|
||||
And I set the field "ajaxgrade" to "90"
|
||||
And I press key "13" in the field "ajaxgrade"
|
||||
And I press the enter key
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -16- |
|
||||
| Student 1 | 90.00 |
|
||||
@@ -112,7 +112,7 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
|
||||
And I should see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "33"
|
||||
And I set the field "ajaxfeedback" to "Student 2 VU feedback"
|
||||
And I press key "13" in the field "ajaxfeedback"
|
||||
And I press the enter key
|
||||
And I click on student "Student 3" for grade item "Item VL"
|
||||
And I should not see a grade field for "Student 3" and grade item "Item VL"
|
||||
And I should not see a feedback field for "Student 3" and grade item "Item VL"
|
||||
@@ -120,11 +120,11 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
|
||||
And I should not see a grade field for "Student 3" and grade item "Item TU"
|
||||
And I should see a feedback field for "Student 3" and grade item "Item TU"
|
||||
And I set the field "ajaxfeedback" to "Student 3 TU feedback"
|
||||
And I press key "13" in the field "ajaxfeedback"
|
||||
And I press the enter key
|
||||
And I click on student "Student 2" for grade item "Item SU"
|
||||
And I set the field "ajaxgrade" to "Very good"
|
||||
And I set the field "ajaxfeedback" to "Student 2 SU feedback"
|
||||
And I press key "13" in the field "ajaxfeedback"
|
||||
And I press the enter key
|
||||
# Reload grader report:
|
||||
And I navigate to "View > User report" in the course gradebook
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
@@ -148,7 +148,7 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
|
||||
Then I should see a grade field for "Student 2" and grade item "Item VU"
|
||||
And I should see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "33"
|
||||
And I press key "13" in the field "ajaxgrade"
|
||||
And I press the enter key
|
||||
And I click on student "Student 2" for grade item "Course total"
|
||||
And I should not see a grade field for "Student 3" and grade item "Course total"
|
||||
And I should not see a feedback field for "Student 3" and grade item "Course total"
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
@core @core_grades
|
||||
Feature: Hidden grade items should be hidden when grade category is locked, but should be visible in overridden category
|
||||
In order to verify existing grades items display as expected
|
||||
As an teacher
|
||||
I need to modify grade items and grade categories
|
||||
I need to ensure existing grades display in an expected manner
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category | groupmode |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | idnumber |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
|
||||
| student1 | Student | 1 | student1@example.com | s1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Setup > Gradebook setup" in the course gradebook
|
||||
And I press "Add category"
|
||||
And I set the following fields to these values:
|
||||
| Category name | Test locked category |
|
||||
And I press "Save changes"
|
||||
And I press "Add grade item"
|
||||
And I set the following fields to these values:
|
||||
| Item name | Hidden item |
|
||||
| Hidden | 1 |
|
||||
| Grade category | Test locked category |
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
And I give the grade "50.00" to the user "Student 1" for the grade item "Hidden item"
|
||||
And I press "Save changes"
|
||||
And I navigate to "Setup > Gradebook setup" in the course gradebook
|
||||
And I set the following settings for grade item "Test locked category":
|
||||
| Locked | 1 |
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
|
||||
Scenario: Hidden grade items in locked category is hidden for teacher
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "View > User report" in the course gradebook
|
||||
And I select "Myself" from the "View report as" singleselect
|
||||
When I select "Student 1" from the "Select all or one user" singleselect
|
||||
Then the following should exist in the "user-grade" table:
|
||||
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
|
||||
| Test locked category total | 100.00 % | 50.00 | 0–100 | 50.00 % | - |
|
||||
| Course total | - | 50.00 | 0–100 | 50.00 % | - |
|
||||
|
||||
Scenario: Hidden grade items in locked category is hidden for student
|
||||
Given I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I navigate to "User report" in the course gradebook
|
||||
Then the following should exist in the "user-grade" table:
|
||||
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
|
||||
| Test locked category total | 100.00 % | - | 0–100 | - | - |
|
||||
| Course total | - | - | 0–100 | - | - |
|
||||
And I should not see "Hidden item"
|
||||
|
||||
Scenario: Hidden grade items in overridden category should show
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Setup > Gradebook setup" in the course gradebook
|
||||
And I press "Add category"
|
||||
And I set the following fields to these values:
|
||||
| Category name | Test overridden category B|
|
||||
And I press "Save changes"
|
||||
And I press "Add grade item"
|
||||
And I set the following fields to these values:
|
||||
| Item name | Cat b item |
|
||||
| Grade category | Test overridden category B |
|
||||
And I press "Save changes"
|
||||
When I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
And I give the grade "50.00" to the user "Student 1" for the grade item "Test overridden category B total"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "User report" in the course gradebook
|
||||
Then the following should exist in the "user-grade" table:
|
||||
| Grade item | Calculated weight | Grade | Range | Percentage | Contribution to course total |
|
||||
| Test locked category total | 50.00 % | - | 0–100 | - | - |
|
||||
| Test overridden category B total | 50.00 % | 50.00 | 0–100 | 50.00 % | - |
|
||||
| Course total | - | - | 0–200 | - | - |
|
||||
@@ -209,7 +209,7 @@ Feature: We can set the grade to pass value
|
||||
| Ratings > Grade to pass | 90 |
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
And I click on "Edit forum Rating grade for Test Forum 1" "link"
|
||||
And I click on "Edit forum Test Forum 1 rating" "link"
|
||||
And I expand all fieldsets
|
||||
Then the field "Grade to pass" matches value "90"
|
||||
And I set the field "Grade to pass" to "80"
|
||||
|
||||
@@ -161,14 +161,60 @@ class fetch_test extends advanced_testcase {
|
||||
$course = $forum->get_course_record();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$this->setUser($teacher);
|
||||
|
||||
$this->execute_and_assert_fetch($forum, $teacher, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class mates should not get other's grades.
|
||||
*/
|
||||
public function test_execute_fetch_does_not_return_data_to_other_students(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$forum = $this->get_forum_instance([
|
||||
// Negative numbers mean a scale.
|
||||
'grade_forum' => 5,
|
||||
]);
|
||||
$course = $forum->get_course_record();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
$this->execute_and_assert_fetch($forum, $evilstudent, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grades can be returned to graded user.
|
||||
*/
|
||||
public function test_execute_fetch_return_data_to_graded_user(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$forum = $this->get_forum_instance([
|
||||
// Negative numbers mean a scale.
|
||||
'grade_forum' => 5,
|
||||
]);
|
||||
$course = $forum->get_course_record();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
$this->execute_and_assert_fetch($forum, $student, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the fetch method with the given users and returns the result.
|
||||
*/
|
||||
private function execute_and_assert_fetch ($forum, $fetcheruser, $grader, $gradeduser) {
|
||||
$this->setUser($grader);
|
||||
|
||||
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
|
||||
$gradeitem->store_grade_from_formdata($student, $teacher, (object) [
|
||||
$gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [
|
||||
'grade' => 4,
|
||||
]);
|
||||
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
|
||||
$this->setUser($fetcheruser);
|
||||
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id);
|
||||
$result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
@@ -200,7 +246,9 @@ class fetch_test extends advanced_testcase {
|
||||
$this->assertEquals(5, $result['grade']['maxgrade']);
|
||||
|
||||
$this->assertArrayHasKey('gradedby', $result['grade']);
|
||||
$this->assertEquals(fullname($teacher), $result['grade']['gradedby']);
|
||||
$this->assertEquals(fullname($grader), $result['grade']['gradedby']);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -189,14 +189,75 @@ class fetch_test extends advanced_testcase {
|
||||
$course = $forum->get_course_record();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$this->setUser($teacher);
|
||||
|
||||
$this->execute_and_assert_fetch($forum, $options, $scale, $teacher, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class mates should not get other's grades.
|
||||
*/
|
||||
public function test_execute_fetch_does_not_return_data_to_other_students(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$options = [
|
||||
'A',
|
||||
'B',
|
||||
'C'
|
||||
];
|
||||
$scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]);
|
||||
|
||||
$forum = $this->get_forum_instance([
|
||||
// Negative numbers mean a scale.
|
||||
'grade_forum' => -1 * $scale->id
|
||||
]);
|
||||
$course = $forum->get_course_record();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
$evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
$this->execute_and_assert_fetch($forum, $options, $scale, $evilstudent, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grades can be returned to graded user.
|
||||
*/
|
||||
public function test_execute_fetch_return_data_to_graded_user(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$options = [
|
||||
'A',
|
||||
'B',
|
||||
'C'
|
||||
];
|
||||
$scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]);
|
||||
|
||||
$forum = $this->get_forum_instance([
|
||||
// Negative numbers mean a scale.
|
||||
'grade_forum' => -1 * $scale->id
|
||||
]);
|
||||
$course = $forum->get_course_record();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
$this->execute_and_assert_fetch($forum, $options, $scale, $student, $teacher, $student);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the fetch method with the given users and returns the result.
|
||||
*/
|
||||
private function execute_and_assert_fetch ($forum, $options, $scale, $fetcheruser, $grader, $gradeduser) {
|
||||
|
||||
$this->setUser($grader);
|
||||
|
||||
$gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
|
||||
$gradeitem->store_grade_from_formdata($student, $teacher, (object) [
|
||||
$gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [
|
||||
'grade' => 2,
|
||||
]);
|
||||
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
|
||||
$this->setUser($fetcheruser);
|
||||
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id);
|
||||
$result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
@@ -204,7 +265,7 @@ class fetch_test extends advanced_testcase {
|
||||
|
||||
$this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
|
||||
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
|
||||
$result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id);
|
||||
$result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
@@ -232,7 +293,7 @@ class fetch_test extends advanced_testcase {
|
||||
$this->assertEquals(3, $result['grade']['maxgrade']);
|
||||
|
||||
$this->assertArrayHasKey('gradedby', $result['grade']);
|
||||
$this->assertEquals(fullname($teacher), $result['grade']['gradedby']);
|
||||
$this->assertEquals(fullname($grader), $result['grade']['gradedby']);
|
||||
|
||||
$this->assertArrayHasKey('options', $result['grade']);
|
||||
$this->assertCount(count($options), $result['grade']['options']);
|
||||
|
||||
@@ -50,15 +50,12 @@ Feature: Organize students into groups
|
||||
And the "members" select box should not contain "Student 1 (student1@example.com)"
|
||||
And I navigate to course participants
|
||||
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
|
||||
And I click on "Group 1" "list_item"
|
||||
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group 1"
|
||||
And I click on "Apply filters" "button"
|
||||
And I should see "Student 0"
|
||||
And I should see "Student 1"
|
||||
And I should not see "Student 2"
|
||||
And I click on "Group 1" "autocomplete_selection"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
|
||||
And I click on "Group 2" "list_item"
|
||||
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group 2"
|
||||
And I click on "Apply filters" "button"
|
||||
And I should see "Student 2"
|
||||
And I should see "Student 3"
|
||||
|
||||
@@ -46,8 +46,7 @@ Feature: The description of a group can be viewed by students and teachers
|
||||
And I should see "Description for Group A"
|
||||
And ".groupinfobox" "css_element" should exist
|
||||
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
|
||||
And I click on "Group B" "list_item"
|
||||
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
|
||||
And I click on "Apply filters" "button"
|
||||
And I click on "Student 2" "link" in the "participants" "table"
|
||||
And I click on "Group B" "link"
|
||||
@@ -96,8 +95,7 @@ Feature: The description of a group can be viewed by students and teachers
|
||||
And I should see "Description for Group A"
|
||||
And ".groupinfobox" "css_element" should exist
|
||||
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
|
||||
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
|
||||
And I click on "Group B" "list_item"
|
||||
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
|
||||
And I click on "Apply filters" "button"
|
||||
And I click on "Student 2" "link" in the "participants" "table"
|
||||
And I click on "Group B" "link"
|
||||
|
||||
+64
-13
@@ -194,7 +194,10 @@ class api {
|
||||
global $DB;
|
||||
|
||||
// Deconstruct the URL and get the pathname associated.
|
||||
$pathnamehash = self::get_pluginfile_hash($url, $preventredirect);
|
||||
if (self::can_access_pluginfile_hash($url, $preventredirect)) {
|
||||
$pathnamehash = self::get_pluginfile_hash($url);
|
||||
}
|
||||
|
||||
if (!$pathnamehash) {
|
||||
return [false, false];
|
||||
}
|
||||
@@ -337,22 +340,28 @@ class api {
|
||||
* @param factory $factory The \core_h5p\factory object
|
||||
*/
|
||||
public static function delete_content_from_pluginfile_url(string $url, factory $factory): void {
|
||||
global $DB;
|
||||
|
||||
// Get the H5P to delete.
|
||||
list($file, $h5p) = self::get_content_from_pluginfile_url($url);
|
||||
$pathnamehash = self::get_pluginfile_hash($url);
|
||||
$h5p = $DB->get_record('h5p', ['pathnamehash' => $pathnamehash]);
|
||||
if ($h5p) {
|
||||
self::delete_content($h5p, $factory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pathnamehash from an H5P internal URL.
|
||||
* If user can access pathnamehash from an H5P internal URL.
|
||||
*
|
||||
* @param string $url H5P pluginfile URL poiting to an H5P file.
|
||||
* @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
|
||||
*
|
||||
* @return string|false pathnamehash for the file in the internal URL.
|
||||
* @return bool if user can access pluginfile hash.
|
||||
* @throws \moodle_exception
|
||||
* @throws \coding_exception
|
||||
* @throws \require_login_exception
|
||||
*/
|
||||
protected static function get_pluginfile_hash(string $url, bool $preventredirect = true) {
|
||||
protected static function can_access_pluginfile_hash(string $url, bool $preventredirect = true): bool {
|
||||
global $USER, $CFG;
|
||||
|
||||
// Decode the URL before start processing it.
|
||||
@@ -365,7 +374,6 @@ class api {
|
||||
// We only need the slasharguments.
|
||||
$path = substr($path, strpos($path, '.php/') + 5);
|
||||
$parts = explode('/', $path);
|
||||
$filename = array_pop($parts);
|
||||
|
||||
// If the request is made by tokenpluginfile.php we need to avoid userprivateaccesskey.
|
||||
if (strpos($url, '/tokenpluginfile.php')) {
|
||||
@@ -377,11 +385,6 @@ class api {
|
||||
$component = array_shift($parts);
|
||||
$filearea = array_shift($parts);
|
||||
|
||||
// Ignore draft files, because they are considered temporary files, so shouldn't be displayed.
|
||||
if ($filearea == 'draft') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the context.
|
||||
try {
|
||||
list($context, $course, $cm) = get_context_info_array($contextid);
|
||||
@@ -415,7 +418,7 @@ class api {
|
||||
$parentcontext = $context->get_parent_context();
|
||||
if ($parentcontext->contextlevel === CONTEXT_COURSECAT) {
|
||||
// Check if category is visible and user can view this category.
|
||||
if (!core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) {
|
||||
if (!\core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) {
|
||||
send_file_not_found();
|
||||
}
|
||||
} else if ($parentcontext->contextlevel === CONTEXT_USER && $parentcontext->instanceid != $USER->id) {
|
||||
@@ -431,7 +434,7 @@ class api {
|
||||
// For CONTEXT_MODULE and CONTEXT_COURSE check if the user is enrolled in the course.
|
||||
// And for CONTEXT_MODULE has permissions view this .h5p file.
|
||||
if ($context->contextlevel == CONTEXT_MODULE ||
|
||||
$context->contextlevel == CONTEXT_COURSE) {
|
||||
$context->contextlevel == CONTEXT_COURSE) {
|
||||
// Require login to the course first (without login to the module).
|
||||
require_course_login($course, true, null, !$preventredirect, $preventredirect);
|
||||
|
||||
@@ -448,6 +451,54 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pathnamehash from an H5P internal URL.
|
||||
*
|
||||
* @param string $url H5P pluginfile URL poiting to an H5P file.
|
||||
*
|
||||
* @return string|false pathnamehash for the file in the internal URL.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
protected static function get_pluginfile_hash(string $url) {
|
||||
|
||||
// Decode the URL before start processing it.
|
||||
$url = new \moodle_url(urldecode($url));
|
||||
|
||||
// Remove params from the URL (such as the 'forcedownload=1'), to avoid errors.
|
||||
$url->remove_params(array_keys($url->params()));
|
||||
$path = $url->out_as_local_url();
|
||||
|
||||
// We only need the slasharguments.
|
||||
$path = substr($path, strpos($path, '.php/') + 5);
|
||||
$parts = explode('/', $path);
|
||||
$filename = array_pop($parts);
|
||||
|
||||
// If the request is made by tokenpluginfile.php we need to avoid userprivateaccesskey.
|
||||
if (strpos($url, '/tokenpluginfile.php')) {
|
||||
array_shift($parts);
|
||||
}
|
||||
|
||||
// Get the contextid, component and filearea.
|
||||
$contextid = array_shift($parts);
|
||||
$component = array_shift($parts);
|
||||
$filearea = array_shift($parts);
|
||||
|
||||
// Ignore draft files, because they are considered temporary files, so shouldn't be displayed.
|
||||
if ($filearea == 'draft') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the context.
|
||||
try {
|
||||
list($context, $course, $cm) = get_context_info_array($contextid);
|
||||
} catch (\moodle_exception $e) {
|
||||
throw new \moodle_exception('invalidcontextid', 'core_h5p');
|
||||
}
|
||||
|
||||
// Some components, such as mod_page or mod_resource, add the revision to the URL to prevent caching problems.
|
||||
// So the URL contains this revision number as itemid but a 0 is always stored in the files table.
|
||||
// In order to get the proper hash, a callback should be done (looking for those exceptions).
|
||||
|
||||
@@ -127,7 +127,8 @@ class player {
|
||||
$url,
|
||||
$config,
|
||||
$this->factory,
|
||||
$this->messages
|
||||
$this->messages,
|
||||
$this->preventredirect
|
||||
);
|
||||
if ($file) {
|
||||
$this->context = \context::instance_by_id($file->get_contextid());
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['thislanguage'] = 'Аԥсуа бызшәа';
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatedboninstall'] = '<p>Kon nie die databasis skep nie.</p>
|
||||
<p>Die gespesifiseerde databasis bestaan nie en die aangetoonde gebruiker het nie toestemming om die databasis te skep nie.</p>
|
||||
<p>Die werf se administrateur moet die konfigurasie van databasisse verifieer.</p>';
|
||||
$string['cannotcreatelangdir'] = 'Kan nie taalgids skep nie';
|
||||
$string['cannotcreatetempdir'] = 'Kan nie tydelike gids skep nie';
|
||||
$string['cannotdownloadcomponents'] = 'Kan nie komponente aflaai nie';
|
||||
$string['cannotdownloadzipfile'] = 'Kan nie ZIP-lêer aflaai nie';
|
||||
$string['cannotfindcomponent'] = 'Kan nie komponent vind nie';
|
||||
$string['cannotsavemd5file'] = 'Kan nie md5-lêer stoor nie';
|
||||
$string['cannotsavezipfile'] = 'Kan nie ZIP-lêer stoor nie';
|
||||
$string['cannotunzipfile'] = 'Kan nie lêer uitpak nie';
|
||||
$string['componentisuptodate'] = 'Komponent is bygewerk';
|
||||
$string['dmlexceptiononinstall'] = '<p>\'n Databasisfout het opgeduik [{$a->errorcode}].<br />{$a->debuginfo}</p>';
|
||||
$string['downloadedfilecheckfailed'] = 'Kontrolering van afgelaaide lêer het misluk';
|
||||
$string['invalidmd5'] = 'Die kontroleveranderlike was verkeerd - probeer weer';
|
||||
$string['missingrequiredfield'] = 'Een of ander verlangde veld ontbreek';
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
// 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
|
||||
@@ -13,7 +12,7 @@
|
||||
// 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/>.
|
||||
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user