Compare commits
135 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 | |||
| f4d5e50005 | |||
| 63492d72ff | |||
| 6059688bb8 | |||
| e5da091061 | |||
| cab24c1838 | |||
| dde44877bc | |||
| 7db4a93901 | |||
| 5e5de409b5 | |||
| a5d8388b33 | |||
| f74f0c5c10 | |||
| 84d08b1651 | |||
| 71433c086b | |||
| a449ad1ae0 | |||
| e3f5f18813 | |||
| d2a670ec68 | |||
| 75baf79c47 | |||
| a8265dc500 | |||
| 999f67d464 | |||
| 7ff7e29b85 | |||
| c847b6d725 | |||
| cd227ab8ec | |||
| 476c26b2db | |||
| 1ac49204f8 | |||
| 53dde4e6a8 | |||
| 261c5a765f | |||
| 56ff846bbb | |||
| 71fda60d39 | |||
| 62064c3c86 | |||
| 9c4055943e | |||
| 2dc0582f5e | |||
| a8dd761083 | |||
| 8c82cc367c | |||
| 7556fc7c1d | |||
| 0c595030cc | |||
| 47ee1ee0d8 | |||
| 3c36b02b17 | |||
| 220732372a | |||
| 9691a7b331 | |||
| 6bd1059042 | |||
| a6e3b62bbb | |||
| 0788bf7d62 | |||
| 04658e6111 | |||
| 6604114571 | |||
| 2d553d9a11 | |||
| 64ff6f4e12 | |||
| d6efe5c428 | |||
| 9a01bf5dac | |||
| 2ad7c452fa | |||
| dabe4c6a8a | |||
| 68b632949d | |||
| 24ec2b70d4 | |||
| c85a85e1d6 | |||
| 47890d6b75 | |||
| 184523e238 | |||
| 61e908f935 | |||
| 3f86f7028c | |||
| 269569f55a | |||
| 0e253a518f | |||
| b10488d134 | |||
| e4f5c4127b | |||
| cf15e751c2 | |||
| 492e9c9d54 | |||
| 094c79b550 | |||
| c2707068a0 | |||
| e260a4f35e | |||
| 2b2f40f423 | |||
| 7961c01b98 | |||
| 98b0636874 | |||
| 4b28673d1f | |||
| d349c4d0da | |||
| 06544c7721 | |||
| 0255dcd2f4 | |||
| 1e1576a246 | |||
| 8e8c8d86cc |
+19
@@ -13,6 +13,9 @@ language: php
|
||||
|
||||
dist: trusty
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
php:
|
||||
# We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
|
||||
- 7.2
|
||||
@@ -72,6 +75,9 @@ cache:
|
||||
- $HOME/.composer/cache
|
||||
- $HOME/.npm
|
||||
|
||||
before_install:
|
||||
- docker run -d -p 127.0.0.1:8080:80 --name exttests moodlehq/moodle-exttests
|
||||
|
||||
install:
|
||||
- >
|
||||
if [ "$DB" = 'mysqli' ];
|
||||
@@ -181,6 +187,11 @@ before_script:
|
||||
-e "/require_once/i \\\$CFG->phpunit_dataroot = '\/home\/travis\/roots\/phpunit';" \
|
||||
-e "/require_once/i \\\$CFG->phpunit_prefix = 'p_';" \
|
||||
config.php ;
|
||||
# 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.
|
||||
php admin/tool/phpunit/cli/init.php
|
||||
@@ -287,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')));
|
||||
|
||||
@@ -164,6 +164,8 @@ if ($options['install']) {
|
||||
mtrace("Acceptance tests site installed");
|
||||
}
|
||||
|
||||
// Note: Do not build the themes here. This is done during the 'enable' stage.
|
||||
|
||||
} else if ($options['drop']) {
|
||||
// Ensure no tests are running.
|
||||
test_lock::acquire('behat');
|
||||
@@ -182,6 +184,10 @@ if ($options['install']) {
|
||||
// Enable test mode.
|
||||
behat_util::start_test_mode($options['add-core-features-to-theme'], $options['optimize-runs'], $parallel, $run);
|
||||
|
||||
// Themes are only built in the 'enable' command.
|
||||
behat_util::build_themes();
|
||||
mtrace("Testing environment themes built");
|
||||
|
||||
// This is only displayed once for parallel install.
|
||||
if (empty($run)) {
|
||||
// Notify user that 2.5 profile has been converted to 3.5.
|
||||
|
||||
@@ -276,5 +276,28 @@ function xmldb_tool_dataprivacy_upgrade($oldversion) {
|
||||
upgrade_plugin_savepoint(true, 2018051415, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2018051416) {
|
||||
|
||||
// Define field commentsformat to be added to tool_dataprivacy_request.
|
||||
$table = new xmldb_table('tool_dataprivacy_request');
|
||||
$field = new xmldb_field('commentsformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'comments');
|
||||
|
||||
// Conditionally launch add field commentsformat.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field dpocommentformat to be added to tool_dataprivacy_request.
|
||||
$field = new xmldb_field('dpocommentformat', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'dpocomment');
|
||||
|
||||
// Conditionally launch add field dpocommentformat.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2018051416, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$plugin->version = 2018051415;
|
||||
$plugin->version = 2018051416;
|
||||
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
|
||||
$plugin->component = 'tool_dataprivacy';
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["core/yui"],function(a){var b=function(b,c,d,e,f){this.yuiDialogue=null;var g=this;"undefined"==typeof f&&(f=!1),a.use("moodle-core-notification","timers",function(){var h="480px";f&&(h="800px"),g.yuiDialogue=new M.core.dialogue({headerContent:b,bodyContent:c,draggable:!0,visible:!1,center:!0,modal:!0,width:h}),g.yuiDialogue.after("visibleChange",function(b){b.newVal?"undefined"!=typeof d&&a.soon(function(){d(g),g.yuiDialogue.centerDialogue()}):"undefined"!=typeof e&&a.soon(function(){e(g)})}),g.yuiDialogue.show()})};return b.prototype.close=function(){this.yuiDialogue.hide(),this.yuiDialogue.destroy()},b.prototype.getContent=function(){return this.yuiDialogue.bodyNode.getDOMNode()},b});
|
||||
define(["core/yui"],function(a){var b=function(b,c,d,e,f){M.util.js_pending("tool_lp/dialogue:dialogue"),this.yuiDialogue=null;var g=this;"undefined"==typeof f&&(f=!1),a.use("moodle-core-notification","timers",function(){var h="480px";f&&(h="800px"),g.yuiDialogue=new M.core.dialogue({headerContent:b,bodyContent:c,draggable:!0,visible:!1,center:!0,modal:!0,width:h}),g.yuiDialogue.before("visibleChange",function(){M.util.js_pending("tool_lp/dialogue:before:visibleChange")}),g.yuiDialogue.after("visibleChange",function(b){b.newVal?"undefined"!=typeof d?a.soon(function(){d(g),g.yuiDialogue.centerDialogue(),M.util.js_complete("tool_lp/dialogue:before:visibleChange")}):M.util.js_complete("tool_lp/dialogue:before:visibleChange"):"undefined"!=typeof e?a.soon(function(){e(g),M.util.js_complete("tool_lp/dialogue:before:visibleChange")}):M.util.js_complete("tool_lp/dialogue:before:visibleChange")}),g.yuiDialogue.show(),M.util.js_complete("tool_lp/dialogue:dialogue")})};return b.prototype.close=function(){this.yuiDialogue.hide(),this.yuiDialogue.destroy()},b.prototype.getContent=function(){return this.yuiDialogue.bodyNode.getDOMNode()},b});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/event_base","core/str"],function(a,b,c,d,e,f){var g=function(a){e.prototype.constructor.apply(this,[]),this._ratingOptions=a};return g.prototype=Object.create(e.prototype),g.prototype._popup=null,g.prototype._ratingOptions=null,g.prototype._afterRender=function(){var b=this._find('[data-action="rate"]'),c=this._find('[name="rating"]'),d=this._find('[name="comment"]');this._find('[data-action="cancel"]').click(function(a){a.preventDefault(),this._trigger("cancelled"),this.close()}.bind(this)),c.change(function(){var c=a(this);c.val()?b.prop("disabled",!1):b.prop("disabled",!0)}).change(),b.click(function(a){a.preventDefault();var b=c.val();b&&(this._trigger("rated",{rating:b,note:d.val()}),this.close())}.bind(this))},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){return a.when(f.get_string("rate","tool_lp"),this._render()).then(function(a,b){return this._popup=new d(a,b[0],this._afterRender.bind(this)),this._popup}.bind(this))["catch"](b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._render=function(){var a={cangrade:this._canGrade,ratings:this._ratingOptions};return c.render("tool_lp/competency_grader",a)},g});
|
||||
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/event_base","core/str"],function(a,b,c,d,e,f){var g=function(a){e.prototype.constructor.apply(this,[]),this._ratingOptions=a};return g.prototype=Object.create(e.prototype),g.prototype._popup=null,g.prototype._ratingOptions=null,g.prototype._afterRender=function(){var b=this._find('[data-action="rate"]'),c=this._find('[name="rating"]'),d=this._find('[name="comment"]');this._find('[data-action="cancel"]').click(function(a){a.preventDefault(),this._trigger("cancelled"),this.close()}.bind(this)),c.change(function(){var c=a(this);c.val()?b.prop("disabled",!1):b.prop("disabled",!0)}).change(),b.click(function(a){a.preventDefault();var b=c.val();b&&(this._trigger("rated",{rating:b,note:d.val()}),this.close())}.bind(this))},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){return M.util.js_pending("tool_lp/grade_dialogue:display"),a.when(f.get_string("rate","tool_lp"),this._render()).then(function(a,b){return this._popup=new d(a,b[0],function(){this._afterRender(),M.util.js_complete("tool_lp/grade_dialogue:display")}.bind(this)),this._popup}.bind(this))["catch"](b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._render=function(){var a={cangrade:this._canGrade,ratings:this._ratingOptions};return c.render("tool_lp/competency_grader",a)},g});
|
||||
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/ajax","core/log","tool_lp/grade_dialogue","tool_lp/event_base","tool_lp/scalevalues"],function(a,b,c,d,e,f,g){var h=function(b,c,d,e,g,h,i){f.prototype.constructor.apply(this,[]);var j=a(b);if(!j.length)throw new Error("Could not find the trigger");this._scaleId=c,this._competencyId=d,this._userId=e,this._planId=g,this._courseId=h,this._chooseStr=i,this._setUp(),j.click(function(a){a.preventDefault(),this._dialogue.display()}.bind(this)),this._planId?(this._methodName="core_competency_grade_competency_in_plan",this._args={competencyid:this._competencyId,planid:this._planId}):this._courseId?(this._methodName="core_competency_grade_competency_in_course",this._args={competencyid:this._competencyId,courseid:this._courseId,userid:this._userId}):(this._methodName="core_competency_grade_competency",this._args={userid:this._userId,competencyid:this._competencyId})};return h.prototype=Object.create(f.prototype),h.prototype._setUp=function(){var a=[],d=this,f=g.get_values(d._scaleId);f.then(function(b){a.push({value:"",name:d._chooseStr});for(var c=0;c<b.length;c++){var e=b[c];a.push({value:e.id,name:e.name})}return a}).then(function(a){return new e(a)}).then(function(a){return a.on("rated",function(a,e){var f=d._args;f.grade=e.rating,f.note=e.note,c.call([{methodname:d._methodName,args:f,done:function(a){d._trigger("competencyupdated",{args:f,evidence:a})},fail:b.exception}])}),a}).then(function(a){d._dialogue=a}).fail(b.exception)},h.prototype._scaleId=null,h.prototype._competencyId=null,h.prototype._userId=null,h.prototype._planId=null,h.prototype._courseId=null,h.prototype._chooseStr=null,h.prototype._dialogue=null,h});
|
||||
define(["jquery","core/notification","core/ajax","core/log","tool_lp/grade_dialogue","tool_lp/event_base","tool_lp/scalevalues"],function(a,b,c,d,e,f,g){var h=function(b,c,d,e,g,h,i){f.prototype.constructor.apply(this,[]);var j=a(b);if(!j.length)throw new Error("Could not find the trigger");this._scaleId=c,this._competencyId=d,this._userId=e,this._planId=g,this._courseId=h,this._chooseStr=i,this._setUp(),j.click(function(a){a.preventDefault(),this._dialogue.display()}.bind(this)),this._planId?(this._methodName="core_competency_grade_competency_in_plan",this._args={competencyid:this._competencyId,planid:this._planId}):this._courseId?(this._methodName="core_competency_grade_competency_in_course",this._args={competencyid:this._competencyId,courseid:this._courseId,userid:this._userId}):(this._methodName="core_competency_grade_competency",this._args={userid:this._userId,competencyid:this._competencyId})};return h.prototype=Object.create(f.prototype),h.prototype._setUp=function(){var a=[],d=this;M.util.js_pending("tool_lp/grade_user_competency_inline:_setUp");var f=g.get_values(d._scaleId);f.then(function(b){a.push({value:"",name:d._chooseStr});for(var c=0;c<b.length;c++){var e=b[c];a.push({value:e.id,name:e.name})}return a}).then(function(a){return new e(a)}).then(function(a){return a.on("rated",function(a,e){var f=d._args;f.grade=e.rating,f.note=e.note,c.call([{methodname:d._methodName,args:f,done:function(a){d._trigger("competencyupdated",{args:f,evidence:a})},fail:b.exception}])}),a}).then(function(a){d._dialogue=a,M.util.js_complete("tool_lp/grade_user_competency_inline:_setUp")}).fail(b.exception)},h.prototype._scaleId=null,h.prototype._competencyId=null,h.prototype._userId=null,h.prototype._planId=null,h.prototype._courseId=null,h.prototype._chooseStr=null,h.prototype._dialogue=null,h});
|
||||
@@ -35,6 +35,8 @@ define(['core/yui'], function(Y) {
|
||||
* @param {Boolean} wide Specify we want an extra wide dialogue (the size is standard, but wider than the default).
|
||||
*/
|
||||
var dialogue = function(title, content, afterShow, afterHide, wide) {
|
||||
M.util.js_pending('tool_lp/dialogue:dialogue');
|
||||
|
||||
this.yuiDialogue = null;
|
||||
var parent = this;
|
||||
|
||||
@@ -59,6 +61,10 @@ define(['core/yui'], function(Y) {
|
||||
width: width
|
||||
});
|
||||
|
||||
parent.yuiDialogue.before('visibleChange', function() {
|
||||
M.util.js_pending('tool_lp/dialogue:before:visibleChange');
|
||||
});
|
||||
|
||||
parent.yuiDialogue.after('visibleChange', function(e) {
|
||||
if (e.newVal) {
|
||||
// Delay the callback call to the next tick, otherwise it can happen that it is
|
||||
@@ -67,18 +73,25 @@ define(['core/yui'], function(Y) {
|
||||
Y.soon(function() {
|
||||
afterShow(parent);
|
||||
parent.yuiDialogue.centerDialogue();
|
||||
M.util.js_complete('tool_lp/dialogue:before:visibleChange');
|
||||
});
|
||||
} else {
|
||||
M.util.js_complete('tool_lp/dialogue:before:visibleChange');
|
||||
}
|
||||
} else {
|
||||
if ((typeof afterHide !== 'undefined')) {
|
||||
Y.soon(function() {
|
||||
afterHide(parent);
|
||||
M.util.js_complete('tool_lp/dialogue:before:visibleChange');
|
||||
});
|
||||
} else {
|
||||
M.util.js_complete('tool_lp/dialogue:before:visibleChange');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
parent.yuiDialogue.show();
|
||||
M.util.js_complete('tool_lp/dialogue:dialogue');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ define(['jquery',
|
||||
* @return {Promise}
|
||||
*/
|
||||
Grade.prototype.display = function() {
|
||||
M.util.js_pending('tool_lp/grade_dialogue:display');
|
||||
return $.when(
|
||||
Str.get_string('rate', 'tool_lp'),
|
||||
this._render()
|
||||
@@ -110,7 +111,10 @@ define(['jquery',
|
||||
this._popup = new Dialogue(
|
||||
title,
|
||||
templateResult[0],
|
||||
this._afterRender.bind(this)
|
||||
function() {
|
||||
this._afterRender();
|
||||
M.util.js_complete('tool_lp/grade_dialogue:display');
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
return this._popup;
|
||||
|
||||
@@ -94,6 +94,7 @@ define(['jquery',
|
||||
var options = [],
|
||||
self = this;
|
||||
|
||||
M.util.js_pending('tool_lp/grade_user_competency_inline:_setUp');
|
||||
var promise = ScaleValues.get_values(self._scaleId);
|
||||
promise.then(function(scalevalues) {
|
||||
options.push({
|
||||
@@ -134,6 +135,7 @@ define(['jquery',
|
||||
.then(function(dialogue) {
|
||||
self._dialogue = dialogue;
|
||||
|
||||
M.util.js_complete('tool_lp/grade_user_competency_inline:_setUp');
|
||||
return;
|
||||
})
|
||||
.fail(notification.exception);
|
||||
|
||||
@@ -45,18 +45,20 @@
|
||||
}
|
||||
|
||||
}}
|
||||
<div class="modal" data-role="flexitour-step">
|
||||
<div class="modal" data-region="modal-container" data-role="flexitour-step">
|
||||
<div class="modal" data-region="modal">
|
||||
<div data-role="arrow"></div>
|
||||
<div class="modal-header">
|
||||
<div class="modal-header" data-region="header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" data-role="end">×</button>
|
||||
<h3 data-placeholder="title"></h3>
|
||||
<h3 data-region="title" data-placeholder="title"></h3>
|
||||
</div>
|
||||
<div class="modal-body" data-placeholder="body"></div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-body" data-region="body" data-placeholder="body"></div>
|
||||
<div class="modal-footer" data-region="footer">
|
||||
<div class="btn-group">
|
||||
<button href="#" class="btn" data-role="previous">{{# str }} previous, moodle {{/ str }}</button>
|
||||
<button href="#" class="btn btn-primary" data-role="next">{{# str }} next, moodle {{/ str }}</button>
|
||||
</div>
|
||||
<button class="btn" data-role="end"> {{# str }} endtour, tool_usertours {{/ str }} </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 |
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core_calendar/selectors","core_calendar/events","core/str","core/templates"],function(a,b,c,d,e){var f=function(d){d.on("click",b.eventFilterItem,function(b){var c=a(b.currentTarget);g(c),b.preventDefault()}),a("body").on(c.viewUpdated,function(){var c=d.find(b.eventFilterItem);c.each(function(b,c){if(c=a(c),c.data("eventtype-hidden")){var d=i(c);h(d)}})})},g=function(a){var b=i(a);return b.hidden=!b.hidden,d.get_string("eventtype"+b.eventtype,"calendar").then(function(a){return b.name=a,b}).then(function(a){return e.render("core_calendar/event_filter_key",a)}).then(function(b,c){return e.replaceNode(a,b,c)}).then(function(){h(b)})},h=function(b){M.util.js_pending("month-mini-filterChanged"),a("body").trigger(c.filterChanged,{type:b.eventtype,hidden:b.hidden}),M.util.js_complete("month-mini-filterChanged")},i=function(a){return{eventtype:a.data("eventtype"),hidden:a.data("eventtype-hidden")}};return{init:function(b){b=a(b),f(b)}}});
|
||||
define(["jquery","core_calendar/selectors","core_calendar/events","core/str","core/templates"],function(a,b,c,d,e){var f=function(d){d.on("click",b.eventFilterItem,function(b){var c=a(b.currentTarget);g(c),b.preventDefault()}),a("body").on(c.viewUpdated,function(){var c=d.find(b.eventFilterItem);c.each(function(b,c){if(c=a(c),c.data("eventtype-hidden")){var d=i(c);h(d)}})})},g=function(a){var b=i(a);return b.hidden=!b.hidden,M.util.js_pending("core_calendar/calendar_filter:toggleFilter"),d.get_string("eventtype"+b.eventtype,"calendar").then(function(a){return b.name=a,b}).then(function(a){return e.render("core_calendar/event_filter_key",a)}).then(function(b,c){return e.replaceNode(a,b,c)}).then(function(){h(b),M.util.js_complete("core_calendar/calendar_filter:toggleFilter")})},h=function(b){M.util.js_pending("month-mini-filterChanged"),a("body").trigger(c.filterChanged,{type:b.eventtype,hidden:b.hidden}),M.util.js_complete("month-mini-filterChanged")},i=function(a){return{eventtype:a.data("eventtype"),hidden:a.data("eventtype-hidden")}};return{init:function(b){b=a(b),f(b)}}});
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){function o(d,e,f){var i=[{key:"deleteevent",component:"calendar"}];f=parseInt(f,10);var m,o=f>1;o?(i.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:e,count:f}}),m=g.create({type:l.TYPE})):(i.push({key:"confirmeventdelete",component:"calendar",param:e}),m=g.create({type:g.types.SAVE_CANCEL}));var p=b.get_strings(i),q=a.when(p,m).then(function(b,e){return e.setTitle(b[0]),e.setBody(b[1]),o||e.setSaveButtonText(b[0]),e.show(),e.getRoot().on(h.save,function(){var b=new n("calendar/crud:initModal:deletedevent");j.deleteEvent(d,!1).then(function(){a("body").trigger(k.deleted,[d,!1])}).then(b.resolve)["catch"](c.exception)}),e.getRoot().on(k.deleteAll,function(){var b=new n("calendar/crud:initModal:deletedallevent");j.deleteEvent(d,!0).then(function(){a("body").trigger(k.deleted,[d,!0])}).then(b.resolve)["catch"](c.exception)}),e})["catch"](c.exception);return q}function p(b){b.on("click",m.actions.remove,function(b){var c=a(this).closest(m.eventItem),d=c.data("eventId"),e=c.data("eventTitle"),f=c.data("eventCount");o(d,e,f),b.preventDefault()})}function q(b,d){return d.then(function(c){a("body").on(k.editEvent,function(a,d){var e=b.find(m.wrapper);c.setEventId(d),c.setContextId(e.data("contextId")),c.show(),a.stopImmediatePropagation()})}).fail(c.exception),d}var r=function(b){var d=g.create({type:i.TYPE,large:!0});return b.on("click",m.actions.create,function(a){d.then(function(a){var c=b.find(m.wrapper),d=c.data("categoryid");"undefined"!=typeof d&&a.setCategoryId(d);var e=b.find(m.today),f=b.find(m.day);!e.length&&f.length&&a.setStartTime(f.data("newEventTimestamp")),a.setContextId(c.data("contextId")),a.setCourseId(c.data("courseid")),a.show()}).fail(c.exception),a.preventDefault()}),b.on("click",m.actions.edit,function(b){b.preventDefault();var e=a(b.currentTarget),f=e.closest(m.wrapper),g=e.closest(m.eventItem);d.then(function(a){a.setEventId(g.data("eventId")),a.setContextId(f.data("contextId")),a.show(),b.stopImmediatePropagation()}).fail(c.exception)}),d};return{registerRemove:p,registerEditListeners:q,registerEventFormModal:r}});
|
||||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){function o(d,e,f){var i=[{key:"deleteevent",component:"calendar"}];f=parseInt(f,10);var m,o=f>1;o?(i.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:e,count:f}}),m=g.create({type:l.TYPE})):(i.push({key:"confirmeventdelete",component:"calendar",param:e}),m=g.create({type:g.types.SAVE_CANCEL}));var p=b.get_strings(i),q=a.when(p,m).then(function(b,e){return e.setTitle(b[0]),e.setBody(b[1]),o||e.setSaveButtonText(b[0]),e.show(),e.getRoot().on(h.save,function(){var b=new n("calendar/crud:initModal:deletedevent");j.deleteEvent(d,!1).then(function(){a("body").trigger(k.deleted,[d,!1])}).then(b.resolve)["catch"](c.exception)}),e.getRoot().on(k.deleteAll,function(){var b=new n("calendar/crud:initModal:deletedallevent");j.deleteEvent(d,!0).then(function(){a("body").trigger(k.deleted,[d,!0])}).then(b.resolve)["catch"](c.exception)}),e})["catch"](c.exception);return q}function p(b){b.on("click",m.actions.remove,function(b){var c=a(this).closest(m.eventItem),d=c.data("eventId"),e=c.data("eventTitle"),f=c.data("eventCount");o(d,e,f),b.preventDefault()})}function q(b,d){var e=new n("core_calendar/crud:registerEditListeners");return d.then(function(c){return a("body").on(k.editEvent,function(a,d){var e=b.find(m.wrapper);c.setEventId(d),c.setContextId(e.data("contextId")),c.show(),a.stopImmediatePropagation()}),c}).then(function(a){return e.resolve(),a})["catch"](c.exception)}var r=function(b){var d=g.create({type:i.TYPE,large:!0});return b.on("click",m.actions.create,function(a){d.then(function(a){var c=b.find(m.wrapper),d=c.data("categoryid");"undefined"!=typeof d&&a.setCategoryId(d);var e=b.find(m.today),f=b.find(m.day);!e.length&&f.length&&a.setStartTime(f.data("newEventTimestamp")),a.setContextId(c.data("contextId")),a.setCourseId(c.data("courseid")),a.show()}).fail(c.exception),a.preventDefault()}),b.on("click",m.actions.edit,function(b){b.preventDefault();var e=a(b.currentTarget),f=e.closest(m.wrapper),g=e.closest(m.eventItem);d.then(function(a){a.setEventId(g.data("eventId")),a.setContextId(f.data("contextId")),a.show(),b.stopImmediatePropagation()}).fail(c.exception)}),d};return{registerRemove:p,registerEditListeners:q,registerEventFormModal:r}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/repository","core_calendar/events","core_calendar/crud"],function(a,b,c,d,e,f,g,h,i,j,k){var l=!1,m={ROOT:"[data-region='summary-modal-container']",EDIT_BUTTON:'[data-action="edit"]',DELETE_BUTTON:'[data-action="delete"]'},n=function(a){e.call(this,a)};return n.TYPE="core_calendar-event_summary",n.prototype=Object.create(e.prototype),n.prototype.constructor=n,n.prototype.getEditButton=function(){return"undefined"==typeof this.editButton&&(this.editButton=this.getFooter().find(m.EDIT_BUTTON)),this.editButton},n.prototype.getDeleteButton=function(){return"undefined"==typeof this.deleteButton&&(this.deleteButton=this.getFooter().find(m.DELETE_BUTTON)),this.deleteButton},n.prototype.getEventId=function(){return this.getBody().find(m.ROOT).attr("data-event-id")},n.prototype.getEventTitle=function(){return this.getBody().find(m.ROOT).attr("data-event-title")},n.prototype.getEventCount=function(){return this.getBody().find(m.ROOT).attr("data-event-count")},n.prototype.getEditUrl=function(){return this.getBody().find(m.ROOT).attr("data-edit-url")},n.prototype.isActionEvent=function(){return"true"==this.getBody().find(m.ROOT).attr("data-action-event")},n.prototype.registerEventListeners=function(){e.prototype.registerEventListeners.call(this),this.getRoot().on(h.bodyRendered,function(){this.getModal().data({eventTitle:this.getEventTitle(),eventId:this.getEventId(),eventCount:this.getEventCount()}).attr("data-type","event"),k.registerRemove(this.getModal())}.bind(this)),a("body").on(j.deleted,function(){this.hide()}.bind(this)),d.define(this.getEditButton(),[d.events.activate]),this.getEditButton().on(d.events.activate,function(b,c){this.isActionEvent()?a("body").trigger(j.editActionEvent,[this.getEditUrl()]):a("body").trigger(j.editEvent,[this.getEventId()]),this.hide(),b.preventDefault(),b.stopPropagation(),c.originalEvent.preventDefault(),c.originalEvent.stopPropagation()}.bind(this))},l||(f.register(n.TYPE,n,"core_calendar/event_summary_modal"),l=!0),n});
|
||||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/repository","core_calendar/events","core_calendar/crud"],function(a,b,c,d,e,f,g,h,i,j,k){var l=!1,m={ROOT:"[data-region='summary-modal-container']",EDIT_BUTTON:'[data-action="edit"]',DELETE_BUTTON:'[data-action="delete"]'},n=function(a){e.call(this,a)};return n.TYPE="core_calendar-event_summary",n.prototype=Object.create(e.prototype),n.prototype.constructor=n,n.prototype.getEditButton=function(){return"undefined"==typeof this.editButton&&(this.editButton=this.getFooter().find(m.EDIT_BUTTON)),this.editButton},n.prototype.getDeleteButton=function(){return"undefined"==typeof this.deleteButton&&(this.deleteButton=this.getFooter().find(m.DELETE_BUTTON)),this.deleteButton},n.prototype.getEventId=function(){return this.getBody().find(m.ROOT).attr("data-event-id")},n.prototype.getEventTitle=function(){return this.getBody().find(m.ROOT).attr("data-event-title")},n.prototype.getEventCount=function(){return this.getBody().find(m.ROOT).attr("data-event-count")},n.prototype.getEditUrl=function(){return this.getBody().find(m.ROOT).attr("data-edit-url")},n.prototype.isActionEvent=function(){return"true"==this.getBody().find(m.ROOT).attr("data-action-event")},n.prototype.registerEventListeners=function(){e.prototype.registerEventListeners.call(this),M.util.js_pending("core_calendar/summary_modal:registerEventListeners:bodyRendered"),this.getRoot().on(h.bodyRendered,function(){this.getModal().data({eventTitle:this.getEventTitle(),eventId:this.getEventId(),eventCount:this.getEventCount()}).attr("data-type","event"),k.registerRemove(this.getModal()),M.util.js_complete("core_calendar/summary_modal:registerEventListeners:bodyRendered")}.bind(this)),a("body").on(j.deleted,function(){this.hide()}.bind(this)),d.define(this.getEditButton(),[d.events.activate]),this.getEditButton().on(d.events.activate,function(b,c){this.isActionEvent()?a("body").trigger(j.editActionEvent,[this.getEditUrl()]):a("body").trigger(j.editEvent,[this.getEventId()]),this.hide(),b.preventDefault(),b.stopPropagation(),c.originalEvent.preventDefault(),c.originalEvent.stopPropagation()}.bind(this))},l||(f.register(n.TYPE,n,"core_calendar/event_summary_modal"),l=!0),n});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/templates","core/str","core/notification","core_calendar/repository","core_calendar/events","core_calendar/selectors","core/modal_factory","core/modal_events","core_calendar/summary_modal"],function(a,b,c,d,e,f,g,h,i,j){var k=function(b){b=a(b),b.on("click",g.links.eventLink,function(b){var c,d=a(b.target),e=null;c=d.is(g.actions.viewEvent)?d:d.closest(g.actions.viewEvent),e=c.length?c.data("eventId"):d.find(g.actions.viewEvent).data("eventId"),e&&(b.preventDefault(),b.stopPropagation(),w(e))}),b.on("click",g.links.navLink,function(c){var d=b.find(g.wrapper),e=d.data("view"),f=d.data("courseid"),h=d.data("categoryid"),i=a(c.currentTarget);"month"===e?(m(b,i.attr("href"),i.data("year"),i.data("month"),f,h),c.preventDefault()):"day"===e&&(q(b,i.attr("href"),i.data("year"),i.data("month"),i.data("day"),f,h),c.preventDefault())})},l=function(c,h,i,j,k,l){r(c),l=l||c.find(g.wrapper),M.util.js_pending([c.get("id"),h,i,j].join("-"));var m=c.data("includenavigation"),n=c.data("mini");return e.getCalendarMonthData(h,i,j,k,m,n).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(l,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return M.util.js_complete([c.get("id"),h,i,j].join("-")),s(c)}).fail(d.exception)},m=function(b,c,d,e,g,h){return l(b,d,e,g,h).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(f.monthChanged,[d,e,g,h]),arguments})},n=function(a,b,c){var d=a.find(g.wrapper).data("year"),e=a.find(g.wrapper).data("month");return"undefined"==typeof b&&(b=a.find(g.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(g.wrapper).data("categoryid")),l(a,d,e,b,c)},o=function(c,h,i,j,k,l,m){r(c),m=m||c.find(g.wrapper),M.util.js_pending([c.get("id"),h,i,j,k,l].join("-"));var n=c.data("includenavigation");return e.getCalendarDayData(h,i,j,k,l,n).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(m,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return M.util.js_complete([c.get("id"),h,i,j,k,l].join("-")),s(c)}).fail(d.exception)},p=function(a,b,c){var d=a.find(g.wrapper),e=d.data("year"),f=d.data("month"),h=d.data("day");return b||(b=a.find(g.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(g.wrapper).data("categoryid")),o(a,e,f,h,b,c)},q=function(b,c,d,e,g,h,i){return o(b,d,e,g,h,i).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(f.dayChanged,[d,e,g,h,i]),arguments})},r=function(a){var b=a.find(g.containers.loadingIcon);b.removeClass("hidden")},s=function(a){var b=a.find(g.containers.loadingIcon);b.addClass("hidden")},t=function(c,h,i){r(c);var j=c.find(g.wrapper);return"undefined"==typeof h&&(h=c.find(g.wrapper).data("courseid")),"undefined"==typeof i&&(i=c.find(g.wrapper).data("categoryid")),e.getCalendarUpcomingData(h,i).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(j,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return s(c)}).fail(d.exception)},u=function(a){switch(a){case"user":return"user";case"site":return"site";case"group":return"group";case"category":return"category";default:return"course"}},v=function(a){return"calendar_event_"+u(a)},w=function(a){var c="";e.getEventById(a).then(function(b){if(!b.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+a);var d=b.event;return c=v(d.eventtype),x(d.eventtype).then(function(a){return d.eventtype=a,d})}).then(function(a){var d={title:a.name,type:j.TYPE,body:b.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete,headerclasses:c,isactionevent:a.isactionevent,url:a.url}};return h.create(d)}).done(function(a){a.getRoot().on(i.hidden,function(){a.destroy()}),a.show()}).fail(d.exception)},x=function(a){var b="type"+u(a);return c.get_string(b,"core_calendar").then(function(a){return a})};return{init:function(a){k(a)},reloadCurrentMonth:n,changeMonth:m,refreshMonthContent:l,reloadCurrentDay:p,changeDay:q,refreshDayContent:o,reloadCurrentUpcoming:t}});
|
||||
define(["jquery","core/templates","core/str","core/notification","core_calendar/repository","core_calendar/events","core_calendar/selectors","core/modal_factory","core/modal_events","core_calendar/summary_modal","core/pending"],function(a,b,c,d,e,f,g,h,i,j,k){var l=function(b){b=a(b),b.on("click",g.links.eventLink,function(b){var c,d=new k("core_calendar/view_manager:eventLink:click"),e=a(b.target),f=null;c=e.is(g.actions.viewEvent)?e:e.closest(g.actions.viewEvent),f=c.length?c.data("eventId"):e.find(g.actions.viewEvent).data("eventId"),f?(b.preventDefault(),b.stopPropagation(),x(f).then(d.resolve())["catch"]()):d.resolve()}),b.on("click",g.links.navLink,function(c){var d=b.find(g.wrapper),e=d.data("view"),f=d.data("courseid"),h=d.data("categoryid"),i=a(c.currentTarget);"month"===e?(n(b,i.attr("href"),i.data("year"),i.data("month"),f,h),c.preventDefault()):"day"===e&&(r(b,i.attr("href"),i.data("year"),i.data("month"),i.data("day"),f,h),c.preventDefault())})},m=function(c,h,i,j,k,l){s(c),l=l||c.find(g.wrapper),M.util.js_pending([c.get("id"),h,i,j].join("-"));var m=c.data("includenavigation"),n=c.data("mini");return e.getCalendarMonthData(h,i,j,k,m,n).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(l,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return M.util.js_complete([c.get("id"),h,i,j].join("-")),t(c)}).fail(d.exception)},n=function(b,c,d,e,g,h){return m(b,d,e,g,h).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(f.monthChanged,[d,e,g,h]),arguments})},o=function(a,b,c){var d=a.find(g.wrapper).data("year"),e=a.find(g.wrapper).data("month");return"undefined"==typeof b&&(b=a.find(g.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(g.wrapper).data("categoryid")),m(a,d,e,b,c)},p=function(c,h,i,j,k,l,m){s(c),m=m||c.find(g.wrapper),M.util.js_pending([c.get("id"),h,i,j,k,l].join("-"));var n=c.data("includenavigation");return e.getCalendarDayData(h,i,j,k,l,n).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(m,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return M.util.js_complete([c.get("id"),h,i,j,k,l].join("-")),t(c)}).fail(d.exception)},q=function(a,b,c){var d=a.find(g.wrapper),e=d.data("year"),f=d.data("month"),h=d.data("day");return b||(b=a.find(g.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(g.wrapper).data("categoryid")),p(a,e,f,h,b,c)},r=function(b,c,d,e,g,h,i){return p(b,d,e,g,h,i).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(f.dayChanged,[d,e,g,h,i]),arguments})},s=function(a){var b=a.find(g.containers.loadingIcon);b.removeClass("hidden")},t=function(a){var b=a.find(g.containers.loadingIcon);b.addClass("hidden")},u=function(c,h,i){s(c);var j=c.find(g.wrapper);return"undefined"==typeof h&&(h=c.find(g.wrapper).data("courseid")),"undefined"==typeof i&&(i=c.find(g.wrapper).data("categoryid")),e.getCalendarUpcomingData(h,i).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(j,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return t(c)}).fail(d.exception)},v=function(a){switch(a){case"user":return"user";case"site":return"site";case"group":return"group";case"category":return"category";default:return"course"}},w=function(a){return"calendar_event_"+v(a)},x=function(a){var c=new k("core_calendar/view_manager:renderEventSummaryModal"),f="";return e.getEventById(a).then(function(b){if(!b.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+a);var c=b.event;return f=w(c.eventtype),y(c.eventtype).then(function(a){return c.eventtype=a,c})}).then(function(a){var c={title:a.name,type:j.TYPE,body:b.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete,headerclasses:f,isactionevent:a.isactionevent,url:a.url}};return h.create(c)}).then(function(a){return a.getRoot().on(i.hidden,function(){a.destroy()}),a.show(),a}).then(function(a){return c.resolve(),a})["catch"](d.exception)},y=function(a){var b="type"+v(a);return c.get_string(b,"core_calendar").then(function(a){return a})};return{init:function(a){l(a)},reloadCurrentMonth:o,changeMonth:n,refreshMonthContent:m,reloadCurrentDay:q,changeDay:r,refreshDayContent:p,reloadCurrentUpcoming:u}});
|
||||
@@ -64,6 +64,7 @@ function(
|
||||
// Toggle the hidden. We need to render the template before we change the value.
|
||||
data.hidden = !data.hidden;
|
||||
|
||||
M.util.js_pending("core_calendar/calendar_filter:toggleFilter");
|
||||
return Str.get_string('eventtype' + data.eventtype, 'calendar')
|
||||
.then(function(nameStr) {
|
||||
data.name = nameStr;
|
||||
@@ -78,6 +79,7 @@ function(
|
||||
})
|
||||
.then(function() {
|
||||
fireFilterChangedEvent(data);
|
||||
M.util.js_complete("core_calendar/calendar_filter:toggleFilter");
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -234,7 +234,9 @@ function(
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function registerEditListeners(root, eventFormModalPromise) {
|
||||
eventFormModalPromise
|
||||
var pendingPromise = new Pending('core_calendar/crud:registerEditListeners');
|
||||
|
||||
return eventFormModalPromise
|
||||
.then(function(modal) {
|
||||
// When something within the calendar tells us the user wants
|
||||
// to edit an event then show the event form modal.
|
||||
@@ -246,11 +248,14 @@ function(
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
return;
|
||||
return modal;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
.then(function(modal) {
|
||||
pendingPromise.resolve();
|
||||
|
||||
return eventFormModalPromise;
|
||||
return modal;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -165,6 +165,7 @@ function(
|
||||
|
||||
// We have to wait for the modal to finish rendering in order to ensure that
|
||||
// the data-event-title property is available to use as the modal title.
|
||||
M.util.js_pending('core_calendar/summary_modal:registerEventListeners:bodyRendered');
|
||||
this.getRoot().on(ModalEvents.bodyRendered, function() {
|
||||
this.getModal().data({
|
||||
eventTitle: this.getEventTitle(),
|
||||
@@ -173,7 +174,7 @@ function(
|
||||
})
|
||||
.attr('data-type', 'event');
|
||||
CalendarCrud.registerRemove(this.getModal());
|
||||
|
||||
M.util.js_complete('core_calendar/summary_modal:registerEventListeners:bodyRendered');
|
||||
}.bind(this));
|
||||
|
||||
$('body').on(CalendarEvents.deleted, function() {
|
||||
|
||||
@@ -32,6 +32,7 @@ define([
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core_calendar/summary_modal',
|
||||
'core/pending',
|
||||
], function(
|
||||
$,
|
||||
Templates,
|
||||
@@ -42,7 +43,8 @@ define([
|
||||
CalendarSelectors,
|
||||
ModalFactory,
|
||||
ModalEvents,
|
||||
SummaryModal
|
||||
SummaryModal,
|
||||
Pending
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -55,6 +57,7 @@ define([
|
||||
|
||||
// Bind click events to event links.
|
||||
root.on('click', CalendarSelectors.links.eventLink, function(e) {
|
||||
var pendingPromise = new Pending('core_calendar/view_manager:eventLink:click');
|
||||
var target = $(e.target);
|
||||
var eventId = null;
|
||||
|
||||
@@ -79,7 +82,11 @@ define([
|
||||
// and causing the day click handler to fire.
|
||||
e.stopPropagation();
|
||||
|
||||
renderEventSummaryModal(eventId);
|
||||
renderEventSummaryModal(eventId)
|
||||
.then(pendingPromise.resolve())
|
||||
.catch();
|
||||
} else {
|
||||
pendingPromise.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -375,12 +382,14 @@ define([
|
||||
* Render the event summary modal.
|
||||
*
|
||||
* @param {Number} eventId The calendar event id.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
var renderEventSummaryModal = function(eventId) {
|
||||
var pendingPromise = new Pending('core_calendar/view_manager:renderEventSummaryModal');
|
||||
var typeClass = '';
|
||||
|
||||
// Calendar repository promise.
|
||||
CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
|
||||
return CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
|
||||
if (!getEventResponse.event) {
|
||||
throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
|
||||
}
|
||||
@@ -409,7 +418,8 @@ define([
|
||||
// Create the modal.
|
||||
return ModalFactory.create(modalParams);
|
||||
|
||||
}).done(function(modal) {
|
||||
})
|
||||
.then(function(modal) {
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
@@ -419,7 +429,14 @@ define([
|
||||
// Finally, render the modal!
|
||||
modal.show();
|
||||
|
||||
}).fail(Notification.exception);
|
||||
return modal;
|
||||
})
|
||||
.then(function(modal) {
|
||||
pendingPromise.resolve();
|
||||
|
||||
return modal;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,13 +777,6 @@ $CFG->admin = 'admin';
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// You can force the browser session (not user's sessions) to restart after N seconds. This could
|
||||
// be useful if you are using a cloud-based service with time restrictions in the browser side.
|
||||
// Setting this value the browser session that Behat is using will be restarted. Set the time in
|
||||
// seconds. Is not recommended to use this setting if you don't explicitly need it.
|
||||
// Example:
|
||||
// $CFG->behat_restart_browser_after = 7200; // Restarts the browser session after 2 hours
|
||||
//
|
||||
// All this page's extra Moodle settings are compared against a white list of allowed settings
|
||||
// (the basic and behat_* ones) to avoid problems with production environments. This setting can be
|
||||
// used to expand the default white list with an array of extra settings.
|
||||
|
||||
@@ -97,6 +97,13 @@ switch ($action) {
|
||||
|
||||
if (empty($roleid)) {
|
||||
$roleid = null;
|
||||
} else {
|
||||
if (!has_capability('moodle/role:assign', $context)) {
|
||||
throw new enrol_ajax_exception('assignnotpermitted');
|
||||
}
|
||||
if (!array_key_exists($roleid, get_assignable_roles($context, ROLENAME_ALIAS, false))) {
|
||||
throw new enrol_ajax_exception('invalidrole');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($startdate)) {
|
||||
|
||||
@@ -195,7 +195,7 @@ class behat_grade extends behat_base {
|
||||
$inputxpath = "//input[@class='idnumber'][" .
|
||||
"parent::li[@class='item'][text()='" . $gradeitem . "']" .
|
||||
" | " .
|
||||
"parent::li[@class='categoryitem' | @class='courseitem']" .
|
||||
"parent::li[@class='categoryitem' or @class='courseitem']" .
|
||||
"/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
|
||||
"]";
|
||||
$this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
|
||||
|
||||
@@ -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)';
|
||||
|
||||
@@ -48,6 +48,6 @@ $string['invalidmd5'] = 'Kontrolerako aldagaia gaizki zegoen - saiatu berriz ere
|
||||
$string['missingrequiredfield'] = 'Beharrezko eremuren bat falta da.';
|
||||
$string['remotedownloaderror'] = '<p>Errorea osagaia zure zerbitzarian jaistean, mesedez egiaztatu proxy-ezarpenak, PHP cURL hedapena erabat gomendatzen da.</p>
|
||||
<p> <a href="{$a->url}">{$a->url}</a> fitxategia eskuz jaitsi beharko zenuke, zure zerbitzariko "{$a->dest}"-ra kopiatu eta bertan deskonprimatu.</p>';
|
||||
$string['wrongdestpath'] = 'Bide desegokia';
|
||||
$string['wrongdestpath'] = 'Helmuga-bide desegokia';
|
||||
$string['wrongsourcebase'] = 'URL iturriaren oinarri akastuna';
|
||||
$string['wrongzipfilename'] = 'ZIP fitxategiaren izen desegokia';
|
||||
|
||||
@@ -45,7 +45,7 @@ $string['datarootpermission'] = 'Datu-direktorioen baimena';
|
||||
$string['dbprefix'] = 'Taulen aurrizkia';
|
||||
$string['dirroot'] = 'Moodle direktorioa';
|
||||
$string['environmenthead'] = 'Zure ingurunea egiaztatzen...';
|
||||
$string['environmentsub2'] = 'Moodleko bertsio bakoitzak PHPko gutxieneko bertsioa eta derrigorrez instalatu beharreko PHP hedapen batzuk ditu. Ingurunearen azterketa oso bat egiten da instalazioa eta eguneraketa bakoitza egin aurretik. Mesedez, jarri harremanetan zerbitzariaren kudeatzailearekin ez badakizu bertsio berria edo PHP hedapenak nola instalatu.';
|
||||
$string['environmentsub2'] = 'Moodleko bertsio bakoitzak PHPko gutxieneko bertsioa eta derrigorrez instalatu beharreko PHP hedapen batzuk ditu. Ingurunearen azterketa oso bat egiten da instalazioa eta eguneratze bakoitza egin aurretik. Mesedez, jarri harremanetan zerbitzariaren kudeatzailearekin ez badakizu bertsio berria edo PHP hedapenak nola instalatu.';
|
||||
$string['errorsinenvironment'] = 'Huts egin du ingurunearen egiaztatzeak!';
|
||||
$string['installation'] = 'Instalazioa';
|
||||
$string['langdownloaderror'] = 'Zoritxarrez "{$a}" hizkuntza ezin izan da jaitsi. Instalazio-prozesuak ingelesez jarraituko du.';
|
||||
|
||||
@@ -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'] = 'no';
|
||||
$string['thislanguage'] = 'Kvääni';
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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'] = 'w';
|
||||
$string['cliyesnoprompt'] = 'Tape w (pou wi) oswa n (pou non)';
|
||||
@@ -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';
|
||||
@@ -31,6 +31,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = '언어';
|
||||
$string['moodlelogo'] = '무들 로고';
|
||||
$string['next'] = '다음';
|
||||
$string['previous'] = '이전으로';
|
||||
$string['reload'] = '다시 로딩';
|
||||
|
||||
@@ -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'] = 'Mirandés';
|
||||
@@ -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'] = 'بارگیری مجدد';
|
||||
@@ -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['moodlelogo'] = 'د موډل نښان';
|
||||
$string['next'] = 'بل';
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatelangdir'] = 'Ii sáhte ráhkadit máhpa \'lang\'.';
|
||||
$string['cannotcreatetempdir'] = 'Ii sáhttán ráhkadit máhpa \'temp\\';
|
||||
$string['cannotsavemd5file'] = 'Ii sáhte vurket md5-fiilla.';
|
||||
$string['cannotsavezipfile'] = 'Ii sáhte vurket ZIP-fiilla.';
|
||||
$string['wrongzipfilename'] = 'Boasttu ZIP-fiilanamma.';
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['admindirname'] = 'Adminmáhppa';
|
||||
$string['availablelangs'] = 'Giellapáhkat mat leat gávdnamis';
|
||||
$string['chooselanguagehead'] = 'Vállje giela';
|
||||
$string['chooselanguagesub'] = 'Vállje giela (dušše installašuvdnjii). Don sáhtát válljet neahttabáikki ja geavaheaddji giela šearbmagovas maŋŋelaš.';
|
||||
@@ -38,3 +39,4 @@ $string['clialreadyinstalled'] = 'Fiila config.php gávdno jo. Geavat admin/cli/
|
||||
$string['databasename'] = 'Diehtovuođđonamma';
|
||||
$string['langdownloaderror'] = 'Dađibahábut ii installerejuvvon giellapáhkka "{$a}". Installašuvdnaproseassa joatká eaŋgalsgillii.';
|
||||
$string['phpextension'] = '{$a} PHP gilkor';
|
||||
$string['welcomep70'] = 'Coahkkal "Boahtte"-boalu joatkit<strong>Moodle</strong>-installašuvnnain';
|
||||
|
||||
@@ -42,3 +42,6 @@ $string['cannotsavemd5file'] = 'Md5 ఫైల్ను సేవ్ చేయల
|
||||
$string['cannotsavezipfile'] = 'జిప్ ఫైల్ను సేవ్ చేయడం సాధ్యపడదు';
|
||||
$string['cannotunzipfile'] = 'ఫైల్ అన్జిప్ చేయలేరు';
|
||||
$string['componentisuptodate'] = 'కాంపొనెంటు తాజాగా ఉంది';
|
||||
$string['dmlexceptiononinstall'] = '<p>A డేటాబేస్ లోపం ఏర్పడింది [{$ a-> errorcode}]. <br /> {$a->debuginfo} </p>';
|
||||
$string['downloadedfilecheckfailed'] = 'డౌన్లోడ్ చేసిన ఫైల్ తనిఖీ విఫలమైంది';
|
||||
$string['invalidmd5'] = 'చెక్ వేరియబుల్ తప్పు. - మళ్ళీ ప్రయత్నించండి';
|
||||
|
||||
@@ -31,5 +31,5 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = '';
|
||||
$string['thisdirection'] = '符号(ltr)';
|
||||
$string['thisdirection'] = 'ltr';
|
||||
$string['thislanguage'] = '简体中文';
|
||||
|
||||
@@ -32,6 +32,6 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = '语言';
|
||||
$string['moodlelogo'] = 'Moodle图标';
|
||||
$string['next'] = '向后';
|
||||
$string['previous'] = '向前';
|
||||
$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.
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+4
-3
@@ -241,12 +241,13 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
|
||||
e.preventDefault(); // This will prevent default error dialogue.
|
||||
str.get_strings([
|
||||
{key: 'confirm', component: 'core'},
|
||||
{key: 'nameuseddocombine', component: 'tag'},
|
||||
{key: 'yes'},
|
||||
{key: 'cancel'},
|
||||
{key: 'yes', component: 'core'},
|
||||
{key: 'cancel', component: 'core'},
|
||||
])
|
||||
.then(function(s) {
|
||||
return notification.confirm(e.message, s[0], s[1], s[2], function() {
|
||||
return notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
window.location.href = window.location.href + "&newname=" + encodeURIComponent(newvalue) +
|
||||
"&tagid=" + encodeURIComponent(tagid) +
|
||||
'&action=renamecombine&sesskey=' + M.cfg.sesskey;
|
||||
|
||||
+133
-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.
|
||||
@@ -107,6 +111,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
|
||||
if ($selector === 'NodeElement' && is_a($locator, NodeElement::class)) {
|
||||
// Support a NodeElement being passed in for use in step chaining.
|
||||
return $locator;
|
||||
}
|
||||
|
||||
// Returns the first match.
|
||||
$items = $this->find_all($selector, $locator, $exception, $node, $timeout);
|
||||
return count($items) ? reset($items) : null;
|
||||
@@ -463,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');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -692,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'];
|
||||
}
|
||||
@@ -735,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?
|
||||
@@ -1132,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])) {
|
||||
|
||||
@@ -154,7 +154,7 @@ XPATH
|
||||
.//div[
|
||||
contains(concat(' ', normalize-space(@class), ' '), ' modal ')
|
||||
and
|
||||
normalize-space(descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' modal-header ')] = %locator%)
|
||||
normalize-space(descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' modal-header ')]) = %locator%
|
||||
]
|
||||
XPATH
|
||||
, 'icon' => <<<XPATH
|
||||
|
||||
@@ -32,6 +32,7 @@ require_once(__DIR__ . '/behat_config_manager.php');
|
||||
|
||||
require_once(__DIR__ . '/../../filelib.php');
|
||||
require_once(__DIR__ . '/../../clilib.php');
|
||||
require_once(__DIR__ . '/../../csslib.php');
|
||||
|
||||
use Behat\Mink\Session;
|
||||
use Behat\Mink\Exception\ExpectationException;
|
||||
@@ -131,6 +132,35 @@ class behat_util extends testing_util {
|
||||
self::store_database_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build theme CSS.
|
||||
*/
|
||||
public static function build_themes() {
|
||||
global $CFG;
|
||||
require_once("{$CFG->libdir}/outputlib.php");
|
||||
|
||||
$themenames = array_keys(\core_component::get_plugin_list('theme'));
|
||||
|
||||
// Load the theme configs.
|
||||
$themeconfigs = array_map(function($themename) {
|
||||
return \theme_config::load($themename);
|
||||
}, $themenames);
|
||||
|
||||
// Build the list of themes and cache them in local cache.
|
||||
$themes = theme_build_css_for_themes($themeconfigs, ['ltr'], true);
|
||||
|
||||
$framework = self::get_framework();
|
||||
$storageroot = self::get_dataroot() . "/{$framework}/themedata";
|
||||
|
||||
foreach ($themes as $themename => $themedata) {
|
||||
$dirname = "{$storageroot}/{$themename}";
|
||||
check_dir_exists($dirname);
|
||||
foreach ($themedata as $direction => $css) {
|
||||
file_put_contents("{$dirname}/{$direction}.css", $css);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops dataroot and remove test database tables
|
||||
* @throws coding_exception
|
||||
@@ -378,6 +408,37 @@ class behat_util extends testing_util {
|
||||
initialise_cfg();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore theme CSS stored during behat setup.
|
||||
*/
|
||||
public static function restore_saved_themes() {
|
||||
global $CFG;
|
||||
|
||||
$themerev = theme_get_revision();
|
||||
|
||||
$framework = self::get_framework();
|
||||
$storageroot = self::get_dataroot() . "/{$framework}/themedata";
|
||||
$themenames = array_keys(\core_component::get_plugin_list('theme'));
|
||||
$directions = ['ltr', 'rtl'];
|
||||
|
||||
$themeconfigs = array_map(function($themename) {
|
||||
return \theme_config::load($themename);
|
||||
}, $themenames);
|
||||
|
||||
foreach ($themeconfigs as $themeconfig) {
|
||||
$themename = $themeconfig->name;
|
||||
$themesubrev = theme_get_sub_revision_for_theme($themename);
|
||||
|
||||
$dirname = "{$storageroot}/{$themename}";
|
||||
foreach ($directions as $direction) {
|
||||
$cssfile = "{$dirname}/{$direction}.css";
|
||||
if (file_exists($cssfile)) {
|
||||
$themeconfig->set_css_content_cache(file_get_contents($cssfile));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause execution immediately.
|
||||
*
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -132,6 +132,8 @@ function cron_run_inner_scheduled_task(\core\task\task_base $task) {
|
||||
}
|
||||
\core\task\manager::scheduled_task_failed($task);
|
||||
} finally {
|
||||
// Reset back to the standard admin user.
|
||||
cron_setup_user();
|
||||
cron_prepare_core_renderer(true);
|
||||
}
|
||||
get_mailer('close');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+4
-3
@@ -179,12 +179,13 @@ function theme_get_css_filename($themename, $globalrevision, $themerevision, $di
|
||||
* @param theme_config[] $themeconfigs An array of theme_config instances.
|
||||
* @param array $directions Must be a subset of ['rtl', 'ltr'].
|
||||
* @param bool $cache Should the generated files be stored in local cache.
|
||||
* @return array The built theme content in a multi-dimensional array of name => direction => content
|
||||
*/
|
||||
function theme_build_css_for_themes($themeconfigs = [], $directions = ['rtl', 'ltr'], $cache = true) {
|
||||
function theme_build_css_for_themes($themeconfigs = [], $directions = ['rtl', 'ltr'], $cache = true): array {
|
||||
global $CFG;
|
||||
|
||||
if (empty($themeconfigs)) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
require_once("{$CFG->libdir}/csslib.php");
|
||||
@@ -212,7 +213,7 @@ function theme_build_css_for_themes($themeconfigs = [], $directions = ['rtl', 'l
|
||||
css_store_css($themeconfig, $filename, $themecss[$direction]);
|
||||
}
|
||||
}
|
||||
$themescss[] = $themecss;
|
||||
$themescss[$themeconfig->name] = $themecss;
|
||||
|
||||
if ($cache) {
|
||||
// Only update the theme revision after we've successfully created the
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
+202
-115
@@ -63,16 +63,14 @@ use Behat\Testwork\Hook\Scope\BeforeSuiteScope,
|
||||
*/
|
||||
class behat_hooks extends behat_base {
|
||||
|
||||
/**
|
||||
* @var Last browser session start time.
|
||||
*/
|
||||
protected static $lastbrowsersessionstart = 0;
|
||||
|
||||
/**
|
||||
* @var For actions that should only run once.
|
||||
*/
|
||||
protected static $initprocessesfinished = false;
|
||||
|
||||
/** @var bool Whether the first javascript scenario has been seen yet */
|
||||
protected static $firstjavascriptscenarioseen = false;
|
||||
|
||||
/**
|
||||
* @var bool Scenario running
|
||||
*/
|
||||
@@ -88,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.
|
||||
*
|
||||
@@ -113,36 +122,17 @@ class behat_hooks extends behat_base {
|
||||
* Hook to capture BeforeSuite event so as to give access to moodle codebase.
|
||||
* This will try and catch any exception and exists if anything fails.
|
||||
*
|
||||
* @param BeforeSuiteScope $scope scope passed by event fired before suite.
|
||||
* @BeforeSuite
|
||||
* @param BeforeSuiteScope $scope scope passed by event fired before suite.
|
||||
*/
|
||||
public static function before_suite_hook(BeforeSuiteScope $scope) {
|
||||
global $CFG;
|
||||
|
||||
// If behat has been initialised then no need to do this again.
|
||||
if (self::$initprocessesfinished) {
|
||||
if (!self::is_first_scenario()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
self::before_suite($scope);
|
||||
} catch (behat_stop_exception $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives access to moodle codebase, ensures all is ready and sets up the test lock.
|
||||
*
|
||||
* Includes config.php to use moodle codebase with $CFG->behat_*
|
||||
* instead of $CFG->prefix and $CFG->dataroot, called once per suite.
|
||||
*
|
||||
* @param BeforeSuiteScope $scope scope passed by event fired before suite.
|
||||
* @static
|
||||
* @throws behat_stop_exception
|
||||
*/
|
||||
public static function before_suite(BeforeSuiteScope $scope) {
|
||||
global $CFG;
|
||||
|
||||
// Defined only when the behat CLI command is running, the moodle init setup process will
|
||||
// read this value and switch to $CFG->behat_dataroot and $CFG->behat_prefix instead of
|
||||
// the normal site.
|
||||
@@ -170,8 +160,7 @@ class behat_hooks extends behat_base {
|
||||
// before each scenario (accidental user deletes) in the BeforeScenario hook.
|
||||
|
||||
if (!behat_util::is_test_mode_enabled()) {
|
||||
throw new behat_stop_exception('Behat only can run if test mode is enabled. More info in ' .
|
||||
behat_command::DOCS_URL);
|
||||
self::log_and_stop('Behat only can run if test mode is enabled. More info in ' . behat_command::DOCS_URL);
|
||||
}
|
||||
|
||||
// Reset all data, before checking for check_server_status.
|
||||
@@ -179,26 +168,28 @@ class behat_hooks extends behat_base {
|
||||
behat_util::clean_tables_updated_by_scenario_list();
|
||||
behat_util::reset_all_data();
|
||||
|
||||
// Check if server is running and using same version for cli and apache.
|
||||
// Check if the web server is running and using same version for cli and apache.
|
||||
behat_util::check_server_status();
|
||||
|
||||
// Prevents using outdated data, upgrade script would start and tests would fail.
|
||||
if (!behat_util::is_test_data_updated()) {
|
||||
$commandpath = 'php admin/tool/behat/cli/init.php';
|
||||
throw new behat_stop_exception("Your behat test site is outdated, please run\n\n " .
|
||||
$commandpath . "\n\nfrom your moodle dirroot to drop and install the behat test site again.");
|
||||
$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.
|
||||
|
||||
{$commandpath}
|
||||
|
||||
EOF;
|
||||
self::log_and_stop($message);
|
||||
}
|
||||
|
||||
// Avoid parallel tests execution, it continues when the previous lock is released.
|
||||
test_lock::acquire('behat');
|
||||
|
||||
// Store the browser reset time if reset after N seconds is specified in config.php.
|
||||
if (!empty($CFG->behat_restart_browser_after)) {
|
||||
// Store the initial browser session opening.
|
||||
self::$lastbrowsersessionstart = time();
|
||||
}
|
||||
|
||||
if (!empty($CFG->behat_faildump_path) && !is_writable($CFG->behat_faildump_path)) {
|
||||
throw new behat_stop_exception('You set $CFG->behat_faildump_path to a non-writable directory');
|
||||
self::log_and_stop(
|
||||
"The \$CFG->behat_faildump_path value is set to a non-writable directory ({$CFG->behat_faildump_path})."
|
||||
);
|
||||
}
|
||||
|
||||
// Handle interrupts on PHP7.
|
||||
@@ -210,6 +201,25 @@ class behat_hooks extends behat_base {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run final tests before running the suite.
|
||||
*
|
||||
* @BeforeSuite
|
||||
* @param BeforeSuiteScope $scope scope passed by event fired before suite.
|
||||
*/
|
||||
public static function before_suite_final_checks(BeforeSuiteScope $scope) {
|
||||
$happy = defined('BEHAT_TEST');
|
||||
$happy = $happy && defined('BEHAT_SITE_RUNNING');
|
||||
$happy = $happy && php_sapi_name() == 'cli';
|
||||
$happy = $happy && behat_util::is_test_mode_enabled();
|
||||
$happy = $happy && behat_util::is_test_site();
|
||||
|
||||
if (!$happy) {
|
||||
error_log('Behat only can modify the test database and the test dataroot!');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives access to moodle codebase, to keep track of feature start time.
|
||||
*
|
||||
@@ -266,51 +276,123 @@ class behat_hooks extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to capture before scenario event to get scope.
|
||||
*
|
||||
* @param BeforeScenarioScope $scope scope passed by event fired before scenario.
|
||||
* @BeforeScenario
|
||||
* Helper function to restart the Mink session.
|
||||
*/
|
||||
public function before_scenario_hook(BeforeScenarioScope $scope) {
|
||||
protected function restart_session() {
|
||||
$session = $this->getSession();
|
||||
if ($session->isStarted()) {
|
||||
$session->restart();
|
||||
} else {
|
||||
$session->start();
|
||||
}
|
||||
if ($this->running_javascript() && $this->getSession()->getDriver()->getWebDriverSessionId() === 'session') {
|
||||
throw new DriverException('Unable to create a valid session');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the session before each non-javascript scenario.
|
||||
*
|
||||
* @BeforeScenario @~javascript
|
||||
* @param BeforeScenarioScope $scope scope passed by event fired before scenario.
|
||||
*/
|
||||
public function before_goutte_scenarios(BeforeScenarioScope $scope) {
|
||||
if ($this->running_javascript()) {
|
||||
// A bug in the BeforeScenario filtering prevents the @~javascript filter on this hook from working
|
||||
// properly.
|
||||
// See https://github.com/Behat/Behat/issues/1235 for further information.
|
||||
return;
|
||||
}
|
||||
|
||||
$this->restart_session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the session before the first javascript scenario.
|
||||
*
|
||||
* This is treated slightly differently to try to capture when Selenium is not running at all.
|
||||
*
|
||||
* @BeforeScenario @javascript
|
||||
* @param BeforeScenarioScope $scope scope passed by event fired before scenario.
|
||||
*/
|
||||
public function before_first_scenario_start_session(BeforeScenarioScope $scope) {
|
||||
if (!self::is_first_javascript_scenario()) {
|
||||
// The first Scenario has started.
|
||||
// The `before_subsequent_scenario_start_session` function will restart the session instead.
|
||||
return;
|
||||
}
|
||||
self::$firstjavascriptscenarioseen = true;
|
||||
|
||||
$docsurl = behat_command::DOCS_URL;
|
||||
$driverexceptionmsg = <<<EOF
|
||||
|
||||
The Selenium or WebDriver server is not running. You must start it to run tests that involve Javascript.
|
||||
See {$docsurl} for more information.
|
||||
|
||||
The following debugging information is available:
|
||||
|
||||
EOF;
|
||||
|
||||
|
||||
try {
|
||||
$this->before_scenario($scope);
|
||||
} catch (behat_stop_exception $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
$this->restart_session();
|
||||
} catch (CurlExec $e) {
|
||||
// The CurlExec Exception is thrown by WebDriver.
|
||||
self::log_and_stop(
|
||||
$driverexceptionmsg . '. ' .
|
||||
$e->getMessage() . "\n\n" .
|
||||
format_backtrace($e->getTrace(), true)
|
||||
);
|
||||
} catch (DriverException $e) {
|
||||
self::log_and_stop(
|
||||
$driverexceptionmsg . '. ' .
|
||||
$e->getMessage() . "\n\n" .
|
||||
format_backtrace($e->getTrace(), true)
|
||||
);
|
||||
} catch (UnknownError $e) {
|
||||
// Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions.
|
||||
self::log_and_stop(
|
||||
$e->getMessage() . "\n\n" .
|
||||
format_backtrace($e->getTrace(), true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the session before each javascript scenario.
|
||||
*
|
||||
* Note: Before the first scenario the @see before_first_scenario_start_session() function is used instead.
|
||||
*
|
||||
* @BeforeScenario @javascript
|
||||
* @param BeforeScenarioScope $scope scope passed by event fired before scenario.
|
||||
*/
|
||||
public function before_subsequent_scenario_start_session(BeforeScenarioScope $scope) {
|
||||
if (self::is_first_javascript_scenario()) {
|
||||
// The initial init has not yet finished.
|
||||
// The `before_first_scenario_start_session` function will have started the session instead.
|
||||
return;
|
||||
}
|
||||
self::$currentscenarioexception = null;
|
||||
|
||||
try {
|
||||
$this->restart_session();
|
||||
} catch (Exception $e) {
|
||||
self::$currentscenarioexception = $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the test environment.
|
||||
*
|
||||
* @BeforeScenario
|
||||
* @param BeforeScenarioScope $scope scope passed by event fired before scenario.
|
||||
* @throws behat_stop_exception If here we are not using the test database it should be because of a coding error
|
||||
*/
|
||||
public function before_scenario(BeforeScenarioScope $scope) {
|
||||
global $DB, $CFG;
|
||||
|
||||
// As many checks as we can.
|
||||
if (!defined('BEHAT_TEST') ||
|
||||
!defined('BEHAT_SITE_RUNNING') ||
|
||||
php_sapi_name() != 'cli' ||
|
||||
!behat_util::is_test_mode_enabled() ||
|
||||
!behat_util::is_test_site()) {
|
||||
throw new behat_stop_exception('Behat only can modify the test database and the test dataroot!');
|
||||
}
|
||||
|
||||
$moreinfo = 'More info in ' . behat_command::DOCS_URL;
|
||||
$driverexceptionmsg = 'Selenium server is not running, you need to start it to run tests that involve Javascript. ' . $moreinfo;
|
||||
try {
|
||||
$session = $this->getSession();
|
||||
} catch (CurlExec $e) {
|
||||
// Exception thrown by WebDriver, so only @javascript tests will be caugth; in
|
||||
// behat_util::check_server_status() we already checked that the server is running.
|
||||
throw new behat_stop_exception($driverexceptionmsg);
|
||||
} catch (DriverException $e) {
|
||||
throw new behat_stop_exception($driverexceptionmsg);
|
||||
} catch (UnknownError $e) {
|
||||
// Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions.
|
||||
throw new behat_stop_exception($e->getMessage());
|
||||
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();
|
||||
@@ -347,9 +429,6 @@ class behat_hooks extends behat_base {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset mink session between the scenarios.
|
||||
$session->reset();
|
||||
|
||||
// Reset $SESSION.
|
||||
\core\session\manager::init_empty_session();
|
||||
|
||||
@@ -360,19 +439,20 @@ class behat_hooks extends behat_base {
|
||||
behat_util::reset_all_data();
|
||||
error_reporting($errorlevel);
|
||||
|
||||
if ($this->running_javascript()) {
|
||||
// Fetch the user agent.
|
||||
// This isused to choose between the SVG/Non-SVG versions of themes.
|
||||
$useragent = $this->getSession()->evaluateScript('return navigator.userAgent;');
|
||||
\core_useragent::instance(true, $useragent);
|
||||
|
||||
// Restore the saved themes.
|
||||
behat_util::restore_saved_themes();
|
||||
}
|
||||
|
||||
// Assign valid data to admin user (some generator-related code needs a valid user).
|
||||
$user = $DB->get_record('user', array('username' => 'admin'));
|
||||
\core\session\manager::set_user($user);
|
||||
|
||||
// Reset the browser if specified in config.php.
|
||||
if (!empty($CFG->behat_restart_browser_after) && $this->running_javascript()) {
|
||||
$now = time();
|
||||
if (self::$lastbrowsersessionstart + $CFG->behat_restart_browser_after < $now) {
|
||||
$session->restart();
|
||||
self::$lastbrowsersessionstart = $now;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the theme if not default.
|
||||
if ($suitename !== "default") {
|
||||
set_config('theme', $suitename);
|
||||
@@ -406,7 +486,7 @@ class behat_hooks extends behat_base {
|
||||
// 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()) {
|
||||
@@ -437,6 +517,12 @@ class behat_hooks extends behat_base {
|
||||
* @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.
|
||||
@@ -528,25 +614,13 @@ class behat_hooks extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed after scenario having switch window to restart session.
|
||||
* This is needed to close all extra browser windows and starting
|
||||
* one browser window.
|
||||
* Reset the session between each scenario.
|
||||
*
|
||||
* @param AfterScenarioScope $scope scope passed by event fired after scenario.
|
||||
* @AfterScenario @_switch_window
|
||||
* @AfterScenario
|
||||
*/
|
||||
public function after_scenario_switchwindow(AfterScenarioScope $scope) {
|
||||
for ($count = 0; $count < behat_base::get_extended_timeout(); $count++) {
|
||||
try {
|
||||
$this->getSession()->restart();
|
||||
break;
|
||||
} catch (DriverException $e) {
|
||||
// Wait for timeout and try again.
|
||||
sleep(self::get_timeout());
|
||||
}
|
||||
}
|
||||
// If session is not restarted above then it will try to start session before next scenario
|
||||
// and if that fails then exception will be thrown.
|
||||
public function reset_webdriver_between_scenarios(AfterScenarioScope $scope) {
|
||||
$this->getSession()->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -665,6 +739,11 @@ class behat_hooks extends behat_base {
|
||||
* @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;
|
||||
@@ -682,6 +761,15 @@ class behat_hooks extends behat_base {
|
||||
return !(self::$initprocessesfinished);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the first scenario of the suite is running
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_first_javascript_scenario(): bool {
|
||||
return !self::$firstjavascriptscenarioseen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a set of component selectors.
|
||||
*
|
||||
@@ -725,16 +813,15 @@ class behat_hooks extends behat_base {
|
||||
self::$initprocessesfinished = true;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Log a notification, and then exit.
|
||||
*
|
||||
* @param string $message The content to dispaly
|
||||
*/
|
||||
protected static function log_and_stop(string $message) {
|
||||
error_log($message);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Behat stop exception
|
||||
*
|
||||
* This exception is thrown from before suite or scenario if any setup problem found.
|
||||
*
|
||||
* @package core_test
|
||||
* @copyright 2016 Rajesh Taneja <rajesh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_stop_exception extends \Exception {
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ class behat_navigation extends behat_base {
|
||||
// We just want to expand the node, we don't want to follow it.
|
||||
$node = $node->getParent();
|
||||
}
|
||||
$node->click();
|
||||
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +217,7 @@ class behat_navigation extends behat_base {
|
||||
// We just want to expand the node, we don't want to follow it.
|
||||
$node = $node->getParent();
|
||||
}
|
||||
$node->click();
|
||||
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +267,7 @@ class behat_navigation extends behat_base {
|
||||
// don't wait, it is non-JS and we already waited for the DOM.
|
||||
$siteadminlink = $this->getSession()->getPage()->find('named_exact', array('link', "'" . $siteadminstr . "'"));
|
||||
if ($siteadminlink) {
|
||||
$siteadminlink->click();
|
||||
$this->execute('behat_general::i_click_on', [$siteadminlink, 'NodeElement']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +325,7 @@ class behat_navigation extends behat_base {
|
||||
implode($parentnodes, ' > ') . '"', $this->getSession());
|
||||
}
|
||||
|
||||
$nodetoclick->click();
|
||||
$this->execute('behat_general::i_click_on', [$nodetoclick, 'NodeElement']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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) {
|
||||
|
||||
@@ -118,11 +118,15 @@ EOD;
|
||||
$oldproxy = $CFG->proxyhost;
|
||||
$CFG->proxyhost = 'xxxxxxxxxxxxxxx.moodle.org';
|
||||
|
||||
$oldproxybypass = $CFG->proxybypass; // Ensure we don't get locally served extests bypassing the proxy.
|
||||
$CFG->proxybypass = '';
|
||||
|
||||
$feed = new moodle_simplepie($this->getExternalTestFileUrl('/rsstest.xml'));
|
||||
|
||||
$this->assertNotEmpty($feed->error());
|
||||
$this->assertEmpty($feed->get_title());
|
||||
$CFG->proxyhost = $oldproxy;
|
||||
$CFG->proxybypass = $oldproxybypass;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
This files describes API changes in core libraries and APIs,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.5.11 ===
|
||||
* The `$CFG->behat_retart_browser_after` configuration setting has been removed.
|
||||
The browser session is now restarted between all tests.
|
||||
|
||||
=== 3.5.10 ===
|
||||
|
||||
* behat_data_generators::the_following_exist() has been removed, please use
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -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']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,8 @@ if ($mform->is_cancelled()) {
|
||||
// Now, delete the remaining records.
|
||||
if (!empty($recordstokeep)) {
|
||||
list($select, $params) = $DB->get_in_or_equal($recordstokeep, SQL_PARAMS_QM, 'param', false);
|
||||
$DB->delete_records_select('quiz_slot_tags', "id $select", $params);
|
||||
array_unshift($params, $slot->id);
|
||||
$DB->delete_records_select('quiz_slot_tags', "slotid = ? AND id $select", $params);
|
||||
} else {
|
||||
$DB->delete_records('quiz_slot_tags', array('slotid' => $slot->id));
|
||||
}
|
||||
|
||||
@@ -2461,13 +2461,13 @@ function quiz_is_overriden_calendar_event(\calendar_event $event) {
|
||||
* has one tag, and the third has zero tags. The return structure will look like:
|
||||
* [
|
||||
* 1 => [
|
||||
* { ...tag data... },
|
||||
* { ...tag data... },
|
||||
* quiz_slot_tags.id => { ...tag data... },
|
||||
* quiz_slot_tags.id => { ...tag data... },
|
||||
* ],
|
||||
* 2 => [
|
||||
* { ...tag data... }
|
||||
* quiz_slot_tags.id => { ...tag data... },
|
||||
* ],
|
||||
* 3 => []
|
||||
* 3 => [],
|
||||
* ]
|
||||
*
|
||||
* @param int[] $slotids The list of id for the quiz slots.
|
||||
@@ -2519,7 +2519,7 @@ function quiz_retrieve_tags_for_slot_ids($slotids) {
|
||||
}
|
||||
}
|
||||
|
||||
$carry[$slottag->slotid][] = $slottag;
|
||||
$carry[$slottag->slotid][$slottag->id] = $slottag;
|
||||
return $carry;
|
||||
},
|
||||
$emptytagids
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Steps definitions related to the SCORM activity module.
|
||||
*
|
||||
* @package mod_scorm
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
|
||||
|
||||
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
|
||||
|
||||
use Behat\Behat\Hook\Scope\AfterScenarioScope;
|
||||
|
||||
/**
|
||||
* Steps definitions related to the SCORM activity module.
|
||||
*
|
||||
* @package mod_scorm
|
||||
* @category test
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_mod_scorm extends behat_base {
|
||||
|
||||
/**
|
||||
* Restart the Seleium Session after each mod_scorm Scenario.
|
||||
*
|
||||
* This prevents issues with the scorm player's onbeforeunload event, and cached SCORM content being served to the
|
||||
* browser in subsequent tests.
|
||||
*
|
||||
* @AfterScenario @mod_scorm
|
||||
* @param AfterScenarioScope $scope The scenario scope
|
||||
*/
|
||||
public function reset_after_scorm(AfterScenarioScope $scope) {
|
||||
$this->getSession()->stop();
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+144
-143
@@ -701,9 +701,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
@@ -1619,9 +1619,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
|
||||
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
|
||||
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
@@ -2404,13 +2404,39 @@
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz",
|
||||
"integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==",
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^5.3.0",
|
||||
"ajv": "^6.5.5",
|
||||
"har-schema": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
||||
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
|
||||
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
|
||||
"dev": true
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"has-ansi": {
|
||||
@@ -2547,9 +2573,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"in-publish": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
|
||||
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
|
||||
"integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
|
||||
"dev": true
|
||||
},
|
||||
"indent-string": {
|
||||
@@ -2804,9 +2830,8 @@
|
||||
"dev": true
|
||||
},
|
||||
"istanbul": {
|
||||
"version": "0.1.37",
|
||||
"resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.1.37.tgz",
|
||||
"integrity": "sha1-7ZwC4q+A2JRtQG1wFNkxk17y1+A=",
|
||||
"version": "https://github.com/andrewnicols/istanbul/archive/v0.1.37_moodle-0.tar.gz",
|
||||
"integrity": "sha512-6i+2THnhvtbNwRHDyVHUy+1VBzlKqxBjpZ//Y3tw9Te0mFz3GPkk0SxvynAekD+s83kmE1mrWCwJP3mbd/e/Zg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"abbrev": "1.0.x",
|
||||
@@ -2896,12 +2921,6 @@
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz",
|
||||
"integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=",
|
||||
"dev": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2935,9 +2954,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"jshint": {
|
||||
"version": "2.10.2",
|
||||
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz",
|
||||
"integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==",
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.11.0.tgz",
|
||||
"integrity": "sha512-ooaD/hrBPhu35xXW4gn+o3SOuzht73gdBuffgJzrZBJZPGgGiiTvJEgTyxFvBO2nz0+X1G6etF8SzUODTlLY6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cli": "~1.0.0",
|
||||
@@ -3200,24 +3219,6 @@
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.assign": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
||||
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
|
||||
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
|
||||
@@ -3470,9 +3471,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz",
|
||||
"integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==",
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
|
||||
"dev": true
|
||||
},
|
||||
"natural-compare": {
|
||||
@@ -3502,9 +3503,9 @@
|
||||
}
|
||||
},
|
||||
"node-sass": {
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.3.tgz",
|
||||
"integrity": "sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww==",
|
||||
"version": "4.14.0",
|
||||
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.0.tgz",
|
||||
"integrity": "sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async-foreach": "^0.1.3",
|
||||
@@ -3514,15 +3515,13 @@
|
||||
"get-stdin": "^4.0.1",
|
||||
"glob": "^7.0.3",
|
||||
"in-publish": "^2.0.0",
|
||||
"lodash.assign": "^4.2.0",
|
||||
"lodash.clonedeep": "^4.3.2",
|
||||
"lodash.mergewith": "^4.6.0",
|
||||
"lodash": "^4.17.15",
|
||||
"meow": "^3.7.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nan": "^2.10.0",
|
||||
"nan": "^2.13.2",
|
||||
"node-gyp": "^3.8.0",
|
||||
"npmlog": "^4.0.0",
|
||||
"request": "2.87.0",
|
||||
"request": "^2.88.0",
|
||||
"sass-graph": "^2.2.4",
|
||||
"stdout-stream": "^1.4.0",
|
||||
"true-case-path": "^1.0.2"
|
||||
@@ -3530,7 +3529,7 @@
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -3551,54 +3550,38 @@
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
||||
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^5.1.0",
|
||||
"har-schema": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.87.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz",
|
||||
"integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==",
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.6.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.5",
|
||||
"extend": "~3.0.1",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.1",
|
||||
"har-validator": "~5.0.3",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.17",
|
||||
"oauth-sign": "~0.8.2",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.1",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"tough-cookie": "~2.3.3",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.1.0"
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
@@ -3609,15 +3592,6 @@
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
||||
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"punycode": "^1.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3749,14 +3723,6 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wordwrap": "~0.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"optionator": {
|
||||
@@ -3771,6 +3737,14 @@
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2",
|
||||
"wordwrap": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
@@ -3781,7 +3755,7 @@
|
||||
},
|
||||
"os-locale": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
|
||||
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -4127,9 +4101,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.1.29",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
|
||||
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
@@ -4286,9 +4260,9 @@
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"version": "2.88.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
@@ -4298,7 +4272,7 @@
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.0",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
@@ -4308,9 +4282,45 @@
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.4.3",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
||||
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
|
||||
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
|
||||
"dev": true
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.5.5",
|
||||
"har-schema": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
@@ -4565,9 +4575,8 @@
|
||||
"dev": true
|
||||
},
|
||||
"shifter": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/shifter/-/shifter-0.5.0.tgz",
|
||||
"integrity": "sha1-nyS3AptrpnACdSDu+Lq4m5L/n2g=",
|
||||
"version": "https://github.com/andrewnicols/shifter/archive/v0.5.0_moodle-0.tar.gz",
|
||||
"integrity": "sha512-D5ITAbIdqWx2leZfL2L21TfeezbTGLrgnB0JWZPPdJwRBa/Fgtdy6XvQ3mW6HtC3eC5x0vtxZedFZCWdAaFXOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-color": "*",
|
||||
@@ -4575,7 +4584,7 @@
|
||||
"cssproc": "~0.0.1",
|
||||
"gear": "~0.8.0",
|
||||
"gear-lib": "~0.8.0",
|
||||
"istanbul": "0.1.37",
|
||||
"istanbul": "https://github.com/andrewnicols/istanbul/archive/v0.1.37_moodle-0.tar.gz",
|
||||
"jshint": "~0.9.0",
|
||||
"mkdirp": "*",
|
||||
"nopt": "*",
|
||||
@@ -5354,13 +5363,13 @@
|
||||
}
|
||||
},
|
||||
"tar": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
|
||||
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
|
||||
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"block-stream": "*",
|
||||
"fstream": "^1.0.2",
|
||||
"fstream": "^1.0.12",
|
||||
"inherits": "2"
|
||||
}
|
||||
},
|
||||
@@ -5465,21 +5474,13 @@
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"psl": "^1.1.24",
|
||||
"punycode": "^1.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
|
||||
"dev": true
|
||||
}
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"trim-newlines": {
|
||||
@@ -5610,9 +5611,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"dev": true
|
||||
},
|
||||
"validate-npm-package-license": {
|
||||
@@ -5695,14 +5696,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
|
||||
"dev": true
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
||||
+4
-3
@@ -14,9 +14,10 @@
|
||||
"grunt-eslint": "20.1.0",
|
||||
"grunt-sass": "2.1.0",
|
||||
"grunt-stylelint": "0.6.0",
|
||||
"jshint": "^2.10.2",
|
||||
"jshint": "^2.11.0",
|
||||
"node-sass": "^4.14.0",
|
||||
"semver": "5.3.0",
|
||||
"shifter": "0.5.0",
|
||||
"shifter": "https://github.com/andrewnicols/shifter/archive/v0.5.0_moodle-0.tar.gz",
|
||||
"stylelint": "8.0.0",
|
||||
"stylelint-checkstyle-formatter": "0.1.0",
|
||||
"stylelint-csstree-validator": "1.1.1",
|
||||
@@ -24,6 +25,6 @@
|
||||
"xpath": "0.0.23"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9 <9"
|
||||
"node": ">=14.0.0 <15"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/str","core/ajax","core/log","core/templates","tool_lp/dialogue"],function(a,b,c,d,e,f,g){var h=function(b,c){this._regionSelector=b,this._userCompetencySelector=c,a(this._regionSelector).on("click",this._userCompetencySelector,this._handleClick.bind(this))};return h.prototype._handleClick=function(c){var f=a(c.target).closest(this._userCompetencySelector),g=a(f).data("competencyid"),h=a(f).data("courseid"),i=a(f).data("userid");e.debug("Clicked on cell: competencyId="+g+", courseId="+h+", userId="+i);var j=d.call([{methodname:"tool_lp_data_for_user_competency_summary_in_course",args:{userid:i,competencyid:g,courseid:h}},{methodname:"core_competency_user_competency_viewed_in_course",args:{userid:i,competencyid:g,courseid:h}}]);a.when.apply(a,j).then(function(a){this._contextLoaded.bind(this)(a)}.bind(this))["catch"](b.exception)},h.prototype._contextLoaded=function(a){var d=this;a.displayuser=!0,f.render("tool_lp/user_competency_summary_in_course",a).done(function(a,e){c.get_string("usercompetencysummary","report_competency").done(function(b){new g(b,a,f.runTemplateJS.bind(f,e),d._refresh.bind(d),(!0))}).fail(b.exception)}).fail(b.exception)},h.prototype._refresh=function(){var c=a(this._regionSelector),e=c.data("courseid"),f=c.data("userid");d.call([{methodname:"report_competency_data_for_report",args:{courseid:e,userid:f},done:this._pageContextLoaded.bind(this),fail:b.exception}])},h.prototype._pageContextLoaded=function(a){var c=this;f.render("report_competency/report",a).done(function(a,b){f.replaceNode(c._regionSelector,a,b)}).fail(b.exception)},h.prototype._regionSelector=null,h.prototype._userCompetencySelector=null,h});
|
||||
define(["jquery","core/notification","core/str","core/ajax","core/log","core/templates","tool_lp/dialogue"],function(a,b,c,d,e,f,g){var h=function(b,c){this._regionSelector=b,this._userCompetencySelector=c,a(this._regionSelector).on("click",this._userCompetencySelector,this._handleClick.bind(this))};return h.prototype._handleClick=function(c){var f=a(c.target).closest(this._userCompetencySelector),g=a(f).data("competencyid"),h=a(f).data("courseid"),i=a(f).data("userid");e.debug("Clicked on cell: competencyId="+g+", courseId="+h+", userId="+i);var j=d.call([{methodname:"tool_lp_data_for_user_competency_summary_in_course",args:{userid:i,competencyid:g,courseid:h}},{methodname:"core_competency_user_competency_viewed_in_course",args:{userid:i,competencyid:g,courseid:h}}]);a.when(j[0],j[1]).then(this._contextLoaded.bind(this))["catch"](b.exception)},h.prototype._contextLoaded=function(b){return b.displayuser=!0,M.util.js_pending("report_competency/grading_popup:_contextLoaded"),a.when(c.get_string("usercompetencysummary","report_competency"),f.render("tool_lp/user_competency_summary_in_course",b)).then(function(a,b){return new g(a,b[0],function(){f.runTemplateJS(b[1]),M.util.js_complete("report_competency/grading_popup:_contextLoaded")},this._refresh.bind(this),(!0))}.bind(this))},h.prototype._refresh=function(){var c=a(this._regionSelector),e=c.data("courseid"),f=c.data("userid");return d.call([{methodname:"report_competency_data_for_report",args:{courseid:e,userid:f},done:this._pageContextLoaded.bind(this),fail:b.exception}])},h.prototype._pageContextLoaded=function(a){f.render("report_competency/report",a).then(function(a,b){f.replaceNode(this._regionSelector,a,b)}.bind(this))["catch"](b.exception)},h.prototype._regionSelector=null,h.prototype._userCompetencySelector=null,h});
|
||||
@@ -1 +1 @@
|
||||
define(["jquery"],function(a){var b=function(b,c,d,e){this._baseUrl=c,this._userId=d+"",this._courseId=e,a(b).on("change",this._userChanged.bind(this))};return b.prototype._userChanged=function(b){var c=a(b.target).val(),d="?user="+c+"&id="+this._courseId;document.location=this._baseUrl+d},b.prototype._userId=null,b.prototype._courseId=null,b.prototype._baseUrl=null,b});
|
||||
define(["jquery"],function(a){var b=function(b,c,d,e){this._baseUrl=c,this._userId=d+"",this._courseId=e,a(b).on("change",this._userChanged.bind(this))};return b.prototype._userChanged=function(b){M.util.js_pending("report_competency/user_course_navigation:_userChanged");var c=a(b.target).val(),d="?user="+c+"&id="+this._courseId;document.location=this._baseUrl+d},b.prototype._userId=null,b.prototype._courseId=null,b.prototype._baseUrl=null,b});
|
||||
@@ -59,10 +59,9 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'cor
|
||||
args: {userid: userId, competencyid: competencyId, courseid: courseId},
|
||||
}]);
|
||||
|
||||
$.when.apply($, requests).then(function(context) {
|
||||
this._contextLoaded.bind(this)(context);
|
||||
return;
|
||||
}.bind(this)).catch(notification.exception);
|
||||
$.when(requests[0], requests[1])
|
||||
.then(this._contextLoaded.bind(this))
|
||||
.catch(notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -70,29 +69,44 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'cor
|
||||
*
|
||||
* @method _contextLoaded
|
||||
* @param {Object} context
|
||||
* @returns {Promise}
|
||||
*/
|
||||
GradingPopup.prototype._contextLoaded = function(context) {
|
||||
var self = this;
|
||||
// We have to display user info in popup.
|
||||
context.displayuser = true;
|
||||
templates.render('tool_lp/user_competency_summary_in_course', context).done(function(html, js) {
|
||||
str.get_string('usercompetencysummary', 'report_competency').done(function(title) {
|
||||
(new Dialogue(title, html, templates.runTemplateJS.bind(templates, js), self._refresh.bind(self), true));
|
||||
}).fail(notification.exception);
|
||||
}).fail(notification.exception);
|
||||
|
||||
M.util.js_pending('report_competency/grading_popup:_contextLoaded');
|
||||
|
||||
return $.when(
|
||||
str.get_string('usercompetencysummary', 'report_competency'),
|
||||
templates.render('tool_lp/user_competency_summary_in_course', context)
|
||||
)
|
||||
.then(function(title, templateData) {
|
||||
return new Dialogue(
|
||||
title,
|
||||
templateData[0],
|
||||
function() {
|
||||
templates.runTemplateJS(templateData[1]);
|
||||
M.util.js_complete('report_competency/grading_popup:_contextLoaded');
|
||||
},
|
||||
this._refresh.bind(this),
|
||||
true
|
||||
);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the page.
|
||||
*
|
||||
* @method _refresh
|
||||
* @returns {Promise}
|
||||
*/
|
||||
GradingPopup.prototype._refresh = function() {
|
||||
var region = $(this._regionSelector);
|
||||
var courseId = region.data('courseid');
|
||||
var userId = region.data('userid');
|
||||
|
||||
ajax.call([{
|
||||
return ajax.call([{
|
||||
methodname: 'report_competency_data_for_report',
|
||||
args: {courseid: courseId, userid: userId},
|
||||
done: this._pageContextLoaded.bind(this),
|
||||
@@ -107,10 +121,13 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'cor
|
||||
* @param {Object} context
|
||||
*/
|
||||
GradingPopup.prototype._pageContextLoaded = function(context) {
|
||||
var self = this;
|
||||
templates.render('report_competency/report', context).done(function(html, js) {
|
||||
templates.replaceNode(self._regionSelector, html, js);
|
||||
}).fail(notification.exception);
|
||||
templates.render('report_competency/report', context)
|
||||
.then(function(html, js) {
|
||||
templates.replaceNode(this._regionSelector, html, js);
|
||||
|
||||
return;
|
||||
}.bind(this))
|
||||
.catch(notification.exception);
|
||||
};
|
||||
|
||||
/** @type {String} The selector for the region with the user competencies */
|
||||
|
||||
@@ -46,6 +46,8 @@ define(['jquery'], function($) {
|
||||
* @param {Event} e the event
|
||||
*/
|
||||
UserCourseNavigation.prototype._userChanged = function(e) {
|
||||
// Note: This change causes a page reload and is intentionally not paired with a js_complete call.
|
||||
M.util.js_pending('report_competency/user_course_navigation:_userChanged');
|
||||
var newUserId = $(e.target).val();
|
||||
var queryStr = '?user=' + newUserId + '&id=' + this._courseId;
|
||||
document.location = this._baseUrl + queryStr;
|
||||
|
||||
@@ -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')) : '').
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
position: relative;
|
||||
|
||||
h3 {
|
||||
margin-top: 0px;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9929,7 +9929,7 @@ body.path-question-type .mform fieldset.hidden {
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.groupinfobox h3 {
|
||||
margin-top: 0px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.groupinfobox .left {
|
||||
padding: 10px;
|
||||
|
||||
+15
-1
@@ -38,9 +38,21 @@ if (!$parts) {
|
||||
combo_not_found();
|
||||
}
|
||||
|
||||
$etag = sha1($parts);
|
||||
$parts = trim($parts, '&');
|
||||
|
||||
// Remove any duplicate parts, since each file only needs to be loaded once (which also helps reduce total file size).
|
||||
$parts = implode('&', array_unique(explode('&', $parts)));
|
||||
|
||||
// Limit length of parts to match the YUI loader limit of 1024, to prevent loading an arbitrary number of files.
|
||||
if (strlen($parts) > 1024) {
|
||||
$parts = substr($parts, 0, 1024);
|
||||
|
||||
// If the shortened $parts has been cut off mid-way through a filename, trim back to the end of the previous filename.
|
||||
if (substr($parts, -3) !== '.js' && substr($parts, -4) !== '.css') {
|
||||
$parts = substr($parts, 0, strrpos($parts, '&'));
|
||||
}
|
||||
}
|
||||
|
||||
// find out what we are serving - only one type per request
|
||||
$content = '';
|
||||
if (substr($parts, -3) === '.js') {
|
||||
@@ -51,6 +63,8 @@ if (substr($parts, -3) === '.js') {
|
||||
combo_not_found();
|
||||
}
|
||||
|
||||
$etag = sha1($parts);
|
||||
|
||||
// if they are requesting a revision that's not -1, and they have supplied an
|
||||
// If-Modified-Since header, we can send back a 304 Not Modified since the
|
||||
// content never changes (the rev number is increased any time the content changes)
|
||||
|
||||
+2
-2
@@ -29,11 +29,11 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2018051712.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.12 (Build: 20200511)'; // 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