Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 598064e4d9 | |||
| a96a428c7d | |||
| 4e64550116 | |||
| f330e8146f | |||
| 5b0902eb06 | |||
| 9aa9e5f1e6 | |||
| c718c85046 | |||
| fe6dc7ff41 | |||
| 9a1d611089 | |||
| 393e43991f | |||
| e72e2edba5 | |||
| 783f714bbe | |||
| d986d964b3 | |||
| 7f1103b12d | |||
| ef1938e622 | |||
| 15fd9288ae | |||
| 26b09187b8 | |||
| f3e7a7f813 | |||
| 5a9347a0d3 | |||
| 738c06f7f2 | |||
| 25cfc3c9f2 | |||
| 5dd88c28d7 | |||
| 90426f3906 | |||
| 357073866e | |||
| 3e144d2a0b | |||
| 879dad536b | |||
| d80294e172 | |||
| 256926ba71 | |||
| 4423aca97f | |||
| a5a9583f4d | |||
| fd4c40c893 | |||
| 15ceddf963 | |||
| d1479874a9 | |||
| 44ef640fb0 | |||
| 04a448a2ab | |||
| b00782a63b | |||
| 45008c77e8 | |||
| 2612f3771d | |||
| 5f72b91962 | |||
| 565a4974e3 | |||
| bbe8f2b9a7 | |||
| 5724c6a382 | |||
| b07642e882 | |||
| b77d0239b9 | |||
| 654c27eece | |||
| f0f4003c9a | |||
| 6a5085ce04 | |||
| 2700ab11c7 | |||
| c9585b3ed9 | |||
| f4b63de27b | |||
| 9ab2003217 | |||
| 970ffe0417 | |||
| 7b24d50738 | |||
| d377bf6b78 | |||
| 1126438d1a | |||
| 6805a7ae33 | |||
| 50d41b6804 | |||
| 75a54d45b6 | |||
| 63e098d9e9 | |||
| f752683a47 | |||
| f14cb06317 |
+9
-2
@@ -190,6 +190,7 @@ before_script:
|
||||
# Enable test external resources
|
||||
sed -i \
|
||||
-e "/require_once/i \\define('TEST_EXTERNAL_FILES_HTTP_URL', 'http://127.0.0.1:8080');" \
|
||||
-e "/require_once/i \\define('TEST_EXTERNAL_FILES_HTTPS_URL', 'http://127.0.0.1:8080');" \
|
||||
config.php ;
|
||||
|
||||
# Initialise PHPUnit for Moodle.
|
||||
@@ -261,8 +262,6 @@ script:
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
then
|
||||
vendor/bin/phpunit --fail-on-risky --disallow-test-output --verbose;
|
||||
EXTTESTS_HITS=$(docker logs exttests 2>&1 | grep -Fv -e 'AH00558' -e '[pid 1]' | wc -l)
|
||||
echo -e "\nTest local resources number of hits: ${EXTTESTS_HITS}.\n"
|
||||
fi
|
||||
|
||||
- >
|
||||
@@ -299,3 +298,11 @@ script:
|
||||
exit 1 ;
|
||||
fi
|
||||
fi
|
||||
|
||||
after_script:
|
||||
- >
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
then
|
||||
EXTTESTS_HITS=$(docker logs exttests 2>&1 | grep -Fv -e 'AH00558' -e '[pid 1]' | wc -l)
|
||||
echo -e "\nTest local resources number of hits: ${EXTTESTS_HITS}.\n"
|
||||
fi
|
||||
|
||||
@@ -3163,4 +3163,185 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.10" requires="3.5">
|
||||
<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="11.0" />
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.2.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_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>
|
||||
|
||||
@@ -100,7 +100,7 @@ $options = array(
|
||||
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'REMOTE_ADDR');
|
||||
$temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'),
|
||||
new lang_string('configgetremoteaddrconf', 'admin'),
|
||||
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP, $options));
|
||||
GETREMOTEADDR_SKIP_DEFAULT, $options));
|
||||
$temp->add(new admin_setting_configtext('reverseproxyignore', new lang_string('reverseproxyignore', 'admin'), new lang_string('configreverseproxyignore', 'admin'), ''));
|
||||
|
||||
$temp->add(new admin_setting_heading('webproxy', new lang_string('webproxy', 'admin'), new lang_string('webproxyinfo', 'admin')));
|
||||
|
||||
@@ -49,6 +49,6 @@ class behat_auth_email extends behat_base {
|
||||
$confirmationpath = $confirmationurl->out_as_local_url(false);
|
||||
$url = $confirmationpath . '?' . 'data='. $secret .'/'. $username;
|
||||
|
||||
$this->getSession()->visit($this->locate_path($url));
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,16 +79,19 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
|
||||
$o['ou'] = 'users';
|
||||
ldap_add($connection, 'ou='.$o['ou'].','.$topdn, $o);
|
||||
|
||||
$createdusers = array();
|
||||
for ($i=1; $i<=5; $i++) {
|
||||
$this->create_ldap_user($connection, $topdn, $i);
|
||||
$createdusers[] = 'username' . $i;
|
||||
}
|
||||
|
||||
// Set up creators group.
|
||||
$assignedroles = array('username1', 'username2');
|
||||
$o = array();
|
||||
$o['objectClass'] = array('posixGroup');
|
||||
$o['cn'] = 'creators';
|
||||
$o['gidNumber'] = 1;
|
||||
$o['memberUid'] = array('username1', 'username2');
|
||||
$o['memberUid'] = $assignedroles;
|
||||
ldap_add($connection, 'cn='.$o['cn'].','.$topdn, $o);
|
||||
|
||||
$creatorrole = $DB->get_record('role', array('shortname'=>'coursecreator'));
|
||||
@@ -145,15 +148,23 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
|
||||
// Check events, 5 users created with 2 users having roles.
|
||||
$this->assertCount(7, $events);
|
||||
foreach ($events as $index => $event) {
|
||||
$usercreatedindex = array(0, 2, 4, 5, 6);
|
||||
$roleassignedindex = array (1, 3);
|
||||
if (in_array($index, $usercreatedindex)) {
|
||||
$this->assertInstanceOf('\core\event\user_created', $event);
|
||||
}
|
||||
if (in_array($index, $roleassignedindex)) {
|
||||
$this->assertInstanceOf('\core\event\role_assigned', $event);
|
||||
$username = $DB->get_field('user', 'username', array('id' => $event->relateduserid)); // Get username.
|
||||
|
||||
if ($event->eventname === '\core\event\user_created') {
|
||||
$this->assertContains($username, $createdusers);
|
||||
unset($events[$index]); // Remove matching event.
|
||||
|
||||
} else if ($event->eventname === '\core\event\role_assigned') {
|
||||
$this->assertContains($username, $assignedroles);
|
||||
unset($events[$index]); // Remove matching event.
|
||||
|
||||
} else {
|
||||
$this->fail('Unexpected event found: ' . $event->eventname);
|
||||
}
|
||||
}
|
||||
// If all the user_created and role_assigned events have matched
|
||||
// then the $events array should be now empty.
|
||||
$this->assertCount(0, $events);
|
||||
|
||||
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
|
||||
$this->assertEquals(2, $DB->count_records('role_assignments'));
|
||||
|
||||
@@ -53,7 +53,7 @@ class behat_auth extends behat_base {
|
||||
}
|
||||
|
||||
// Visit login page.
|
||||
$this->getSession()->visit($this->locate_path($loginurl->out_as_local_url()));
|
||||
$this->execute('behat_general::i_visit', [$loginurl]);
|
||||
|
||||
// Enter username and password.
|
||||
$this->execute('behat_forms::i_set_the_field_to', array('Username', $this->escape($username)));
|
||||
|
||||
@@ -56,7 +56,7 @@ class behat_backup extends behat_base {
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Go to homepage.
|
||||
$this->getSession()->visit($this->locate_path('/?redirect=0'));
|
||||
$this->execute('behat_general::i_visit', ['/?redirect=0']);
|
||||
$this->execute("behat_general::wait_until_the_page_is_ready");
|
||||
|
||||
// Click the course link.
|
||||
@@ -98,7 +98,7 @@ class behat_backup extends behat_base {
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Go to homepage.
|
||||
$this->getSession()->visit($this->locate_path('/?redirect=0'));
|
||||
$this->execute('behat_general::i_visit', ['/?redirect=0']);
|
||||
|
||||
// Click the course link.
|
||||
$this->execute("behat_general::click_link", $backupcourse);
|
||||
@@ -134,7 +134,7 @@ class behat_backup extends behat_base {
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Go to homepage.
|
||||
$this->getSession()->visit($this->locate_path('/?redirect=0'));
|
||||
$this->execute('behat_general::i_visit', ['/?redirect=0']);
|
||||
$this->execute("behat_general::wait_until_the_page_is_ready");
|
||||
|
||||
// Click the course link.
|
||||
|
||||
@@ -9,9 +9,14 @@ Feature: Random glossary entry block linking to global glossary
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
| Course 2 | C2 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | globalglossary | defaultapproval |
|
||||
| glossary | Tips and Tricks | Frontpage glossary description | C2 | glossary0 | 1 | 1 |
|
||||
And the following "activity" exists:
|
||||
| activity | glossary |
|
||||
| name | Tips and Tricks |
|
||||
| intro | Frontpage glossary description |
|
||||
| course | C2 |
|
||||
| idnumber | glossary0 |
|
||||
| globalglossary | 1 |
|
||||
| defaultapproval | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Sam1 | Student1 | student1@example.com |
|
||||
|
||||
@@ -1270,6 +1270,12 @@ class calendar_information {
|
||||
* @param string|null $view preference view options (eg: day, month, upcoming)
|
||||
*/
|
||||
public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
|
||||
global $PAGE;
|
||||
|
||||
if (!has_capability('moodle/block:view', $PAGE->context) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($showfilters) {
|
||||
$filters = new block_contents();
|
||||
$filters->content = $renderer->event_filter();
|
||||
|
||||
@@ -121,7 +121,7 @@ class behat_calendar extends behat_base {
|
||||
*/
|
||||
public function i_view_the_calendar_for($month, $year) {
|
||||
$time = make_timestamp($year, $month, 1);
|
||||
$this->getSession()->visit($this->locate_path('/calendar/view.php?view=month&course=1&time='.$time));
|
||||
$this->execute('behat_general::i_visit', ['/calendar/view.php?view=month&course=1&time='.$time]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* 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['clianswerno'] = 'n';
|
||||
$string['cliansweryes'] = 'j';
|
||||
$string['cliincorrectvalueretry'] = 'Falscha Wert, probiers nummal';
|
||||
$string['cliyesnoprompt'] = 'druck j (bedeit ja) oda n (bedeit na)';
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* 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['parentlanguage'] = 'de';
|
||||
$string['thislanguage'] = 'Bairisch';
|
||||
@@ -41,5 +41,5 @@ $string['cliunknowoption'] = 'Aukera ezezagunak:
|
||||
Mesedez, erabili --help aukera.';
|
||||
$string['cliyesnoprompt'] = 'idatzi b (bai esateko) edo e (ez esateko)';
|
||||
$string['environmentrequireinstall'] = 'derrigorrezkoa da instalatuta eta gaituta izatea';
|
||||
$string['environmentrequireversion'] = '{$a->needed} bertsioa beharrezkoa da eta zu {$a->current} ari zara egikaritzen';
|
||||
$string['environmentrequireversion'] = '{$a->needed} bertsioa beharrezkoa da eta zu {$a->current} ari zara exekutatzen';
|
||||
$string['upgradekeyset'] = 'Eguneraketa-kodea (utzi hutsik kodea erabili nahi ez baduzu)';
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'भाषा';
|
||||
$string['moodlelogo'] = 'Moodle लोगो';
|
||||
$string['next'] = 'अगला';
|
||||
$string['reload'] = 'सीमा से अधिक लादना';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* 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['parentlanguage'] = 'hi';
|
||||
$string['thislanguage'] = 'हिंदी';
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* 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'] = 'Qaraqalpaq tili';
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* 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['language'] = 'زبان';
|
||||
$string['moodlelogo'] = 'لوگوی مودل (Moodle)';
|
||||
$string['next'] = 'بعدی';
|
||||
$string['previous'] = 'قبلی';
|
||||
$string['reload'] = 'بارگیری مجدد';
|
||||
@@ -56,6 +56,8 @@ $string['cannotdelete'] = 'Cannot delete this file.';
|
||||
$string['cannotdownload'] = 'Cannot download this file';
|
||||
$string['cannotdownloaddir'] = 'Cannot download this folder';
|
||||
$string['cannotinitplugin'] = 'Call plugin_init failed';
|
||||
$string['cannotunzipcontentunreadable'] = 'Cannot unzip this file because the contents of the file cannot be read.';
|
||||
$string['cannotunzipquotaexceeded'] = 'Cannot unzip this file because the maximum size allowed in this draft area will be exceeded.';
|
||||
$string['cleancache'] = 'Clean my cache files';
|
||||
$string['close'] = 'Close';
|
||||
$string['commonrepositorysettings'] = 'Common repository settings';
|
||||
|
||||
@@ -487,6 +487,16 @@ function has_capability($capability, context $context, $user = null, $doanything
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($USER->loginascontext)) {
|
||||
// The current user is logged in as another user and can assume their identity at or below the `loginascontext`
|
||||
// defined in the USER session.
|
||||
// The user may not assume their identity at any other location.
|
||||
if (!$USER->loginascontext->is_parent_of($context, true)) {
|
||||
// The context being checked is not the specified context, or one of its children.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find out if user is admin - it is not possible to override the doanything in any way
|
||||
// and it is not possible to switch to admin role either.
|
||||
if ($doanything) {
|
||||
@@ -5360,6 +5370,30 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current context is a parent of the possible child.
|
||||
*
|
||||
* @param context $possiblechild
|
||||
* @param bool $includeself Whether to check the current context
|
||||
* @return bool
|
||||
*/
|
||||
public function is_parent_of(context $possiblechild, bool $includeself): bool {
|
||||
// A simple substring check is used on the context path.
|
||||
// The possible child's path is used as a haystack, with the current context as the needle.
|
||||
// The path is prefixed with '+' to ensure that the parent always starts at the top.
|
||||
// It is suffixed with '+' to ensure that parents are not included.
|
||||
// The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
|
||||
// The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
|
||||
// The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
|
||||
$haystacksuffix = $includeself ? '/+' : '+';
|
||||
|
||||
$strpos = strpos(
|
||||
"+{$possiblechild->path}{$haystacksuffix}",
|
||||
"+{$this->path}/"
|
||||
);
|
||||
return $strpos === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent contexts of this context in reversed order, i.e. parent first,
|
||||
* then grand parent, etc.
|
||||
@@ -5384,6 +5418,30 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current context is a child of the possible parent.
|
||||
*
|
||||
* @param context $possibleparent
|
||||
* @param bool $includeself Whether to check the current context
|
||||
* @return bool
|
||||
*/
|
||||
public function is_child_of(context $possibleparent, bool $includeself): bool {
|
||||
// A simple substring check is used on the context path.
|
||||
// The current context is used as a haystack, with the possible parent as the needle.
|
||||
// The path is prefixed with '+' to ensure that the parent always starts at the top.
|
||||
// It is suffixed with '+' to ensure that children are not included.
|
||||
// The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
|
||||
// The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
|
||||
// The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
|
||||
$haystacksuffix = $includeself ? '/+' : '+';
|
||||
|
||||
$strpos = strpos(
|
||||
"+{$this->path}{$haystacksuffix}",
|
||||
"+{$possibleparent->path}/"
|
||||
);
|
||||
return $strpos === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent contexts of this context in reversed order, i.e. parent first,
|
||||
* then grand parent, etc.
|
||||
|
||||
+128
-25
@@ -79,8 +79,12 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
|
||||
/**
|
||||
* The JS code to check that the page is ready.
|
||||
*
|
||||
* The document must be complete and either M.util.pending_js must be empty, or it must not be defined at all.
|
||||
*/
|
||||
const PAGE_READY_JS = '(typeof M !== "undefined" && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")';
|
||||
const PAGE_READY_JS = "document.readyState === 'complete' && " .
|
||||
"(typeof M !== 'object' || typeof M.util !== 'object' || " .
|
||||
"typeof M.util.pending_js === 'undefined' || M.util.pending_js.length === 0)";
|
||||
|
||||
/**
|
||||
* Locates url, based on provided path.
|
||||
@@ -468,12 +472,45 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the scenario is running in a browser that can run Javascript or not.
|
||||
* Whether Javascript is available in the current Session.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function running_javascript() {
|
||||
return get_class($this->getSession()->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver';
|
||||
return self::running_javascript_in_session($this->getSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* Require that javascript be available in the current Session.
|
||||
*
|
||||
* @throws DriverException
|
||||
*/
|
||||
protected function require_javascript() {
|
||||
return self::require_javascript_in_session($this->getSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether Javascript is available in the specified Session.
|
||||
*
|
||||
* @param Session $session
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function running_javascript_in_session(Session $session): bool {
|
||||
return get_class($session->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver';
|
||||
}
|
||||
|
||||
/**
|
||||
* Require that javascript be available for the specified Session.
|
||||
*
|
||||
* @param Session $session
|
||||
* @throws DriverException
|
||||
*/
|
||||
protected static function require_javascript_in_session(Session $session) {
|
||||
if (self::running_javascript_in_session($session)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new DriverException('Javascript is required');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -697,14 +734,18 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
// The window inner height will be as specified, which means the available viewport will
|
||||
// actually be smaller if there is a horizontal scrollbar. We assume that horizontal
|
||||
// scrollbars are rare so this doesn't matter.
|
||||
$offset = $this->getSession()->getDriver()->evaluateScript(
|
||||
'return (function() { var before = document.body.style.overflowY;' .
|
||||
'document.body.style.overflowY = "scroll";' .
|
||||
'var result = {};' .
|
||||
'result.x = window.outerWidth - document.body.offsetWidth;' .
|
||||
'result.y = window.outerHeight - window.innerHeight;' .
|
||||
'document.body.style.overflowY = before;' .
|
||||
'return result; })();');
|
||||
$js = <<<EOF
|
||||
return (function() {
|
||||
var before = document.body.style.overflowY;
|
||||
document.body.style.overflowY = "scroll";
|
||||
var result = {};
|
||||
result.x = window.outerWidth - document.body.offsetWidth;
|
||||
result.y = window.outerHeight - window.innerHeight;
|
||||
document.body.style.overflowY = before;
|
||||
return result;
|
||||
})();
|
||||
EOF;
|
||||
$offset = $this->evaluate_script($js);
|
||||
$width += $offset['x'];
|
||||
$height += $offset['y'];
|
||||
}
|
||||
@@ -740,21 +781,17 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
try {
|
||||
$jscode = trim(preg_replace('/\s+/', ' ', '
|
||||
return (function() {
|
||||
if (typeof M === "undefined") {
|
||||
if (document.readyState === "complete") {
|
||||
return "";
|
||||
} else {
|
||||
return "incomplete";
|
||||
}
|
||||
} else if (' . self::PAGE_READY_JS . ') {
|
||||
return "";
|
||||
} else if (typeof M.util !== "undefined") {
|
||||
return M.util.pending_js.join(":");
|
||||
} else {
|
||||
return "incomplete"
|
||||
if (document.readyState !== "complete") {
|
||||
return "incomplete";
|
||||
}
|
||||
}());'));
|
||||
$pending = $session->evaluateScript($jscode);
|
||||
|
||||
if (typeof M !== "object" || typeof M.util !== "object" || typeof M.util.pending_js === "undefined") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return M.util.pending_js.join(":");
|
||||
})()'));
|
||||
$pending = self::evaluate_script_in_session($session, $jscode);
|
||||
} catch (NoSuchWindow $nsw) {
|
||||
// We catch an exception here, in case we just closed the window we were interacting with.
|
||||
// No javascript is running if there is no window right?
|
||||
@@ -1137,4 +1174,70 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
public static function get_named_replacements(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the supplied script in the current session, returning the result.
|
||||
*
|
||||
* @param string $script
|
||||
* @return mixed
|
||||
*/
|
||||
public function evaluate_script(string $script) {
|
||||
return self::evaluate_script_in_session($this->getSession(), $script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the supplied script in the specified session, returning the result.
|
||||
*
|
||||
* @param Session $session
|
||||
* @param string $script
|
||||
* @return mixed
|
||||
*/
|
||||
public static function evaluate_script_in_session(Session $session, string $script) {
|
||||
self::require_javascript_in_session($session);
|
||||
|
||||
return $session->evaluateScript($script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the supplied script in the current session.
|
||||
*
|
||||
* No result will be returned.
|
||||
*
|
||||
* @param string $script
|
||||
*/
|
||||
public function execute_script(string $script) {
|
||||
self::execute_script_in_session($this->getSession(), $script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excecute the supplied script in the specified session.
|
||||
*
|
||||
* No result will be returned.
|
||||
*
|
||||
* @param Session $session
|
||||
* @param string $script
|
||||
*/
|
||||
public static function execute_script_in_session(Session $session, string $script) {
|
||||
self::require_javascript_in_session($session);
|
||||
|
||||
$session->executeScript($script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the session key for the current session via Javascript.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_sesskey(): string {
|
||||
$script = <<<EOF
|
||||
return (function() {
|
||||
if (M && M.cfg && M.cfg.sesskey) {
|
||||
return M.cfg.sesskey;
|
||||
}
|
||||
return '';
|
||||
})()
|
||||
EOF;
|
||||
|
||||
return $this->evaluate_script($script);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,89 +39,107 @@ defined('MOODLE_INTERNAL') || die();
|
||||
class behat_core_generator extends behat_generator_base {
|
||||
|
||||
protected function get_creatable_entities(): array {
|
||||
return [
|
||||
$entities = [
|
||||
'users' => [
|
||||
'singular' => 'user',
|
||||
'datagenerator' => 'user',
|
||||
'required' => ['username'],
|
||||
],
|
||||
'categories' => [
|
||||
'singular' => 'category',
|
||||
'datagenerator' => 'category',
|
||||
'required' => ['idnumber'],
|
||||
'switchids' => ['category' => 'parent'],
|
||||
],
|
||||
'courses' => [
|
||||
'singular' => 'course',
|
||||
'datagenerator' => 'course',
|
||||
'required' => ['shortname'],
|
||||
'switchids' => ['category' => 'category'],
|
||||
],
|
||||
'groups' => [
|
||||
'singular' => 'group',
|
||||
'datagenerator' => 'group',
|
||||
'required' => ['idnumber', 'course'],
|
||||
'switchids' => ['course' => 'courseid'],
|
||||
],
|
||||
'groupings' => [
|
||||
'singular' => 'grouping',
|
||||
'datagenerator' => 'grouping',
|
||||
'required' => ['idnumber', 'course'],
|
||||
'switchids' => ['course' => 'courseid'],
|
||||
],
|
||||
'course enrolments' => [
|
||||
'singular' => 'course enrolment',
|
||||
'datagenerator' => 'enrol_user',
|
||||
'required' => ['user', 'course', 'role'],
|
||||
'switchids' => ['user' => 'userid', 'course' => 'courseid', 'role' => 'roleid'],
|
||||
],
|
||||
'permission overrides' => [
|
||||
'singular' => 'permission override',
|
||||
'datagenerator' => 'permission_override',
|
||||
'required' => ['capability', 'permission', 'role', 'contextlevel', 'reference'],
|
||||
'switchids' => ['role' => 'roleid'],
|
||||
],
|
||||
'system role assigns' => [
|
||||
'singular' => 'system role assignment',
|
||||
'datagenerator' => 'system_role_assign',
|
||||
'required' => ['user', 'role'],
|
||||
'switchids' => ['user' => 'userid', 'role' => 'roleid'],
|
||||
],
|
||||
'role assigns' => [
|
||||
'singular' => 'role assignment',
|
||||
'datagenerator' => 'role_assign',
|
||||
'required' => ['user', 'role', 'contextlevel', 'reference'],
|
||||
'switchids' => ['user' => 'userid', 'role' => 'roleid'],
|
||||
],
|
||||
'activities' => [
|
||||
'singular' => 'activity',
|
||||
'datagenerator' => 'activity',
|
||||
'required' => ['activity', 'idnumber', 'course'],
|
||||
'switchids' => ['course' => 'course', 'gradecategory' => 'gradecat', 'grouping' => 'groupingid'],
|
||||
],
|
||||
'blocks' => [
|
||||
'singular' => 'block',
|
||||
'datagenerator' => 'block_instance',
|
||||
'required' => ['blockname', 'contextlevel', 'reference'],
|
||||
],
|
||||
'group members' => [
|
||||
'singular' => 'group member',
|
||||
'datagenerator' => 'group_member',
|
||||
'required' => ['user', 'group'],
|
||||
'switchids' => ['user' => 'userid', 'group' => 'groupid'],
|
||||
],
|
||||
'grouping groups' => [
|
||||
'singular' => 'grouping group',
|
||||
'datagenerator' => 'grouping_group',
|
||||
'required' => ['grouping', 'group'],
|
||||
'switchids' => ['grouping' => 'groupingid', 'group' => 'groupid'],
|
||||
],
|
||||
'cohorts' => [
|
||||
'singular' => 'cohort',
|
||||
'datagenerator' => 'cohort',
|
||||
'required' => ['idnumber'],
|
||||
],
|
||||
'cohort members' => [
|
||||
'singular' => 'cohort member',
|
||||
'datagenerator' => 'cohort_member',
|
||||
'required' => ['user', 'cohort'],
|
||||
'switchids' => ['user' => 'userid', 'cohort' => 'cohortid'],
|
||||
],
|
||||
'roles' => [
|
||||
'singular' => 'role',
|
||||
'datagenerator' => 'role',
|
||||
'required' => ['shortname'],
|
||||
],
|
||||
'grade categories' => [
|
||||
'singular' => 'grade category',
|
||||
'datagenerator' => 'grade_category',
|
||||
'required' => ['fullname', 'course'],
|
||||
'switchids' => ['course' => 'courseid', 'gradecategory' => 'parent'],
|
||||
],
|
||||
'grade items' => [
|
||||
'singular' => 'grade item',
|
||||
'datagenerator' => 'grade_item',
|
||||
'required' => ['course'],
|
||||
'switchids' => [
|
||||
@@ -132,30 +150,36 @@ class behat_core_generator extends behat_generator_base {
|
||||
],
|
||||
],
|
||||
'grade outcomes' => [
|
||||
'singular' => 'grade outcome',
|
||||
'datagenerator' => 'grade_outcome',
|
||||
'required' => ['shortname', 'scale'],
|
||||
'switchids' => ['course' => 'courseid', 'gradecategory' => 'categoryid', 'scale' => 'scaleid'],
|
||||
],
|
||||
'scales' => [
|
||||
'singular' => 'scale',
|
||||
'datagenerator' => 'scale',
|
||||
'required' => ['name', 'scale'],
|
||||
'switchids' => ['course' => 'courseid'],
|
||||
],
|
||||
'question categories' => [
|
||||
'singular' => 'question category',
|
||||
'datagenerator' => 'question_category',
|
||||
'required' => ['name', 'contextlevel', 'reference'],
|
||||
'switchids' => ['questioncategory' => 'parent'],
|
||||
],
|
||||
'questions' => [
|
||||
'singular' => 'question',
|
||||
'datagenerator' => 'question',
|
||||
'required' => ['qtype', 'questioncategory', 'name'],
|
||||
'switchids' => ['questioncategory' => 'category', 'user' => 'createdby'],
|
||||
],
|
||||
'tags' => [
|
||||
'singular' => 'tag',
|
||||
'datagenerator' => 'tag',
|
||||
'required' => ['name'],
|
||||
],
|
||||
'events' => [
|
||||
'singular' => 'event',
|
||||
'datagenerator' => 'event',
|
||||
'required' => ['name', 'eventtype'],
|
||||
'switchids' => [
|
||||
@@ -165,6 +189,8 @@ class behat_core_generator extends behat_generator_base {
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -171,13 +171,23 @@ abstract class behat_generator_base {
|
||||
*
|
||||
* @param string $generatortype The name of the entity to create.
|
||||
* @param TableNode $data from the step.
|
||||
* @param bool $singular Whether there is only one record and it is pivotted
|
||||
*/
|
||||
public function generate_items(string $generatortype, TableNode $data) {
|
||||
public function generate_items(string $generatortype, TableNode $data, bool $singular = false) {
|
||||
// Now that we need them require the data generators.
|
||||
require_once(__DIR__ . '/../../testing/generator/lib.php');
|
||||
|
||||
$elements = $this->get_creatable_entities();
|
||||
|
||||
foreach ($elements as $key => $configuration) {
|
||||
if (array_key_exists('singular', $configuration)) {
|
||||
$singularverb = $configuration['singular'];
|
||||
unset($configuration['singular']);
|
||||
unset($elements[$key]['singular']);
|
||||
$elements[$singularverb] = $configuration;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($elements[$generatortype])) {
|
||||
throw new PendingException($this->name_for_errors($generatortype) .
|
||||
' is not a known type of entity that can be generated.');
|
||||
@@ -193,8 +203,17 @@ abstract class behat_generator_base {
|
||||
|
||||
$generatortype = $entityinfo['datagenerator'];
|
||||
|
||||
foreach ($data->getHash() as $elementdata) {
|
||||
if ($singular) {
|
||||
// There is only one record to generate, and the table has been pivotted.
|
||||
// The rows each represent a single field.
|
||||
$rows = [$data->getRowsHash()];
|
||||
} else {
|
||||
// There are multiple records to generate.
|
||||
// The rows represent an item to create.
|
||||
$rows = $data->getHash();
|
||||
}
|
||||
|
||||
foreach ($rows as $elementdata) {
|
||||
// Check if all the required fields are there.
|
||||
foreach ($entityinfo['required'] as $requiredfield) {
|
||||
if (!isset($elementdata[$requiredfield])) {
|
||||
|
||||
@@ -52,14 +52,16 @@ class behat_form_editor extends behat_form_textarea {
|
||||
if ($this->running_javascript()) {
|
||||
$value = addslashes($value);
|
||||
$js = '
|
||||
var editor = Y.one(document.getElementById("'.$editorid.'editable"));
|
||||
if (editor) {
|
||||
editor.setHTML("' . $value . '");
|
||||
}
|
||||
editor = Y.one(document.getElementById("'.$editorid.'"));
|
||||
editor.set("value", "' . $value . '");
|
||||
(function() {
|
||||
var editor = Y.one(document.getElementById("'.$editorid.'editable"));
|
||||
if (editor) {
|
||||
editor.setHTML("' . $value . '");
|
||||
}
|
||||
editor = Y.one(document.getElementById("'.$editorid.'"));
|
||||
editor.set("value", "' . $value . '");
|
||||
})();
|
||||
';
|
||||
$this->session->executeScript($js);
|
||||
behat_base::execute_script_in_session($this->session, $js);
|
||||
} else {
|
||||
parent::set_value($value);
|
||||
}
|
||||
@@ -88,7 +90,7 @@ editor.set("value", "' . $value . '");
|
||||
r.selectNodeContents(e);
|
||||
s.setSingleRange(r);
|
||||
}()); ';
|
||||
$this->session->executeScript($js);
|
||||
behat_base::execute_script_in_session($this->session, $js);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,12 +51,14 @@ class behat_form_passwordunmask extends behat_form_text {
|
||||
if ($this->running_javascript()) {
|
||||
$id = $this->field->getAttribute('id');
|
||||
$js = <<<JS
|
||||
require(["jquery"], function($) {
|
||||
var wrapper = $(document.getElementById("{$id}")).closest('[data-passwordunmask="wrapper"]');
|
||||
wrapper.find('[data-passwordunmask="edit"]').trigger("click");
|
||||
});
|
||||
(function() {
|
||||
require(["jquery"], function($) {
|
||||
var wrapper = $(document.getElementById("{$id}")).closest('[data-passwordunmask="wrapper"]');
|
||||
wrapper.find('[data-passwordunmask="edit"]').trigger("click");
|
||||
});
|
||||
})();
|
||||
JS;
|
||||
$this->session->executeScript($js);
|
||||
behat_base::execute_script_in_session($this->session, $js);
|
||||
}
|
||||
|
||||
$this->field->setValue($value);
|
||||
|
||||
@@ -1260,6 +1260,12 @@ class block_manager {
|
||||
*/
|
||||
public function ensure_content_created($region, $output) {
|
||||
$this->ensure_instances_exist($region);
|
||||
|
||||
if (!has_capability('moodle/block:view', $this->page->context) ) {
|
||||
$this->visibleblockcontent[$region] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!array_key_exists($region, $this->visibleblockcontent)) {
|
||||
$contents = array();
|
||||
if (array_key_exists($region, $this->extracontent)) {
|
||||
|
||||
@@ -496,6 +496,27 @@ class stored_file {
|
||||
return $this->filesystem->list_files($this, $packer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total size (in bytes) of the contents of an archive.
|
||||
*
|
||||
* @param file_packer $packer file packer instance
|
||||
* @return int|null total size in bytes
|
||||
*/
|
||||
public function get_total_content_size(file_packer $packer) {
|
||||
// Fetch the contents of the archive.
|
||||
$files = $this->list_files($packer);
|
||||
|
||||
// Early return if the value of $files is not of type array.
|
||||
// This can happen when the utility class is unable to open or read the contents of the archive.
|
||||
if (!is_array($files)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_reduce($files, function ($contentsize, $file) {
|
||||
return $contentsize + $file->size;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract file to given file path (real OS filesystem), existing files are overwritten.
|
||||
*
|
||||
|
||||
@@ -884,14 +884,21 @@ M.form_filemanager.init = function(Y, options) {
|
||||
}
|
||||
params['filepath'] = fileinfo.filepath;
|
||||
params['filename'] = fileinfo.fullname;
|
||||
// The unlimited value of areamaxbytes is -1, it is defined by FILE_AREA_MAX_BYTES_UNLIMITED.
|
||||
params['areamaxbytes'] = this.areamaxbytes ? this.areamaxbytes : -1;
|
||||
selectnode.addClass('loading');
|
||||
this.request({
|
||||
action: 'unzip',
|
||||
scope: this,
|
||||
params: params,
|
||||
callback: function(id, obj, args) {
|
||||
args.scope.selectui.hide();
|
||||
args.scope.refresh(obj.filepath);
|
||||
if (obj.error) {
|
||||
selectnode.removeClass('loading');
|
||||
args.scope.print_msg(obj.error, 'error');
|
||||
} else {
|
||||
args.scope.selectui.hide();
|
||||
args.scope.refresh(obj.filepath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, this);
|
||||
|
||||
+5
-1
@@ -362,6 +362,10 @@ define('PAGE_COURSE_VIEW', 'course-view');
|
||||
define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
|
||||
/** Get remote addr constant */
|
||||
define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
|
||||
/**
|
||||
* GETREMOTEADDR_SKIP_DEFAULT defines the default behavior remote IP address validation.
|
||||
*/
|
||||
define('GETREMOTEADDR_SKIP_DEFAULT', GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP);
|
||||
|
||||
// Blog access level constant declaration.
|
||||
define ('BLOG_USER_LEVEL', 1);
|
||||
@@ -9014,7 +9018,7 @@ function getremoteaddr($default='0.0.0.0') {
|
||||
if (empty($CFG->getremoteaddrconf)) {
|
||||
// This will happen, for example, before just after the upgrade, as the
|
||||
// user is redirected to the admin screen.
|
||||
$variablestoskip = 0;
|
||||
$variablestoskip = GETREMOTEADDR_SKIP_DEFAULT;
|
||||
} else {
|
||||
$variablestoskip = $CFG->getremoteaddrconf;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* A collection of tests for accesslib::has_capability().
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Unit tests tests for has_capability.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class accesslib_has_capability_testcase extends \advanced_testcase {
|
||||
/**
|
||||
* Data provider for for has_capability tests when logged in as a different user.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function login_as_provider(): array {
|
||||
return [
|
||||
[
|
||||
'system',
|
||||
[
|
||||
'cat1course1block' => true,
|
||||
'cat1course1' => true,
|
||||
'cat1course2block' => true,
|
||||
'cat1course2' => true,
|
||||
'cat2course1block' => true,
|
||||
'cat2course1' => true,
|
||||
'cat2course2block' => true,
|
||||
'cat2course2' => true,
|
||||
],
|
||||
],
|
||||
[
|
||||
'cat1',
|
||||
[
|
||||
'cat1course1block' => true,
|
||||
'cat1course1' => true,
|
||||
'cat1course2block' => true,
|
||||
'cat1course2' => true,
|
||||
|
||||
'cat2course1block' => false,
|
||||
'cat2course1' => false,
|
||||
'cat2course2block' => false,
|
||||
'cat2course2' => false,
|
||||
],
|
||||
],
|
||||
[
|
||||
'cat1course1',
|
||||
[
|
||||
'cat1course1block' => true,
|
||||
'cat1course1' => true,
|
||||
|
||||
'cat1course2block' => false,
|
||||
'cat1course2' => false,
|
||||
'cat2course1block' => false,
|
||||
'cat2course1' => false,
|
||||
'cat2course2block' => false,
|
||||
'cat2course2' => false,
|
||||
],
|
||||
],
|
||||
[
|
||||
'cat1course1block',
|
||||
[
|
||||
'cat1course1block' => true,
|
||||
|
||||
'cat1course1' => false,
|
||||
'cat1course2block' => false,
|
||||
'cat1course2' => false,
|
||||
'cat2course1block' => false,
|
||||
'cat2course1' => false,
|
||||
'cat2course2block' => false,
|
||||
'cat2course2' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the log in as functionality works as expected for an administrator.
|
||||
*
|
||||
* An administrator logged in as another user assumes all of their capabilities.
|
||||
*
|
||||
* @dataProvider login_as_provider
|
||||
* @param string $loginascontext
|
||||
* @param string $testcontexts
|
||||
*/
|
||||
public function test_login_as_admin(string $loginascontext, array $testcontexts) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$contexts = $this->get_test_data();
|
||||
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
$testcontext = $contexts->$loginascontext;
|
||||
\core\session\manager::loginas($user->id, $testcontext);
|
||||
|
||||
$capability = 'moodle/block:view';
|
||||
foreach ($testcontexts as $contextname => $hascapability) {
|
||||
$this->assertEquals($hascapability, has_capability($capability, $contexts->$contextname));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the log in as functionality works as expected for a regulr user.
|
||||
*
|
||||
* @dataProvider login_as_provider
|
||||
* @param string $loginascontext
|
||||
* @param string $testcontexts
|
||||
*/
|
||||
public function test_login_as_user(string $loginascontext, array $testcontexts) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$contexts = $this->get_test_data();
|
||||
|
||||
$initialuser = $this->getDataGenerator()->create_user();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($initialuser);
|
||||
|
||||
$testcontext = $contexts->$loginascontext;
|
||||
\core\session\manager::loginas($user->id, $testcontext);
|
||||
|
||||
$capability = 'moodle/block:view';
|
||||
foreach ($testcontexts as $contextname => $hascapability) {
|
||||
$this->assertEquals($hascapability, has_capability($capability, $contexts->$contextname));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the test data contexts.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function get_test_data(): stdclass {
|
||||
$generator = $this->getDataGenerator();
|
||||
$otheruser = $generator->create_user();
|
||||
|
||||
// / (system)
|
||||
// /Cat1
|
||||
// /Cat1/Block
|
||||
// /Cat1/Course1
|
||||
// /Cat1/Course1/Block
|
||||
// /Cat1/Course2
|
||||
// /Cat1/Course2/Block
|
||||
// /Cat1/Cat1a
|
||||
// /Cat1/Cat1a/Block
|
||||
// /Cat1/Cat1a/Course1
|
||||
// /Cat1/Cat1a/Course1/Block
|
||||
// /Cat1/Cat1a/Course2
|
||||
// /Cat1/Cat1a/Course2/Block
|
||||
// /Cat1/Cat1b
|
||||
// /Cat1/Cat1b/Block
|
||||
// /Cat1/Cat1b/Course1
|
||||
// /Cat1/Cat1b/Course1/Block
|
||||
// /Cat1/Cat1b/Course2
|
||||
// /Cat1/Cat1b/Course2/Block
|
||||
// /Cat2
|
||||
// /Cat2/Block
|
||||
// /Cat2/Course1
|
||||
// /Cat2/Course1/Block
|
||||
// /Cat2/Course2
|
||||
// /Cat2/Course2/Block
|
||||
// /Cat2/Cat2a
|
||||
// /Cat2/Cat2a/Block
|
||||
// /Cat2/Cat2a/Course1
|
||||
// /Cat2/Cat2a/Course1/Block
|
||||
// /Cat2/Cat2a/Course2
|
||||
// /Cat2/Cat2a/Course2/Block
|
||||
// /Cat2/Cat2b
|
||||
// /Cat2/Cat2b/Block
|
||||
// /Cat2/Cat2b/Course1
|
||||
// /Cat2/Cat2b/Course1/Block
|
||||
// /Cat2/Cat2b/Course2
|
||||
// /Cat2/Cat2b/Course2/Block
|
||||
|
||||
$adminuser = \core_user::get_user_by_username('admin');
|
||||
$contexts = (object) [
|
||||
'system' => \context_system::instance(),
|
||||
'adminuser' => \context_user::instance($adminuser->id),
|
||||
];
|
||||
|
||||
$cat1 = $generator->create_category();
|
||||
$cat1a = $generator->create_category(['parent' => $cat1->id]);
|
||||
$cat1b = $generator->create_category(['parent' => $cat1->id]);
|
||||
|
||||
$contexts->cat1 = \context_coursecat::instance($cat1->id);
|
||||
$contexts->cat1a = \context_coursecat::instance($cat1a->id);
|
||||
$contexts->cat1b = \context_coursecat::instance($cat1b->id);
|
||||
|
||||
$cat1course1 = $generator->create_course(['category' => $cat1->id]);
|
||||
$cat1course2 = $generator->create_course(['category' => $cat1->id]);
|
||||
$cat1acourse1 = $generator->create_course(['category' => $cat1a->id]);
|
||||
$cat1acourse2 = $generator->create_course(['category' => $cat1a->id]);
|
||||
$cat1bcourse1 = $generator->create_course(['category' => $cat1b->id]);
|
||||
$cat1bcourse2 = $generator->create_course(['category' => $cat1b->id]);
|
||||
|
||||
$contexts->cat1course1 = \context_course::instance($cat1course1->id);
|
||||
$contexts->cat1acourse1 = \context_course::instance($cat1acourse1->id);
|
||||
$contexts->cat1bcourse1 = \context_course::instance($cat1bcourse1->id);
|
||||
$contexts->cat1course2 = \context_course::instance($cat1course2->id);
|
||||
$contexts->cat1acourse2 = \context_course::instance($cat1acourse2->id);
|
||||
$contexts->cat1bcourse2 = \context_course::instance($cat1bcourse2->id);
|
||||
|
||||
$cat1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1->id]);
|
||||
$cat1ablock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1a->id]);
|
||||
$cat1bblock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1b->id]);
|
||||
$cat1course1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1course1->id]);
|
||||
$cat1course2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1course2->id]);
|
||||
$cat1acourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1acourse1->id]);
|
||||
$cat1acourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1acourse2->id]);
|
||||
$cat1bcourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1bcourse1->id]);
|
||||
$cat1bcourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat1bcourse2->id]);
|
||||
|
||||
$contexts->cat1block = \context_block::instance($cat1block->id);
|
||||
$contexts->cat1ablock = \context_block::instance($cat1ablock->id);
|
||||
$contexts->cat1bblock = \context_block::instance($cat1bblock->id);
|
||||
$contexts->cat1course1block = \context_block::instance($cat1course1block->id);
|
||||
$contexts->cat1course2block = \context_block::instance($cat1course2block->id);
|
||||
$contexts->cat1acourse1block = \context_block::instance($cat1acourse1block->id);
|
||||
$contexts->cat1acourse2block = \context_block::instance($cat1acourse2block->id);
|
||||
$contexts->cat1bcourse1block = \context_block::instance($cat1bcourse1block->id);
|
||||
$contexts->cat1bcourse2block = \context_block::instance($cat1bcourse2block->id);
|
||||
|
||||
$cat2 = $generator->create_category();
|
||||
$cat2a = $generator->create_category(['parent' => $cat2->id]);
|
||||
$cat2b = $generator->create_category(['parent' => $cat2->id]);
|
||||
|
||||
$contexts->cat2 = \context_coursecat::instance($cat2->id);
|
||||
$contexts->cat2a = \context_coursecat::instance($cat2a->id);
|
||||
$contexts->cat2b = \context_coursecat::instance($cat2b->id);
|
||||
|
||||
$cat2course1 = $generator->create_course(['category' => $cat2->id]);
|
||||
$cat2course2 = $generator->create_course(['category' => $cat2->id]);
|
||||
$cat2acourse1 = $generator->create_course(['category' => $cat2a->id]);
|
||||
$cat2acourse2 = $generator->create_course(['category' => $cat2a->id]);
|
||||
$cat2bcourse1 = $generator->create_course(['category' => $cat2b->id]);
|
||||
$cat2bcourse2 = $generator->create_course(['category' => $cat2b->id]);
|
||||
|
||||
$contexts->cat2course1 = \context_course::instance($cat2course1->id);
|
||||
$contexts->cat2acourse1 = \context_course::instance($cat2acourse1->id);
|
||||
$contexts->cat2bcourse1 = \context_course::instance($cat2bcourse1->id);
|
||||
$contexts->cat2course2 = \context_course::instance($cat2course2->id);
|
||||
$contexts->cat2acourse2 = \context_course::instance($cat2acourse2->id);
|
||||
$contexts->cat2bcourse2 = \context_course::instance($cat2bcourse2->id);
|
||||
|
||||
$cat2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2->id]);
|
||||
$cat2ablock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2a->id]);
|
||||
$cat2bblock = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2b->id]);
|
||||
$cat2course1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2course1->id]);
|
||||
$cat2course2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2course2->id]);
|
||||
$cat2acourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2acourse1->id]);
|
||||
$cat2acourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2acourse2->id]);
|
||||
$cat2bcourse1block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2bcourse1->id]);
|
||||
$cat2bcourse2block = $generator->create_block('online_users', ['parentcontextid' => $contexts->cat2bcourse2->id]);
|
||||
|
||||
$contexts->cat2block = \context_block::instance($cat2block->id);
|
||||
$contexts->cat2ablock = \context_block::instance($cat2ablock->id);
|
||||
$contexts->cat2bblock = \context_block::instance($cat2bblock->id);
|
||||
$contexts->cat2course1block = \context_block::instance($cat2course1block->id);
|
||||
$contexts->cat2course2block = \context_block::instance($cat2course2block->id);
|
||||
$contexts->cat2acourse1block = \context_block::instance($cat2acourse1block->id);
|
||||
$contexts->cat2acourse2block = \context_block::instance($cat2acourse2block->id);
|
||||
$contexts->cat2bcourse1block = \context_block::instance($cat2bcourse1block->id);
|
||||
$contexts->cat2bcourse2block = \context_block::instance($cat2bcourse2block->id);
|
||||
|
||||
return $contexts;
|
||||
}
|
||||
}
|
||||
@@ -3945,6 +3945,204 @@ class core_accesslib_testcase extends advanced_testcase {
|
||||
$this->assertEquals($expectedteacher, get_profile_roles($coursecontext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for is_parent_of context checks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function is_parent_of_provider(): array {
|
||||
$provideboth = function(string $desc, string $contextpath, string $testpath, bool $expected): array {
|
||||
return [
|
||||
"includeself: true; {$desc}" => [
|
||||
$contextpath,
|
||||
$testpath,
|
||||
true,
|
||||
$expected,
|
||||
],
|
||||
"includeself: false; {$desc}" => [
|
||||
$contextpath,
|
||||
$testpath,
|
||||
false,
|
||||
$expected,
|
||||
],
|
||||
];
|
||||
};
|
||||
|
||||
return array_merge(
|
||||
[
|
||||
'includeself: true, testing self' => [
|
||||
'/1/4/17/291/1001/17105',
|
||||
'/1/4/17/291/1001/17105',
|
||||
true,
|
||||
true,
|
||||
],
|
||||
'includeself: false, testing self' => [
|
||||
'/1/4/17/291/1001/17105',
|
||||
'/1/4/17/291/1001/17105',
|
||||
false,
|
||||
false,
|
||||
],
|
||||
],
|
||||
$provideboth(
|
||||
'testing parent',
|
||||
'/1/4/17/291/1001/17105',
|
||||
'/1/4/17/291/1001',
|
||||
false
|
||||
),
|
||||
$provideboth(
|
||||
'testing child',
|
||||
'/1/4/17/291/1001',
|
||||
'/1/4/17/291/1001/17105',
|
||||
true
|
||||
),
|
||||
$provideboth(
|
||||
'testing grandchild',
|
||||
'/1',
|
||||
'/1/4/17/291/1001/17105',
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the is_parent_of() function works as anticipated.
|
||||
*
|
||||
* @dataProvider is_parent_of_provider
|
||||
* @param string $contextpath The path of the context being compared with
|
||||
* @param string $testpath The path of the context being compared
|
||||
* @param bool $testself Whether to check the current context
|
||||
* @param bool $expected The expected result
|
||||
*/
|
||||
public function test_is_parent_of(string $contextpath, string $testpath, bool $testself, bool $expected) {
|
||||
$context = $this->getMockBuilder(\context::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'get_url',
|
||||
'get_capabilities',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$rcp = new ReflectionProperty($context, '_path');
|
||||
$rcp->setAccessible(true);
|
||||
$rcp->setValue($context, $contextpath);
|
||||
|
||||
$comparisoncontext = $this->getMockBuilder(\context::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'get_url',
|
||||
'get_capabilities',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$rcp = new ReflectionProperty($comparisoncontext, '_path');
|
||||
$rcp->setAccessible(true);
|
||||
$rcp->setValue($comparisoncontext, $testpath);
|
||||
|
||||
$this->assertEquals($expected, $context->is_parent_of($comparisoncontext, $testself));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for is_child_of context checks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function is_child_of_provider(): array {
|
||||
$provideboth = function(string $desc, string $contextpath, string $testpath, bool $expected): array {
|
||||
return [
|
||||
"includeself: true; {$desc}" => [
|
||||
$contextpath,
|
||||
$testpath,
|
||||
true,
|
||||
$expected,
|
||||
],
|
||||
"includeself: false; {$desc}" => [
|
||||
$contextpath,
|
||||
$testpath,
|
||||
false,
|
||||
$expected,
|
||||
],
|
||||
];
|
||||
};
|
||||
|
||||
return array_merge(
|
||||
[
|
||||
'includeself: true, testing self' => [
|
||||
'/1/4/17/291/1001/17105',
|
||||
'/1/4/17/291/1001/17105',
|
||||
true,
|
||||
true,
|
||||
],
|
||||
'includeself: false, testing self' => [
|
||||
'/1/4/17/291/1001/17105',
|
||||
'/1/4/17/291/1001/17105',
|
||||
false,
|
||||
false,
|
||||
],
|
||||
],
|
||||
$provideboth(
|
||||
'testing child',
|
||||
'/1/4/17/291/1001/17105',
|
||||
'/1/4/17/291/1001',
|
||||
true
|
||||
),
|
||||
$provideboth(
|
||||
'testing parent',
|
||||
'/1/4/17/291/1001',
|
||||
'/1/4/17/291/1001/17105',
|
||||
false
|
||||
),
|
||||
$provideboth(
|
||||
'testing grandchild',
|
||||
'/1/4/17/291/1001/17105',
|
||||
'/1',
|
||||
true
|
||||
),
|
||||
$provideboth(
|
||||
'testing grandparent',
|
||||
'/1',
|
||||
'/1/4/17/291/1001/17105',
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the is_child_of() function works as anticipated.
|
||||
*
|
||||
* @dataProvider is_child_of_provider
|
||||
* @param string $contextpath The path of the context being compared with
|
||||
* @param string $testpath The path of the context being compared
|
||||
* @param bool $testself Whether to check the current context
|
||||
* @param bool $expected The expected result
|
||||
*/
|
||||
public function test_is_child_of(string $contextpath, string $testpath, bool $testself, bool $expected) {
|
||||
$context = $this->getMockBuilder(\context::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'get_url',
|
||||
'get_capabilities',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$rcp = new ReflectionProperty($context, '_path');
|
||||
$rcp->setAccessible(true);
|
||||
$rcp->setValue($context, $contextpath);
|
||||
|
||||
$comparisoncontext = $this->getMockBuilder(\context::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods([
|
||||
'get_url',
|
||||
'get_capabilities',
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$rcp = new ReflectionProperty($comparisoncontext, '_path');
|
||||
$rcp->setAccessible(true);
|
||||
$rcp->setValue($comparisoncontext, $testpath);
|
||||
|
||||
$this->assertEquals($expected, $context->is_child_of($comparisoncontext, $testself));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the get_parent_contexts() function limits the number of queries it performs.
|
||||
*/
|
||||
|
||||
@@ -75,7 +75,7 @@ class behat_data_generators extends behat_base {
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates the specified element.
|
||||
* Creates the specified elements.
|
||||
*
|
||||
* See the class comment for an overview.
|
||||
*
|
||||
@@ -92,6 +92,24 @@ class behat_data_generators extends behat_base {
|
||||
$this->get_instance_for_component($component)->generate_items($entity, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the specified element.
|
||||
*
|
||||
* See the class comment for an overview.
|
||||
*
|
||||
* @Given the following :entitytype exists:
|
||||
*
|
||||
* @param string $entitytype The name of the type entity to add
|
||||
* @param TableNode $data
|
||||
*/
|
||||
public function the_following_entity_exists($entitytype, TableNode $data) {
|
||||
if (isset($this->movedentitytypes[$entitytype])) {
|
||||
$entitytype = $this->movedentitytypes[$entitytype];
|
||||
}
|
||||
list($component, $entity) = $this->parse_entity_type($entitytype);
|
||||
$this->get_instance_for_component($component)->generate_items($entity, $data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a full entity type like 'users' or 'mod_forum > subscription'.
|
||||
*
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
|
||||
require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
|
||||
|
||||
use Behat\Gherkin\Node\TableNode as TableNode,
|
||||
Behat\Gherkin\Node\PyStringNode as PyStringNode,
|
||||
Behat\Mink\Exception\ExpectationException as ExpectationException,
|
||||
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
|
||||
use Behat\Gherkin\Node\{TableNode, PyStringNode};
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Behat\Mink\Exception\{ElementNotFoundException, ExpectationException};
|
||||
|
||||
/**
|
||||
* Forms-related steps definitions.
|
||||
@@ -286,10 +285,7 @@ class behat_forms extends behat_base {
|
||||
* @return void
|
||||
*/
|
||||
public function i_set_the_field_with_xpath_to($fieldxpath, $value) {
|
||||
$fieldnode = $this->find('xpath', $fieldxpath);
|
||||
$this->ensure_node_is_visible($fieldnode);
|
||||
$field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
|
||||
$field->set_value($value);
|
||||
$this->set_field_node_value($this->find('xpath', $fieldxpath), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -627,13 +623,24 @@ class behat_forms extends behat_base {
|
||||
* @return void
|
||||
*/
|
||||
protected function set_field_value($fieldlocator, $value) {
|
||||
|
||||
// We delegate to behat_form_field class, it will
|
||||
// guess the type properly as it is a select tag.
|
||||
$field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
|
||||
$field->set_value($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic field setter to be used by chainable steps.
|
||||
*
|
||||
* @param NodeElement $fieldnode
|
||||
* @param string $value
|
||||
*/
|
||||
public function set_field_node_value(NodeElement $fieldnode, string $value) {
|
||||
$this->ensure_node_is_visible($fieldnode);
|
||||
$field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
|
||||
$field->set_value($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic field setter.
|
||||
*
|
||||
@@ -646,12 +653,8 @@ class behat_forms extends behat_base {
|
||||
* @param string $containerelement Element we look in
|
||||
*/
|
||||
protected function set_field_value_in_container($fieldlocator, $value, $containerselectortype, $containerelement) {
|
||||
|
||||
$node = $this->get_node_in_container('field', $fieldlocator, $containerselectortype, $containerelement);
|
||||
// We delegate to behat_form_field class, it will
|
||||
// guess the type properly as it is a select tag.
|
||||
$field = behat_field_manager::get_form_field($node, $this->getSession());
|
||||
$field->set_value($value);
|
||||
$this->set_field_node_value($node, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,7 +74,7 @@ class behat_general extends behat_base {
|
||||
* @Given /^I am on homepage$/
|
||||
*/
|
||||
public function i_am_on_homepage() {
|
||||
$this->getSession()->visit($this->locate_path('/'));
|
||||
$this->execute('behat_general::i_visit', ['/']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +83,7 @@ class behat_general extends behat_base {
|
||||
* @Given /^I am on site homepage$/
|
||||
*/
|
||||
public function i_am_on_site_homepage() {
|
||||
$this->getSession()->visit($this->locate_path('/?redirect=0'));
|
||||
$this->execute('behat_general::i_visit', ['/?redirect=0']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +92,7 @@ class behat_general extends behat_base {
|
||||
* @Given /^I am on course index$/
|
||||
*/
|
||||
public function i_am_on_course_index() {
|
||||
$this->getSession()->visit($this->locate_path('/course/index.php'));
|
||||
$this->execute('behat_general::i_visit', ['/course/index.php']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,8 +200,7 @@ class behat_general extends behat_base {
|
||||
// unnamed window (presumably the main window) to some other named
|
||||
// window, then we first set the main window name to a conventional
|
||||
// value that we can later use this name to switch back.
|
||||
$this->getSession()->executeScript(
|
||||
'if (window.name == "") window.name = "' . self::MAIN_WINDOW_NAME . '"');
|
||||
$this->execute_script('if (window.name == "") window.name = "' . self::MAIN_WINDOW_NAME . '"');
|
||||
|
||||
$this->getSession()->switchToWindow($windowname);
|
||||
}
|
||||
@@ -872,7 +871,7 @@ class behat_general extends behat_base {
|
||||
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING;
|
||||
})()
|
||||
EOF;
|
||||
$ok = $this->getSession()->getDriver()->evaluateScript($js);
|
||||
$ok = $this->evaluate_script($js);
|
||||
} else {
|
||||
|
||||
// Using following xpath axe to find it.
|
||||
@@ -1006,7 +1005,7 @@ EOF;
|
||||
* @Given /^I trigger cron$/
|
||||
*/
|
||||
public function i_trigger_cron() {
|
||||
$this->getSession()->visit($this->locate_path('/admin/cron.php'));
|
||||
$this->execute('behat_general::i_visit', ['/admin/cron.php']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1517,11 +1516,12 @@ EOF;
|
||||
|
||||
$this->pageloaddetectionrunning = true;
|
||||
|
||||
$session->executeScript(
|
||||
'var span = document.createElement("span");
|
||||
span.setAttribute("data-rel", "' . self::PAGE_LOAD_DETECTION_STRING . '");
|
||||
span.setAttribute("style", "display: none;");
|
||||
document.body.appendChild(span);');
|
||||
$this->execute_script(
|
||||
'var span = document.createElement("span");
|
||||
span.setAttribute("data-rel", "' . self::PAGE_LOAD_DETECTION_STRING . '");
|
||||
span.setAttribute("style", "display: none;");
|
||||
document.body.appendChild(span);'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1719,7 +1719,7 @@ EOF;
|
||||
$xpath = addslashes_js($element->getXpath());
|
||||
$script = 'return (function() { return document.activeElement === document.evaluate("' . $xpath . '",
|
||||
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; })(); ';
|
||||
$targetisfocused = $this->getSession()->evaluateScript($script);
|
||||
$targetisfocused = $this->evaluate_script($script);
|
||||
if ($not == ' not') {
|
||||
if ($targetisfocused) {
|
||||
throw new ExpectationException("$nodeelement $nodeselectortype is focused", $this->getSession());
|
||||
@@ -1751,7 +1751,7 @@ EOF;
|
||||
$xpath = addslashes_js($element->getXpath());
|
||||
$script = 'return (function() { return document.activeElement === document.evaluate("' . $xpath . '",
|
||||
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; })(); ';
|
||||
$targetisfocused = $this->getSession()->evaluateScript($script);
|
||||
$targetisfocused = $this->evaluate_script($script);
|
||||
if ($not == ' not') {
|
||||
if ($targetisfocused) {
|
||||
throw new ExpectationException("$nodeelement $nodeselectortype is focused", $this->getSession());
|
||||
@@ -1778,4 +1778,16 @@ EOF;
|
||||
$value = ($shift == ' shift') ? [\WebDriver\Key::SHIFT . \WebDriver\Key::TAB] : [\WebDriver\Key::TAB];
|
||||
$this->getSession()->getDriver()->getWebDriverSession()->activeElement()->postValue(['value' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit a local URL relative to the behat root.
|
||||
*
|
||||
* @When I visit :localurl
|
||||
*
|
||||
* @param string|moodle_url $localurl The URL relative to the behat_wwwroot to visit.
|
||||
*/
|
||||
public function i_visit($localurl) {
|
||||
$localurl = new moodle_url($localurl);
|
||||
$this->getSession()->visit($this->locate_path($localurl->out_as_local_url(false)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,17 @@ class behat_hooks extends behat_base {
|
||||
*/
|
||||
protected static $currentstepexception = null;
|
||||
|
||||
/**
|
||||
* If an Exception is thrown in the BeforeScenario hook it will cause the Scenario to be skipped, and the exit code
|
||||
* to be non-zero triggering a potential rerun.
|
||||
*
|
||||
* To combat this the exception is stored and re-thrown when looking for exceptions.
|
||||
* This allows the test to instead be failed and re-run correctly.
|
||||
*
|
||||
* @var null|Exception
|
||||
*/
|
||||
protected static $currentscenarioexception = null;
|
||||
|
||||
/**
|
||||
* If we are saving any kind of dump on failure we should use the same parent dir during a run.
|
||||
*
|
||||
@@ -166,7 +177,7 @@ class behat_hooks extends behat_base {
|
||||
$message = <<<EOF
|
||||
Your behat test site is outdated, please run the following command from your Moodle dirroot to drop, and reinstall the Behat test site.
|
||||
|
||||
{$comandpath}
|
||||
{$commandpath}
|
||||
|
||||
EOF;
|
||||
self::log_and_stop($message);
|
||||
@@ -361,8 +372,13 @@ EOF;
|
||||
// The `before_first_scenario_start_session` function will have started the session instead.
|
||||
return;
|
||||
}
|
||||
self::$currentscenarioexception = null;
|
||||
|
||||
$this->restart_session();
|
||||
try {
|
||||
$this->restart_session();
|
||||
} catch (Exception $e) {
|
||||
self::$currentscenarioexception = $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,6 +389,12 @@ EOF;
|
||||
*/
|
||||
public function before_scenario_hook(BeforeScenarioScope $scope) {
|
||||
global $DB;
|
||||
if (self::$currentscenarioexception) {
|
||||
// A BeforeScenario hook triggered an exception and marked this test as failed.
|
||||
// Skip this hook as it will likely fail.
|
||||
return;
|
||||
}
|
||||
|
||||
$suitename = $scope->getSuite()->getName();
|
||||
|
||||
// Register behat selectors for theme, if suite is changed. We do it for every suite change.
|
||||
@@ -464,7 +486,7 @@ EOF;
|
||||
// Again, this would be better in the BeforeSuite hook, but that does not have access to the selectors in
|
||||
// order to perform the necessary searches.
|
||||
$session = $this->getSession();
|
||||
$session->visit($this->locate_path('/'));
|
||||
$this->execute('behat_general::i_visit', ['/']);
|
||||
|
||||
// Checking that the root path is a Moodle test site.
|
||||
if (self::is_first_scenario()) {
|
||||
@@ -495,6 +517,12 @@ EOF;
|
||||
* @BeforeStep
|
||||
*/
|
||||
public function before_step_javascript(BeforeStepScope $scope) {
|
||||
if (self::$currentscenarioexception) {
|
||||
// A BeforeScenario hook triggered an exception and marked this test as failed.
|
||||
// Skip this hook as it will likely fail.
|
||||
return;
|
||||
}
|
||||
|
||||
self::$currentstepexception = null;
|
||||
|
||||
// Only run if JS.
|
||||
@@ -711,6 +739,11 @@ EOF;
|
||||
* @see Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester
|
||||
*/
|
||||
public function i_look_for_exceptions() {
|
||||
// If the scenario already failed in a hook throw the exception.
|
||||
if (!is_null(self::$currentscenarioexception)) {
|
||||
throw self::$currentscenarioexception;
|
||||
}
|
||||
|
||||
// If the step already failed in a hook throw the exception.
|
||||
if (!is_null(self::$currentstepexception)) {
|
||||
throw self::$currentstepexception;
|
||||
|
||||
@@ -426,7 +426,7 @@ class behat_navigation extends behat_base {
|
||||
)";
|
||||
|
||||
// Adding an extra click we need to show the 'Log in' link.
|
||||
if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
|
||||
if (!$this->evaluate_script($navbuttonjs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -567,7 +567,7 @@ class behat_navigation extends behat_base {
|
||||
|
||||
}
|
||||
}
|
||||
$this->getSession()->visit($this->locate_path($url->out_as_local_url()));
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
}
|
||||
|
||||
// Restore global user variable.
|
||||
@@ -590,8 +590,7 @@ class behat_navigation extends behat_base {
|
||||
* @throws Exception if the specified page cannot be determined.
|
||||
*/
|
||||
public function i_am_on_page(string $page) {
|
||||
$this->getSession()->visit($this->locate_path(
|
||||
$this->resolve_page_helper($page)->out_as_local_url()));
|
||||
$this->execute('behat_general::i_visit', [$this->resolve_page_helper($page)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -670,8 +669,7 @@ class behat_navigation extends behat_base {
|
||||
* @throws Exception if the specified page cannot be determined.
|
||||
*/
|
||||
public function i_am_on_page_instance(string $identifier, string $type) {
|
||||
$this->getSession()->visit($this->locate_path(
|
||||
$this->resolve_page_instance_helper($identifier, $type)->out_as_local_url()));
|
||||
$this->execute('behat_general::i_visit', [$this->resolve_page_instance_helper($identifier, $type)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -776,11 +774,11 @@ class behat_navigation extends behat_base {
|
||||
global $DB;
|
||||
$course = $DB->get_record("course", array("fullname" => $coursefullname), 'id', MUST_EXIST);
|
||||
$url = new moodle_url('/course/view.php', ['id' => $course->id]);
|
||||
$this->getSession()->visit($this->locate_path($url->out_as_local_url(false)));
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the course homepage with editing mode on.
|
||||
* Open the course homepage with editing mode enabled.
|
||||
*
|
||||
* @Given /^I am on "(?P<coursefullname_string>(?:[^"]|\\")*)" course homepage with editing mode on$/
|
||||
* @throws coding_exception
|
||||
@@ -789,9 +787,22 @@ class behat_navigation extends behat_base {
|
||||
*/
|
||||
public function i_am_on_course_homepage_with_editing_mode_on($coursefullname) {
|
||||
global $DB;
|
||||
|
||||
$course = $DB->get_record("course", array("fullname" => $coursefullname), 'id', MUST_EXIST);
|
||||
$url = new moodle_url('/course/view.php', ['id' => $course->id]);
|
||||
$this->getSession()->visit($this->locate_path($url->out_as_local_url(false)));
|
||||
|
||||
if ($this->running_javascript() && $sesskey = $this->get_sesskey()) {
|
||||
// Javascript is running so it is possible to grab the session ket and jump straight to editing mode.
|
||||
$url->param('edit', 1);
|
||||
$url->param('sesskey', $sesskey);
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Visit the course page.
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
|
||||
try {
|
||||
$this->execute("behat_forms::press_button", get_string('turneditingon'));
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -148,73 +148,87 @@ class core_scheduled_task_testcase extends advanced_testcase {
|
||||
$this->assertContains('2:15 AM', core_text::strtoupper($userdate));
|
||||
}
|
||||
|
||||
public function test_reset_scheduled_tasks_for_component() {
|
||||
global $DB;
|
||||
|
||||
public function test_reset_scheduled_tasks_for_component_customised() {
|
||||
$this->resetAfterTest(true);
|
||||
// Remember the defaults.
|
||||
$defaulttasks = \core\task\manager::load_scheduled_tasks_for_component('moodle');
|
||||
$initcount = count($defaulttasks);
|
||||
|
||||
$tasks = \core\task\manager::load_scheduled_tasks_for_component('moodle');
|
||||
|
||||
// Customise a task.
|
||||
$firsttask = reset($defaulttasks);
|
||||
$firsttask->set_minute('1');
|
||||
$firsttask->set_hour('2');
|
||||
$firsttask->set_month('3');
|
||||
$firsttask->set_day_of_week('4');
|
||||
$firsttask->set_day('5');
|
||||
$firsttask->set_customised('1');
|
||||
\core\task\manager::configure_scheduled_task($firsttask);
|
||||
$firsttaskrecord = \core\task\manager::record_from_scheduled_task($firsttask);
|
||||
// We reset this field, because we do not want to compare it.
|
||||
$firsttaskrecord->nextruntime = '0';
|
||||
$task = reset($tasks);
|
||||
$task->set_minute('1');
|
||||
$task->set_hour('2');
|
||||
$task->set_month('3');
|
||||
$task->set_day_of_week('4');
|
||||
$task->set_day('5');
|
||||
$task->set_customised('1');
|
||||
\core\task\manager::configure_scheduled_task($task);
|
||||
|
||||
// Now call reset.
|
||||
\core\task\manager::reset_scheduled_tasks_for_component('moodle');
|
||||
|
||||
// Fetch the task again.
|
||||
$taskafterreset = \core\task\manager::get_scheduled_task(get_class($task));
|
||||
|
||||
// The task should still be the same as the customised.
|
||||
$this->assertTaskEquals($task, $taskafterreset);
|
||||
}
|
||||
|
||||
public function test_reset_scheduled_tasks_for_component_deleted() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Delete a task to simulate the fact that its new.
|
||||
$secondtask = next($defaulttasks);
|
||||
$DB->delete_records('task_scheduled', array('classname' => '\\' . trim(get_class($secondtask), '\\')));
|
||||
$this->assertFalse(\core\task\manager::get_scheduled_task(get_class($secondtask)));
|
||||
$tasklist = \core\task\manager::load_scheduled_tasks_for_component('moodle');
|
||||
|
||||
// Edit a task to simulate a change in its definition (as if it was not customised).
|
||||
$thirdtask = next($defaulttasks);
|
||||
$thirdtask->set_minute('1');
|
||||
$thirdtask->set_hour('2');
|
||||
$thirdtask->set_month('3');
|
||||
$thirdtask->set_day_of_week('4');
|
||||
$thirdtask->set_day('5');
|
||||
$thirdtaskbefore = \core\task\manager::get_scheduled_task(get_class($thirdtask));
|
||||
$thirdtaskbefore->set_next_run_time(null); // Ignore this value when comparing.
|
||||
\core\task\manager::configure_scheduled_task($thirdtask);
|
||||
$thirdtask = \core\task\manager::get_scheduled_task(get_class($thirdtask));
|
||||
$thirdtask->set_next_run_time(null); // Ignore this value when comparing.
|
||||
$this->assertNotEquals($thirdtaskbefore, $thirdtask);
|
||||
// Note: This test must use a task which does not use any random values.
|
||||
$task = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
|
||||
|
||||
$DB->delete_records('task_scheduled', array('classname' => '\\' . trim(get_class($task), '\\')));
|
||||
$this->assertFalse(\core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class));
|
||||
|
||||
// Now call reset on all the tasks.
|
||||
\core\task\manager::reset_scheduled_tasks_for_component('moodle');
|
||||
|
||||
// Load the tasks again.
|
||||
$defaulttasks = \core\task\manager::load_scheduled_tasks_for_component('moodle');
|
||||
$finalcount = count($defaulttasks);
|
||||
// Compare the first task.
|
||||
$newfirsttask = reset($defaulttasks);
|
||||
$newfirsttaskrecord = \core\task\manager::record_from_scheduled_task($newfirsttask);
|
||||
// We reset this field, because we do not want to compare it.
|
||||
$newfirsttaskrecord->nextruntime = '0';
|
||||
|
||||
// Assert a customised task was not altered by reset.
|
||||
$this->assertEquals($firsttaskrecord, $newfirsttaskrecord);
|
||||
|
||||
// Assert that the second task was added back.
|
||||
$secondtaskafter = \core\task\manager::get_scheduled_task(get_class($secondtask));
|
||||
$secondtaskafter->set_next_run_time(null); // Do not compare the nextruntime.
|
||||
$secondtask->set_next_run_time(null);
|
||||
$this->assertEquals($secondtask, $secondtaskafter);
|
||||
$taskafterreset = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
|
||||
$this->assertNotFalse($taskafterreset);
|
||||
|
||||
// Assert that the third task edits were overridden.
|
||||
$thirdtaskafter = \core\task\manager::get_scheduled_task(get_class($thirdtask));
|
||||
$thirdtaskafter->set_next_run_time(null);
|
||||
$this->assertEquals($thirdtaskbefore, $thirdtaskafter);
|
||||
$this->assertTaskEquals($task, $taskafterreset);
|
||||
$this->assertCount(count($tasklist), \core\task\manager::load_scheduled_tasks_for_component('moodle'));
|
||||
}
|
||||
|
||||
// Assert we have the same number of tasks.
|
||||
$this->assertEquals($initcount, $finalcount);
|
||||
public function test_reset_scheduled_tasks_for_component_changed_in_source() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Delete a task to simulate the fact that its new.
|
||||
// Note: This test must use a task which does not use any random values.
|
||||
$task = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
|
||||
|
||||
// Get a copy of the task before maing changes for later comparison.
|
||||
$taskbeforechange = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
|
||||
|
||||
// Edit a task to simulate a change in its definition (as if it was not customised).
|
||||
$task->set_minute('1');
|
||||
$task->set_hour('2');
|
||||
$task->set_month('3');
|
||||
$task->set_day_of_week('4');
|
||||
$task->set_day('5');
|
||||
\core\task\manager::configure_scheduled_task($task);
|
||||
|
||||
// Fetch the task out for comparison.
|
||||
$taskafterchange = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
|
||||
|
||||
// The task should now be different to the original.
|
||||
$this->assertTaskNotEquals($taskbeforechange, $taskafterchange);
|
||||
|
||||
// Now call reset.
|
||||
\core\task\manager::reset_scheduled_tasks_for_component('moodle');
|
||||
|
||||
// Fetch the task again.
|
||||
$taskafterreset = \core\task\manager::get_scheduled_task(core\task\session_cleanup_task::class);
|
||||
|
||||
// The task should now be the same as the original.
|
||||
$this->assertTaskEquals($taskbeforechange, $taskafterreset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,4 +516,56 @@ class core_scheduled_task_testcase extends advanced_testcase {
|
||||
$this->assertEquals(0, $task->get_fail_delay());
|
||||
$this->assertLessThan($before + 70, $task->get_next_run_time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the specified tasks are equal.
|
||||
*
|
||||
* @param \core\task\task_base $task
|
||||
* @param \core\task\task_base $comparisontask
|
||||
*/
|
||||
public function assertTaskEquals(\core\task\task_base $task, \core\task\task_base $comparisontask) {
|
||||
// Convert both to an object.
|
||||
$task = \core\task\manager::record_from_scheduled_task($task);
|
||||
$comparisontask = \core\task\manager::record_from_scheduled_task($comparisontask);
|
||||
|
||||
// Reset the nextruntime field as it is intentionally dynamic.
|
||||
$task->nextruntime = null;
|
||||
$comparisontask->nextruntime = null;
|
||||
|
||||
$args = array_merge(
|
||||
[
|
||||
$task,
|
||||
$comparisontask,
|
||||
],
|
||||
array_slice(func_get_args(), 2)
|
||||
);
|
||||
|
||||
call_user_func_array([$this, 'assertEquals'], $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the specified tasks are not equal.
|
||||
*
|
||||
* @param \core\task\task_base $task
|
||||
* @param \core\task\task_base $comparisontask
|
||||
*/
|
||||
public function assertTaskNotEquals(\core\task\task_base $task, \core\task\task_base $comparisontask) {
|
||||
// Convert both to an object.
|
||||
$task = \core\task\manager::record_from_scheduled_task($task);
|
||||
$comparisontask = \core\task\manager::record_from_scheduled_task($comparisontask);
|
||||
|
||||
// Reset the nextruntime field as it is intentionally dynamic.
|
||||
$task->nextruntime = null;
|
||||
$comparisontask->nextruntime = null;
|
||||
|
||||
$args = array_merge(
|
||||
[
|
||||
$task,
|
||||
$comparisontask,
|
||||
],
|
||||
array_slice(func_get_args(), 2)
|
||||
);
|
||||
|
||||
call_user_func_array([$this, 'assertNotEquals'], $args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,21 +61,21 @@ class behat_assignfeedback_editpdf extends behat_base {
|
||||
var event = { clientX: 100, clientY: 250, preventDefault: function() {} };
|
||||
instance.edit_start(event);
|
||||
}()); ';
|
||||
$this->getSession()->executeScript($js);
|
||||
$this->execute_script($js);
|
||||
sleep(1);
|
||||
$js = ' (function() {
|
||||
var instance = M.assignfeedback_editpdf.instance;
|
||||
var event = { clientX: 150, clientY: 275, preventDefault: function() {} };
|
||||
instance.edit_move(event);
|
||||
}()); ';
|
||||
$this->getSession()->executeScript($js);
|
||||
$this->execute_script($js);
|
||||
sleep(1);
|
||||
$js = ' (function() {
|
||||
var instance = M.assignfeedback_editpdf.instance;
|
||||
var event = { clientX: 200, clientY: 300, preventDefault: function() {} };
|
||||
instance.edit_end(event);
|
||||
}()); ';
|
||||
$this->getSession()->executeScript($js);
|
||||
$this->execute_script($js);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
|
||||
+221
-131
@@ -46,39 +46,63 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
* Set up for every test
|
||||
*/
|
||||
public function setUp() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Setup test data.
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
$this->lti = $this->getDataGenerator()->create_module('lti',
|
||||
array('course' => $this->course->id, 'toolurl' => 'http://localhost/not/real/tool.php'));
|
||||
$this->context = context_module::instance($this->lti->cmid);
|
||||
$this->cm = get_coursemodule_from_instance('lti', $this->lti->id);
|
||||
|
||||
// Create users.
|
||||
$this->student = self::getDataGenerator()->create_user();
|
||||
$this->teacher = self::getDataGenerator()->create_user();
|
||||
|
||||
// Users enrolments.
|
||||
$this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
$this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test view_lti
|
||||
* Sets up some basic test data including course, users, roles, and an lti instance, for use in some tests.
|
||||
* @return array
|
||||
*/
|
||||
protected function setup_test_data() {
|
||||
global $DB;
|
||||
$this->setAdminUser();
|
||||
|
||||
// Setup test data.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$lti = $this->getDataGenerator()->create_module(
|
||||
'lti',
|
||||
['course' => $course->id, 'toolurl' => 'http://localhost/not/real/tool.php']
|
||||
);
|
||||
$context = context_module::instance($lti->cmid);
|
||||
$cm = get_coursemodule_from_instance('lti', $lti->id);
|
||||
|
||||
// Create users.
|
||||
$student = self::getDataGenerator()->create_user();
|
||||
$teacher = self::getDataGenerator()->create_user();
|
||||
|
||||
// Users enrolments.
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
|
||||
|
||||
return [
|
||||
'course' => $course,
|
||||
'lti' => $lti,
|
||||
'context' => $context,
|
||||
'cm' => $cm,
|
||||
'student' => $student,
|
||||
'teacher' => $teacher,
|
||||
'studentrole' => $studentrole,
|
||||
'teacherrole' => $teacherrole
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_tool_launch_data.
|
||||
*/
|
||||
public function test_get_tool_launch_data() {
|
||||
global $USER, $SITE;
|
||||
global $USER;
|
||||
|
||||
$result = mod_lti_external::get_tool_launch_data($this->lti->id);
|
||||
$td = $this->setup_test_data();
|
||||
$lti = $td['lti'];
|
||||
$course = $td['course'];
|
||||
|
||||
$result = mod_lti_external::get_tool_launch_data($lti->id);
|
||||
$result = external_api::clean_returnvalue(mod_lti_external::get_tool_launch_data_returns(), $result);
|
||||
|
||||
// Basic test, the function returns what it's expected.
|
||||
self::assertEquals($this->lti->toolurl, $result['endpoint']);
|
||||
self::assertEquals($lti->toolurl, $result['endpoint']);
|
||||
self::assertCount(36, $result['parameters']);
|
||||
|
||||
// Check some parameters.
|
||||
@@ -86,9 +110,9 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
foreach ($result['parameters'] as $param) {
|
||||
$parameters[$param['name']] = $param['value'];
|
||||
}
|
||||
self::assertEquals($this->lti->resourcekey, $parameters['oauth_consumer_key']);
|
||||
self::assertEquals($this->course->fullname, $parameters['context_title']);
|
||||
self::assertEquals($this->course->shortname, $parameters['context_label']);
|
||||
self::assertEquals($lti->resourcekey, $parameters['oauth_consumer_key']);
|
||||
self::assertEquals($course->fullname, $parameters['context_title']);
|
||||
self::assertEquals($course->shortname, $parameters['context_label']);
|
||||
self::assertEquals($USER->id, $parameters['user_id']);
|
||||
self::assertEquals($USER->firstname, $parameters['lis_person_name_given']);
|
||||
self::assertEquals($USER->lastname, $parameters['lis_person_name_family']);
|
||||
@@ -96,14 +120,18 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
self::assertEquals($USER->username, $parameters['ext_user_username']);
|
||||
self::assertEquals("phpunit", $parameters['tool_consumer_instance_name']);
|
||||
self::assertEquals("PHPUnit test site", $parameters['tool_consumer_instance_description']);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Test get ltis by courses
|
||||
/**
|
||||
* Test get_ltis_by_courses.
|
||||
*/
|
||||
public function test_mod_lti_get_ltis_by_courses() {
|
||||
global $DB;
|
||||
$td = $this->setup_test_data();
|
||||
$course = $td['course'];
|
||||
$lti = $td['lti'];
|
||||
$student = $td['student'];
|
||||
$teacher = $td['teacher'];
|
||||
$studentrole = $td['studentrole'];
|
||||
|
||||
// Create additional course.
|
||||
$course2 = self::getDataGenerator()->create_course();
|
||||
@@ -122,19 +150,19 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$enrol->enrol_user($instance2, $this->student->id, $this->studentrole->id);
|
||||
$enrol->enrol_user($instance2, $student->id, $studentrole->id);
|
||||
|
||||
self::setUser($this->student);
|
||||
self::setUser($student);
|
||||
|
||||
$returndescription = mod_lti_external::get_ltis_by_courses_returns();
|
||||
|
||||
// Create what we expect to be returned when querying the two courses.
|
||||
// First for the student user.
|
||||
$expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'launchcontainer',
|
||||
'showtitlelaunch', 'showdescriptionlaunch', 'icon', 'secureicon');
|
||||
$expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles',
|
||||
'launchcontainer', 'showtitlelaunch', 'showdescriptionlaunch', 'icon', 'secureicon');
|
||||
|
||||
// Add expected coursemodule and data.
|
||||
$lti1 = $this->lti;
|
||||
$lti1 = $lti;
|
||||
$lti1->coursemodule = $lti1->cmid;
|
||||
$lti1->introformat = 1;
|
||||
$lti1->section = 0;
|
||||
@@ -152,14 +180,14 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
$lti2->introfiles = [];
|
||||
|
||||
foreach ($expectedfields as $field) {
|
||||
$expected1[$field] = $lti1->{$field};
|
||||
$expected2[$field] = $lti2->{$field};
|
||||
$expected1[$field] = $lti1->{$field};
|
||||
$expected2[$field] = $lti2->{$field};
|
||||
}
|
||||
|
||||
$expectedltis = array($expected2, $expected1);
|
||||
|
||||
// Call the external function passing course ids.
|
||||
$result = mod_lti_external::get_ltis_by_courses(array($course2->id, $this->course->id));
|
||||
$result = mod_lti_external::get_ltis_by_courses(array($course2->id, $course->id));
|
||||
$result = external_api::clean_returnvalue($returndescription, $result);
|
||||
|
||||
$this->assertEquals($expectedltis, $result['ltis']);
|
||||
@@ -172,7 +200,7 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertCount(0, $result['warnings']);
|
||||
|
||||
// Unenrol user from second course and alter expected ltis.
|
||||
$enrol->unenrol_user($instance2, $this->student->id);
|
||||
$enrol->unenrol_user($instance2, $student->id);
|
||||
array_shift($expectedltis);
|
||||
|
||||
// Call the external function without passing course id.
|
||||
@@ -182,20 +210,21 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
|
||||
// Call for the second course we unenrolled the user from, expected warning.
|
||||
$result = mod_lti_external::get_ltis_by_courses(array($course2->id));
|
||||
$result = external_api::clean_returnvalue($returndescription, $result);
|
||||
$this->assertCount(1, $result['warnings']);
|
||||
$this->assertEquals('1', $result['warnings'][0]['warningcode']);
|
||||
$this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
|
||||
|
||||
// Now, try as a teacher for getting all the additional fields.
|
||||
self::setUser($this->teacher);
|
||||
self::setUser($teacher);
|
||||
|
||||
$additionalfields = array('timecreated', 'timemodified', 'typeid', 'toolurl', 'securetoolurl',
|
||||
'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
|
||||
'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
|
||||
'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid');
|
||||
'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
|
||||
'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
|
||||
'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid');
|
||||
|
||||
foreach ($additionalfields as $field) {
|
||||
$expectedltis[0][$field] = $lti1->{$field};
|
||||
$expectedltis[0][$field] = $lti1->{$field};
|
||||
}
|
||||
|
||||
$result = mod_lti_external::get_ltis_by_courses();
|
||||
@@ -205,56 +234,88 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
// Admin also should get all the information.
|
||||
self::setAdminUser();
|
||||
|
||||
$result = mod_lti_external::get_ltis_by_courses(array($this->course->id));
|
||||
$result = mod_lti_external::get_ltis_by_courses(array($course->id));
|
||||
$result = external_api::clean_returnvalue($returndescription, $result);
|
||||
$this->assertEquals($expectedltis, $result['ltis']);
|
||||
|
||||
// Now, prohibit capabilities.
|
||||
$this->setUser($this->student);
|
||||
$contextcourse1 = context_course::instance($this->course->id);
|
||||
$this->setUser($student);
|
||||
$contextcourse1 = context_course::instance($course->id);
|
||||
// Prohibit capability = mod:lti:view on Course1 for students.
|
||||
assign_capability('mod/lti:view', CAP_PROHIBIT, $this->studentrole->id, $contextcourse1->id);
|
||||
assign_capability('mod/lti:view', CAP_PROHIBIT, $studentrole->id, $contextcourse1->id);
|
||||
// Empty all the caches that may be affected by this change.
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
course_modinfo::clear_instance_cache();
|
||||
|
||||
$ltis = mod_lti_external::get_ltis_by_courses(array($this->course->id));
|
||||
$ltis = mod_lti_external::get_ltis_by_courses(array($course->id));
|
||||
$ltis = external_api::clean_returnvalue(mod_lti_external::get_ltis_by_courses_returns(), $ltis);
|
||||
$this->assertCount(0, $ltis['ltis']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test view_lti
|
||||
* Test view_lti with an invalid instance id.
|
||||
*/
|
||||
public function test_view_lti() {
|
||||
global $DB;
|
||||
public function test_view_lti_invalid_instanceid() {
|
||||
$this->expectException(moodle_exception::class);
|
||||
mod_lti_external::view_lti(0);
|
||||
}
|
||||
|
||||
// Test invalid instance id.
|
||||
try {
|
||||
mod_lti_external::view_lti(0);
|
||||
$this->fail('Exception expected due to invalid mod_lti instance id.');
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals('invalidrecord', $e->errorcode);
|
||||
}
|
||||
/**
|
||||
* Test view_lti as a user who is not enrolled in the course.
|
||||
*/
|
||||
public function test_view_lti_no_enrolment() {
|
||||
$td = $this->setup_test_data();
|
||||
$lti = $td['lti'];
|
||||
|
||||
// Test not-enrolled user.
|
||||
$usernotenrolled = self::getDataGenerator()->create_user();
|
||||
$this->setUser($usernotenrolled);
|
||||
try {
|
||||
mod_lti_external::view_lti($this->lti->id);
|
||||
$this->fail('Exception expected due to not enrolled user.');
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals('requireloginerror', $e->errorcode);
|
||||
}
|
||||
|
||||
$this->expectException(moodle_exception::class);
|
||||
mod_lti_external::view_lti($lti->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test view_lti for a user without the mod/lti:view capability.
|
||||
*/
|
||||
public function test_view_lti_no_capability() {
|
||||
$td = $this->setup_test_data();
|
||||
$lti = $td['lti'];
|
||||
$student = $td['student'];
|
||||
$studentrole = $td['studentrole'];
|
||||
$context = $td['context'];
|
||||
|
||||
$this->setUser($student);
|
||||
|
||||
// We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
|
||||
assign_capability('mod/lti:view', CAP_PROHIBIT, $studentrole->id, $context->id);
|
||||
// Empty all the caches that may be affected by this change.
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
course_modinfo::clear_instance_cache();
|
||||
|
||||
$this->expectException(moodle_exception::class);
|
||||
mod_lti_external::view_lti($lti->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test view_lti for a user with the mod/lti:view capability in the course.
|
||||
*/
|
||||
public function test_view_lti() {
|
||||
$td = $this->setup_test_data();
|
||||
$lti = $td['lti'];
|
||||
$context = $td['context'];
|
||||
$cm = $td['cm'];
|
||||
$student = $td['student'];
|
||||
|
||||
// Test user with full capabilities.
|
||||
$this->setUser($this->student);
|
||||
$this->setUser($student);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
$result = mod_lti_external::view_lti($this->lti->id);
|
||||
$result = external_api::clean_returnvalue(mod_lti_external::view_lti_returns(), $result);
|
||||
$result = mod_lti_external::view_lti($lti->id);
|
||||
// The value of the result isn't needed but validation is.
|
||||
external_api::clean_returnvalue(mod_lti_external::view_lti_returns(), $result);
|
||||
|
||||
$events = $sink->get_events();
|
||||
$this->assertCount(1, $events);
|
||||
@@ -262,88 +323,92 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
|
||||
// Checking that the event contains the expected values.
|
||||
$this->assertInstanceOf('\mod_lti\event\course_module_viewed', $event);
|
||||
$this->assertEquals($this->context, $event->get_context());
|
||||
$moodlelti = new \moodle_url('/mod/lti/view.php', array('id' => $this->cm->id));
|
||||
$this->assertEquals($context, $event->get_context());
|
||||
$moodlelti = new moodle_url('/mod/lti/view.php', array('id' => $cm->id));
|
||||
$this->assertEquals($moodlelti, $event->get_url());
|
||||
$this->assertEventContextNotUsed($event);
|
||||
$this->assertNotEmpty($event->get_name());
|
||||
|
||||
// Test user with no capabilities.
|
||||
// We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
|
||||
assign_capability('mod/lti:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
|
||||
// Empty all the caches that may be affected by this change.
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
course_modinfo::clear_instance_cache();
|
||||
|
||||
try {
|
||||
mod_lti_external::view_lti($this->lti->id);
|
||||
$this->fail('Exception expected due to missing capability.');
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals('requireloginerror', $e->errorcode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Test create tool proxy
|
||||
/**
|
||||
* Test create_tool_proxy.
|
||||
*/
|
||||
public function test_mod_lti_create_tool_proxy() {
|
||||
$this->setAdminUser();
|
||||
$capabilities = ['AA', 'BB'];
|
||||
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), $capabilities, []);
|
||||
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
|
||||
|
||||
$this->assertEquals('Test proxy', $proxy->name);
|
||||
$this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
|
||||
$this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
|
||||
$this->assertEquals(implode("\n", $capabilities), $proxy->capabilityoffered);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test create tool proxy with duplicate url
|
||||
/**
|
||||
* Test create_tool_proxy with a duplicate url.
|
||||
*/
|
||||
public function test_mod_lti_create_tool_proxy_duplicateurl() {
|
||||
$this->expectException('moodle_exception');
|
||||
$proxy = mod_lti_external::create_tool_proxy('Test proxy 1', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
$proxy = mod_lti_external::create_tool_proxy('Test proxy 2', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
$this->setAdminUser();
|
||||
mod_lti_external::create_tool_proxy('Test proxy 1', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
|
||||
$this->expectException(moodle_exception::class);
|
||||
mod_lti_external::create_tool_proxy('Test proxy 2', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
}
|
||||
|
||||
/*
|
||||
* Test create tool proxy without sufficient capability
|
||||
/**
|
||||
* Test create_tool_proxy for a user without the required capability.
|
||||
*/
|
||||
public function test_mod_lti_create_tool_proxy_without_capability() {
|
||||
self::setUser($this->teacher);
|
||||
$this->expectException('required_capability_exception');
|
||||
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$this->setUser($teacher);
|
||||
$this->expectException(required_capability_exception::class);
|
||||
mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
}
|
||||
|
||||
/*
|
||||
* Test delete tool proxy
|
||||
/**
|
||||
* Test delete_tool_proxy.
|
||||
*/
|
||||
public function test_mod_lti_delete_tool_proxy() {
|
||||
$this->setAdminUser();
|
||||
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
|
||||
$this->assertNotEmpty(lti_get_tool_proxy($proxy->id));
|
||||
|
||||
$proxy = mod_lti_external::delete_tool_proxy($proxy->id);
|
||||
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::delete_tool_proxy_returns(), $proxy);
|
||||
|
||||
$this->assertEquals('Test proxy', $proxy->name);
|
||||
$this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
|
||||
$this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
|
||||
$this->assertEmpty(lti_get_tool_proxy($proxy->id));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test get tool proxy registration request
|
||||
/**
|
||||
* Test get_tool_proxy_registration_request.
|
||||
*/
|
||||
public function test_mod_lti_get_tool_proxy_registration_request() {
|
||||
$this->setAdminUser();
|
||||
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
|
||||
|
||||
$request = mod_lti_external::get_tool_proxy_registration_request($proxy->id);
|
||||
$request = external_api::clean_returnvalue(mod_lti_external::get_tool_proxy_registration_request_returns(),
|
||||
$request);
|
||||
|
||||
$this->assertEquals('ToolProxyRegistrationRequest', $request['lti_message_type']);
|
||||
$this->assertEquals('LTI-2p0', $request['lti_version']);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test get tool types
|
||||
/**
|
||||
* Test get_tool_types.
|
||||
*/
|
||||
public function test_mod_lti_get_tool_types() {
|
||||
// Create a tool proxy.
|
||||
$this->setAdminUser();
|
||||
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
|
||||
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
|
||||
|
||||
// Create a tool type, associated with that proxy.
|
||||
$type = new stdClass();
|
||||
@@ -353,20 +418,25 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
$type->description = "Example description";
|
||||
$type->toolproxyid = $proxy->id;
|
||||
$type->baseurl = $this->getExternalTestFileUrl('/test.html');
|
||||
$typeid = lti_add_type($type, $data);
|
||||
lti_add_type($type, $data);
|
||||
|
||||
$types = mod_lti_external::get_tool_types($proxy->id);
|
||||
$this->assertEquals(1, count($types));
|
||||
$types = external_api::clean_returnvalue(mod_lti_external::get_tool_types_returns(), $types);
|
||||
|
||||
$this->assertCount(1, $types);
|
||||
$type = $types[0];
|
||||
$this->assertEquals('Test tool', $type['name']);
|
||||
$this->assertEquals('Example description', $type['description']);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test create tool type
|
||||
/**
|
||||
* Test create_tool_type.
|
||||
*/
|
||||
public function test_mod_lti_create_tool_type() {
|
||||
$this->setAdminUser();
|
||||
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
|
||||
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
|
||||
|
||||
$this->assertEquals('Example tool', $type['name']);
|
||||
$this->assertEquals('Example tool description', $type['description']);
|
||||
$this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type['urls']['icon']);
|
||||
@@ -379,70 +449,90 @@ class mod_lti_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertTrue(isset($config['forcessl']));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test create tool type failure from non existant file
|
||||
/**
|
||||
* Test create_tool_type failure from non existent file.
|
||||
*/
|
||||
public function test_mod_lti_create_tool_type_nonexistant_file() {
|
||||
$this->expectException('moodle_exception');
|
||||
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/doesntexist.xml'), '', '');
|
||||
$this->expectException(moodle_exception::class);
|
||||
mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/doesntexist.xml'), '', '');
|
||||
}
|
||||
|
||||
/*
|
||||
* Test create tool type failure from xml that is not a cartridge
|
||||
/**
|
||||
* Test create_tool_type failure from xml that is not a cartridge.
|
||||
*/
|
||||
public function test_mod_lti_create_tool_type_bad_file() {
|
||||
$this->expectException('moodle_exception');
|
||||
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/rsstest.xml'), '', '');
|
||||
$this->expectException(moodle_exception::class);
|
||||
mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/rsstest.xml'), '', '');
|
||||
}
|
||||
|
||||
/*
|
||||
* Test creating of tool types without sufficient capability
|
||||
/**
|
||||
* Test create_tool_type as a user without the required capability.
|
||||
*/
|
||||
public function test_mod_lti_create_tool_type_without_capability() {
|
||||
self::setUser($this->teacher);
|
||||
$this->expectException('required_capability_exception');
|
||||
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$this->setUser($teacher);
|
||||
$this->expectException(required_capability_exception::class);
|
||||
mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
|
||||
}
|
||||
|
||||
/*
|
||||
* Test update tool type
|
||||
/**
|
||||
* Test update_tool_type.
|
||||
*/
|
||||
public function test_mod_lti_update_tool_type() {
|
||||
$this->setAdminUser();
|
||||
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
|
||||
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
|
||||
|
||||
$type = mod_lti_external::update_tool_type($type['id'], 'New name', 'New description', LTI_TOOL_STATE_PENDING);
|
||||
$type = external_api::clean_returnvalue(mod_lti_external::update_tool_type_returns(), $type);
|
||||
|
||||
$this->assertEquals('New name', $type['name']);
|
||||
$this->assertEquals('New description', $type['description']);
|
||||
$this->assertEquals('Pending', $type['state']['text']);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test delete tool type
|
||||
/**
|
||||
* Test delete_tool_type for a user with the required capability.
|
||||
*/
|
||||
public function test_mod_lti_delete_tool_type() {
|
||||
$this->setAdminUser();
|
||||
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
|
||||
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
|
||||
$this->assertNotEmpty(lti_get_type($type['id']));
|
||||
|
||||
$type = mod_lti_external::delete_tool_type($type['id']);
|
||||
$type = external_api::clean_returnvalue(mod_lti_external::delete_tool_type_returns(), $type);
|
||||
$this->assertEmpty(lti_get_type($type['id']));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test delete tool type without sufficient capability
|
||||
/**
|
||||
* Test delete_tool_type for a user without the required capability.
|
||||
*/
|
||||
public function test_mod_lti_delete_tool_type_without_capability() {
|
||||
$this->setAdminUser();
|
||||
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
|
||||
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
|
||||
$this->assertNotEmpty(lti_get_type($type['id']));
|
||||
$this->expectException('required_capability_exception');
|
||||
self::setUser($this->teacher);
|
||||
$type = mod_lti_external::delete_tool_type($type['id']);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$this->setUser($teacher);
|
||||
$this->expectException(required_capability_exception::class);
|
||||
mod_lti_external::delete_tool_type($type['id']);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test is cartridge
|
||||
/**
|
||||
* Test is_cartridge.
|
||||
*/
|
||||
public function test_mod_lti_is_cartridge() {
|
||||
$this->setAdminUser();
|
||||
$result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'));
|
||||
$result = external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
|
||||
$this->assertTrue($result['iscartridge']);
|
||||
|
||||
$result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/test.html'));
|
||||
$result = external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
|
||||
$this->assertFalse($result['iscartridge']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,13 @@ class behat_mod_quiz_generator extends behat_generator_base {
|
||||
protected function get_creatable_entities(): array {
|
||||
return [
|
||||
'group overrides' => [
|
||||
'singular' => 'group override',
|
||||
'datagenerator' => 'override',
|
||||
'required' => ['quiz', 'group'],
|
||||
'switchids' => ['quiz' => 'quiz', 'group' => 'groupid'],
|
||||
],
|
||||
'user overrides' => [
|
||||
'singular' => 'user override',
|
||||
'datagenerator' => 'override',
|
||||
'required' => ['quiz', 'user'],
|
||||
'switchids' => ['quiz' => 'quiz', 'user' => 'userid'],
|
||||
|
||||
@@ -63,20 +63,19 @@ class behat_workshopallocation_manual extends behat_base {
|
||||
$selectnode = $this->find('xpath', $xpathselect);
|
||||
}
|
||||
|
||||
$selectformfield = behat_field_manager::get_form_field($selectnode, $this->getSession());
|
||||
$selectformfield->set_value($reviewername);
|
||||
$this->execute('behat_forms::set_field_node_value', [
|
||||
$selectnode,
|
||||
$reviewername,
|
||||
]);
|
||||
|
||||
if (!$this->running_javascript()) {
|
||||
// Without Javascript we need to press the "Go" button.
|
||||
$go = behat_context_helper::escape(get_string('go'));
|
||||
$this->find('xpath', $xpathtd."/descendant::input[@value=$go]")->click();
|
||||
} else {
|
||||
// With Javascript we just wait for the page to reload.
|
||||
$this->getSession()->wait(behat_base::get_extended_timeout(), self::PAGE_READY_JS);
|
||||
}
|
||||
|
||||
// Check the success string to appear.
|
||||
$allocatedtext = behat_context_helper::escape(
|
||||
get_string('allocationadded', 'workshopallocation_manual'));
|
||||
$allocatedtext = behat_context_helper::escape(get_string('allocationadded', 'workshopallocation_manual'));
|
||||
$this->find('xpath', "//*[contains(.,$allocatedtext)]");
|
||||
}
|
||||
|
||||
@@ -88,8 +87,7 @@ class behat_workshopallocation_manual extends behat_base {
|
||||
* @param TableNode $table should have one column with title 'Reviewer' and another with title 'Participant' (or 'Reviewee')
|
||||
*/
|
||||
public function i_allocate_submissions_in_workshop_as($workshopname, TableNode $table) {
|
||||
|
||||
$this->find_link($workshopname)->click();
|
||||
$this->execute('behat_general::i_click_on', [$workshopname, 'link']);
|
||||
$this->execute('behat_navigation::i_navigate_to_in_current_page_administration', get_string('allocate', 'workshop'));
|
||||
$rows = $table->getRows();
|
||||
$reviewer = $participant = null;
|
||||
@@ -108,8 +106,15 @@ class behat_workshopallocation_manual extends behat_base {
|
||||
if ($participant === null) {
|
||||
throw new ElementTextException('Neither "Participant" nor "Reviewee" column could be located', $this->getSession());
|
||||
}
|
||||
|
||||
for ($i = 1; $i < count($rows); $i++) {
|
||||
$this->i_add_a_reviewer_for_workshop_participant($rows[$i][$reviewer], $rows[$i][$participant]);
|
||||
$this->execute(
|
||||
'behat_workshopallocation_manual::i_add_a_reviewer_for_workshop_participant',
|
||||
[
|
||||
$rows[$i][$reviewer],
|
||||
$rows[$i][$participant],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,7 @@ class behat_qtype_ddmarker extends behat_base {
|
||||
// DOM node so that its centre is over the centre of anothe DOM node.
|
||||
// Therefore to make it drag to the specified place, we have to add
|
||||
// a target div.
|
||||
$session = $this->getSession();
|
||||
$session->executeScript("
|
||||
$this->execute_script("
|
||||
(function() {
|
||||
if (document.getElementById('target-{$x}-{$y}')) {
|
||||
return;
|
||||
@@ -90,7 +89,8 @@ class behat_qtype_ddmarker extends behat_base {
|
||||
target.style.setProperty('top', yadjusted + 'px');
|
||||
target.style.setProperty('width', '1px');
|
||||
target.style.setProperty('height', '1px');
|
||||
}())");
|
||||
}())"
|
||||
);
|
||||
|
||||
$generalcontext = behat_context_helper::get('behat_general');
|
||||
$generalcontext->i_drag_and_i_drop_it_in($this->marker_xpath($marker, $item),
|
||||
|
||||
@@ -210,12 +210,28 @@ switch ($action) {
|
||||
case 'unzip':
|
||||
$filename = required_param('filename', PARAM_FILE);
|
||||
$filepath = required_param('filepath', PARAM_PATH);
|
||||
$areamaxbytes = required_param('areamaxbytes', PARAM_INT);
|
||||
|
||||
$return = new stdClass();
|
||||
$zipper = get_file_packer('application/zip');
|
||||
|
||||
$fs = get_file_storage();
|
||||
|
||||
$file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, $filename);
|
||||
// Get the total size of the content in the archive.
|
||||
$filecontentsize = $file->get_total_content_size($zipper);
|
||||
|
||||
// Return an error if the returned size of the content is NULL.
|
||||
// This means the utility class was unable to read the content of the archive.
|
||||
if (is_null($filecontentsize)) {
|
||||
$return->error = get_string('cannotunzipcontentunreadable', 'repository');
|
||||
die(json_encode($return));
|
||||
}
|
||||
|
||||
// Check whether the maximum size allowed in this draft area will be exceeded with unzipping the file.
|
||||
// If the maximum size allowed is exceeded, return an error before attempting to unzip.
|
||||
if (file_is_draft_area_limit_reached($draftid, $areamaxbytes, $filecontentsize)) {
|
||||
$return->error = get_string('cannotunzipquotaexceeded', 'repository');
|
||||
die(json_encode($return));
|
||||
}
|
||||
|
||||
// Find unused name for directory to extract the archive.
|
||||
$temppath = $fs->get_unused_dirname($user_context->id, 'user', 'draft', $draftid, $filepath. pathinfo($filename, PATHINFO_FILENAME). '/');
|
||||
@@ -243,7 +259,6 @@ switch ($action) {
|
||||
$donotremovedirs[] = $realpath;
|
||||
}
|
||||
}
|
||||
$return = new stdClass();
|
||||
$return->filepath = $filepath;
|
||||
} else {
|
||||
$return = false;
|
||||
|
||||
@@ -51,7 +51,7 @@ class behat_search extends behat_base {
|
||||
$this->execute('behat_forms::i_set_the_field_to', ['q', $query]);
|
||||
|
||||
// Submit the form.
|
||||
$this->getSession()->executeScript('document.querySelector(".search-input-form.expanded").submit();');
|
||||
$this->execute_script('return document.querySelector(".search-input-form.expanded").submit();');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -211,7 +211,7 @@ print('<form class="tag-filter-form" method="get" action="'.$CFG->wwwroot.'/tag/
|
||||
print('<div class="tag-management-form generalbox"><label class="accesshide" for="id_tagfilter">'. get_string('search') .'</label>'.
|
||||
'<input type="hidden" name="tc" value="'.$tagcollid.'" />'.
|
||||
'<input type="hidden" name="perpage" value="'.$perpage.'" />'.
|
||||
'<input id="id_tagfilter" name="filter" type="text" value=' . s($filter) . '>'.
|
||||
'<input id="id_tagfilter" name="filter" type="text" value="' . s($filter) . '">'.
|
||||
'<input value="'. s(get_string('search')) .'" type="submit" class="btn btn-secondary"> '.
|
||||
($filter !== '' ? html_writer::link(new moodle_url($PAGE->url, array('filter' => null)),
|
||||
get_string('resetfilter', 'tag'), array('class' => 'resetfilterlink')) : '').
|
||||
|
||||
+2
-2
@@ -29,11 +29,11 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2018051713.00; // 20180517 = branching date YYYYMMDD - do not modify!
|
||||
$version = 2018051714.00; // 20180517 = branching date YYYYMMDD - do not modify!
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
$release = '3.5.13 (Build: 20200713)'; // Human-friendly version name
|
||||
$release = '3.5.14 (Build: 20200914)'; // Human-friendly version name
|
||||
|
||||
$branch = '35'; // This version's branch.
|
||||
$maturity = MATURITY_STABLE; // This version's maturity level.
|
||||
|
||||
Reference in New Issue
Block a user