Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 940aa17049 | |||
| 536c360cd3 | |||
| 419173f859 | |||
| 3c31f748f1 | |||
| b0fc844ba2 | |||
| 3a692f8080 | |||
| c15e459968 | |||
| db39006c44 | |||
| 7c74cd3910 | |||
| d09e145e84 | |||
| 642ce70984 | |||
| 6e1442bfa7 | |||
| d523a66834 | |||
| d530cd12b3 | |||
| 949e0caf94 | |||
| 98c0290a13 | |||
| 35f84c0e43 | |||
| 612ba453d0 | |||
| 45e68195b9 | |||
| e9018ca76a | |||
| 8d0e2cb079 | |||
| 1cb6257bab | |||
| 3820d5d2be | |||
| 7d47ced769 | |||
| ea9d2e6050 | |||
| a01c6de010 | |||
| 020b7dcb49 | |||
| d74e8be2b5 | |||
| 28944a18db | |||
| d866e7315b | |||
| 217767bb7f | |||
| 7ac5548953 | |||
| 77d95a2ccb | |||
| 025a00781e | |||
| c7489c6d4d | |||
| fe0a150658 | |||
| bed733c86d | |||
| c2d4052ffc | |||
| eac204a06d | |||
| c270851b98 | |||
| f5e4c44183 | |||
| b4241111de | |||
| c0c1997a6d | |||
| 063d798f37 | |||
| 52d5599ff2 | |||
| e972cd3ef3 | |||
| b949f7ae45 | |||
| 95f07d4a29 | |||
| 2efa7007da | |||
| 0d31f8cf80 | |||
| 8ede6eb071 | |||
| 89ee8b5544 | |||
| 889ced5063 | |||
| bd642555ce | |||
| a014760344 | |||
| faf358bfe0 | |||
| 6e2580ca50 | |||
| 4aaf81b43d | |||
| 8e6ccd11b1 | |||
| 5d9da95fc8 | |||
| 9525024349 | |||
| 58f86af654 | |||
| 7459866078 | |||
| c122a4abc7 | |||
| 368b66502a | |||
| a9cfe079a3 | |||
| 6c7657a8f2 | |||
| 9fc24562b5 | |||
| e3bd76b13e | |||
| a470b60534 | |||
| 99fae65ea3 | |||
| 4deccac46a | |||
| 453d4d1846 | |||
| 0a4e899e01 | |||
| 10dee69379 | |||
| ed10e934d3 | |||
| 153e4369e1 | |||
| 5c9b17c5f1 | |||
| dd353c7247 | |||
| b41a56380e | |||
| f3a5e2a31a | |||
| e05d1c34cd | |||
| fd43070bbc | |||
| dd8fa044d9 | |||
| 57f962a915 | |||
| 050ab7d479 | |||
| d20bd99b07 | |||
| 8ac197d07f | |||
| 593b942437 | |||
| 25dcb6f193 | |||
| 98f33548d4 | |||
| feff218f2f | |||
| 668cba6e7e | |||
| 2bc8f72dff | |||
| 4d504d86b7 | |||
| 10e40f0366 | |||
| 53bc7655aa | |||
| 1c127290cf | |||
| dd8f4b46d4 | |||
| 920fb333c0 | |||
| 4c24a03e08 | |||
| 2c0c7e5e71 | |||
| 26a05fb33f | |||
| 6f7aea5351 | |||
| c66e551c11 | |||
| 526fbf8a47 | |||
| 64d99256ef | |||
| 8b39496a87 | |||
| 577b37cb2c | |||
| 205201f7b3 | |||
| 08c86b9e9b | |||
| 3aa4bf022a | |||
| 1f2d7d154a | |||
| 5884f9a07b |
@@ -2982,4 +2982,185 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.9" 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.6" />
|
||||
<VENDOR name="postgres" version="9.5" />
|
||||
<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>
|
||||
|
||||
@@ -98,7 +98,10 @@ $options = array(
|
||||
GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'HTTP_X_FORWARDED_FOR, REMOTE_ADDR',
|
||||
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR => 'HTTP_CLIENT, REMOTE_ADDR',
|
||||
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'), 0, $options));
|
||||
$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));
|
||||
$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')));
|
||||
$temp->add(new admin_setting_configtext('proxyhost', new lang_string('proxyhost', 'admin'), new lang_string('configproxyhost', 'admin'), '', PARAM_HOST));
|
||||
|
||||
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){function h(a){var e={methodname:"tool_dataprivacy_contact_dpo",args:{message:a}},f="success";b.call([e])[0].then(function(a){return a.result?d.get_string("requestsubmitted","tool_dataprivacy"):(f="error",a.warnings.join("<br>"))}).done(function(a){c.addNotification({message:a,type:f})}).fail(c.exception)}var i={CANCEL_REQUEST:'[data-action="cancel"]',CONTACT_DPO:'[data-action="contactdpo"]'},j=function(){this.registerEvents()};return j.prototype.registerEvents=function(){a(i.CANCEL_REQUEST).click(function(g){g.preventDefault();var h=a(this).data("requestid"),i=[{key:"cancelrequest",component:"tool_dataprivacy"},{key:"cancelrequestconfirmation",component:"tool_dataprivacy"}];d.get_strings(i).then(function(a){var d=a[0],g=a[1];return e.create({title:d,body:g,type:e.types.SAVE_CANCEL}).then(function(a){return a.setSaveButtonText(d),a.getRoot().on(f.save,function(){var a={requestid:h},d={methodname:"tool_dataprivacy_cancel_data_request",args:a};b.call([d])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(i.CONTACT_DPO).click(function(b){b.preventDefault();var i=a(this).data("replytoemail"),j=[{key:"contactdataprotectionofficer",component:"tool_dataprivacy"},{key:"send",component:"tool_dataprivacy"}],k="";d.get_strings(j).then(function(a){var b=a[0];k=a[1];var c={replytoemail:i};return e.create({title:b,body:g.render("tool_dataprivacy/contact_dpo",c),type:e.types.SAVE_CANCEL,large:!0})}).done(function(b){b.setSaveButtonText(k),b.getRoot().on(f.save,function(b){var c=a("#message").val().trim();0===c.length?(b.preventDefault(),a('[data-region="messageinput"]').addClass("has-danger notifyproblem"),a("#id_error_message").removeAttr("hidden")):h(c)}),b.getRoot().on(f.hidden,function(){b.destroy()}),b.show()}).fail(c.exception)})},{init:function(){return new j}}});
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","core/pending"],function(a,b,c,d,e,f,g,h){function i(a){var e={methodname:"tool_dataprivacy_contact_dpo",args:{message:a}},f="success";b.call([e])[0].then(function(a){return a.result?d.get_string("requestsubmitted","tool_dataprivacy"):(f="error",a.warnings.join("<br>"))}).done(function(a){c.addNotification({message:a,type:f})}).fail(c.exception)}var j={CANCEL_REQUEST:'[data-action="cancel"]',CONTACT_DPO:'[data-action="contactdpo"]'},k=function(){this.registerEvents()};return k.prototype.registerEvents=function(){a(j.CANCEL_REQUEST).click(function(g){g.preventDefault();var h=a(this).data("requestid"),i=[{key:"cancelrequest",component:"tool_dataprivacy"},{key:"cancelrequestconfirmation",component:"tool_dataprivacy"}];d.get_strings(i).then(function(a){var d=a[0],g=a[1];return e.create({title:d,body:g,type:e.types.SAVE_CANCEL}).then(function(a){return a.setSaveButtonText(d),a.getRoot().on(f.save,function(){var a={requestid:h},d={methodname:"tool_dataprivacy_cancel_data_request",args:a};b.call([d])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(j.CONTACT_DPO).click(function(b){var j=new h("dataprivacy/crud:initModal:contactdpo");b.preventDefault();var k=a(this).data("replytoemail"),l=[{key:"contactdataprotectionofficer",component:"tool_dataprivacy"},{key:"send",component:"tool_dataprivacy"}],m="";d.get_strings(l).then(function(a){var b=a[0];m=a[1];var c={replytoemail:k};return e.create({title:b,body:g.render("tool_dataprivacy/contact_dpo",c),type:e.types.SAVE_CANCEL,large:!0})}).then(function(b){b.setSaveButtonText(m),b.show(),b.getRoot().on(f.save,function(b){var c=a("#message").val().trim();0===c.length?(b.preventDefault(),a('[data-region="messageinput"]').addClass("has-danger notifyproblem"),a("#id_error_message").removeAttr("hidden")):i(c)}),b.getRoot().on(f.hidden,function(){b.destroy()})}).then(j.resolve)["catch"](c.exception)})},{init:function(){return new k}}});
|
||||
@@ -28,8 +28,9 @@ define([
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core/templates'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
|
||||
'core/templates',
|
||||
'core/pending'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Pending) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
@@ -118,6 +119,7 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
|
||||
});
|
||||
|
||||
$(ACTIONS.CONTACT_DPO).click(function(e) {
|
||||
var pendingPromise = new Pending('dataprivacy/crud:initModal:contactdpo');
|
||||
e.preventDefault();
|
||||
|
||||
var replyToEmail = $(this).data('replytoemail');
|
||||
@@ -146,9 +148,12 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
large: true
|
||||
});
|
||||
}).done(function(modal) {
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(sendButtonText);
|
||||
|
||||
// Show the modal!
|
||||
modal.show();
|
||||
|
||||
// Handle send event.
|
||||
modal.getRoot().on(ModalEvents.save, function(e) {
|
||||
var message = $('#message').val().trim();
|
||||
@@ -169,9 +174,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Show the modal!
|
||||
modal.show();
|
||||
}).fail(Notification.exception);
|
||||
return;
|
||||
}).then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -301,6 +301,11 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
// Login as a user without DPO role.
|
||||
$this->setUser($teacher);
|
||||
$this->expectException(required_capability_exception::class);
|
||||
api::approve_data_request($requestid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -131,11 +131,12 @@ if ($execute) {
|
||||
tool_phpunit_problem('Can not create configuration file');
|
||||
}
|
||||
}
|
||||
$configdir = escapeshellarg($configdir);
|
||||
// no cleanup of path - this is tricky because we can not use escapeshellarg and friends for escaping,
|
||||
// this is from admin user so PARAM_PATH must be enough
|
||||
chdir($CFG->dirroot);
|
||||
passthru("php $CFG->admin/tool/phpunit/cli/util.php --run -c $configdir $testclass $testpath", $code);
|
||||
$configdir = escapeshellarg($configdir);
|
||||
$cleanclass = escapeshellarg($testclass);
|
||||
$cleanpath = escapeshellarg($testpath);
|
||||
|
||||
passthru("php $CFG->admin/tool/phpunit/cli/util.php --run -c $configdir $cleanclass $cleanpath", $code);
|
||||
chdir($oldcwd);
|
||||
|
||||
echo '</pre>';
|
||||
@@ -146,7 +147,7 @@ if ($execute) {
|
||||
}
|
||||
|
||||
echo $OUTPUT->box_start('generalbox boxwidthwide boxaligncenter');
|
||||
echo '<form method="get" action="webrunner.php">';
|
||||
echo '<form method="post" action="webrunner.php">';
|
||||
echo '<fieldset class="invisiblefieldset">';
|
||||
echo '<label for="testpath">Test one file</label> ';
|
||||
echo '<input type="text" id="testpath" name="testpath" value="'.s($testpath).'" size="50" /> (all test cases from webrunner.xml if empty)';
|
||||
|
||||
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"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){function n(d,e,f){var i=[{key:"deleteevent",component:"calendar"}];f=parseInt(f,10);var m,n=f>1;n?(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})),m.then(function(a){a.show()}).fail(c.exception);var o=b.get_strings(i),p=a.when(o,m).then(function(b,e){return e.setTitle(b[0]),e.setBody(b[1]),n||e.setSaveButtonText(b[0]),e.getRoot().on(h.save,function(){j.deleteEvent(d,!1).then(function(){a("body").trigger(k.deleted,[d,!1])})["catch"](c.exception)}),e.getRoot().on(k.deleteAll,function(){j.deleteEvent(d,!0).then(function(){a("body").trigger(k.deleted,[d,!0])})["catch"](c.exception)}),e}).fail(c.exception);return p}function o(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");n(d,e,f),b.preventDefault()})}function p(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 q=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:o,registerEditListeners:p,registerEventFormModal:q}});
|
||||
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}});
|
||||
@@ -35,6 +35,7 @@ define([
|
||||
'core_calendar/events',
|
||||
'core_calendar/modal_delete',
|
||||
'core_calendar/selectors',
|
||||
'core/pending',
|
||||
],
|
||||
function(
|
||||
$,
|
||||
@@ -49,7 +50,8 @@ function(
|
||||
CalendarRepository,
|
||||
CalendarEvents,
|
||||
ModalDelete,
|
||||
CalendarSelectors
|
||||
CalendarSelectors,
|
||||
Pending
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -101,13 +103,6 @@ function(
|
||||
);
|
||||
}
|
||||
|
||||
deletePromise.then(function(deleteModal) {
|
||||
deleteModal.show();
|
||||
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
|
||||
var stringsPromise = Str.get_strings(deleteStrings);
|
||||
|
||||
var finalPromise = $.when(stringsPromise, deletePromise)
|
||||
@@ -118,27 +113,33 @@ function(
|
||||
deleteModal.setSaveButtonText(strings[0]);
|
||||
}
|
||||
|
||||
deleteModal.show();
|
||||
|
||||
deleteModal.getRoot().on(ModalEvents.save, function() {
|
||||
var pendingPromise = new Pending('calendar/crud:initModal:deletedevent');
|
||||
CalendarRepository.deleteEvent(eventId, false)
|
||||
.then(function() {
|
||||
$('body').trigger(CalendarEvents.deleted, [eventId, false]);
|
||||
return;
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
});
|
||||
|
||||
deleteModal.getRoot().on(CalendarEvents.deleteAll, function() {
|
||||
var pendingPromise = new Pending('calendar/crud:initModal:deletedallevent');
|
||||
CalendarRepository.deleteEvent(eventId, true)
|
||||
.then(function() {
|
||||
$('body').trigger(CalendarEvents.deleted, [eventId, true]);
|
||||
return;
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
});
|
||||
|
||||
return deleteModal;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
.catch(Notification.exception);
|
||||
|
||||
return finalPromise;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["core/templates","jquery","core/str","core/config","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g,h){var i={COHORTSELECT:"#id_cohortlist",TRIGGERBUTTONS:".enrolusersbutton.enrol_manual_plugin [type='submit']",UNWANTEDHIDDENFIELDS:":input[value='_qf__force_multiselect_submission']"},j=function(a){this.contextid=a.contextid,this.initModal()};return j.prototype.courseid=0,j.prototype.modal=null,j.prototype.initModal=function(){var a=b(i.TRIGGERBUTTONS);b.when(c.get_strings([{key:"enroluserscohorts",component:"enrol_manual"},{key:"enrolusers",component:"enrol_manual"}]),f.create({type:f.types.SAVE_CANCEL,large:!0},a)).then(function(a,c){this.modal=c,c.setTitle(a[1]),c.setSaveButtonText(a[1]),c.getRoot().on(g.save,this.submitForm.bind(this)),c.getRoot().on("submit","form",this.submitFormAjax.bind(this)),c.getRoot().on(g.hidden,function(){c.setBody("")}),c.getRoot().on(g.shown,function(){var d=this.getBody();d.then(function(d){var e=b(d).find(i.COHORTSELECT).length?0:1;c.setSaveButtonText(a[e])}).fail(e.exception),c.setBody(d)}.bind(this))}.bind(this)).fail(e.exception)},j.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},j.prototype.submitFormAjax=function(a){a.preventDefault();var c=this.modal.getRoot().find("form"),f=c.find(i.UNWANTEDHIDDENFIELDS);f.each(function(){b(this).remove()});var g=c.serialize();this.modal.hide();var h={type:"GET",processData:!1,contentType:"application/json"},j=d.wwwroot+"/enrol/manual/ajax.php?"+g;b.ajax(j,h).then(function(a){a.error?e.addNotification({message:a.error,type:"error"}):("undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload())}).fail(e.exception)},j.prototype.getBody=function(){return h.loadFragment("enrol_manual","enrol_users_form",this.contextid,{}).fail(e.exception)},j.prototype.getFooter=function(){return a.render("enrol_manual/enrol_modal_footer",{})},{init:function(a){new j(a)}}});
|
||||
define(["core/templates","jquery","core/str","core/config","core/notification","core/modal_factory","core/modal_events","core/fragment","core/pending"],function(a,b,c,d,e,f,g,h,i){var j={COHORTSELECT:"#id_cohortlist",TRIGGERBUTTONS:".enrolusersbutton.enrol_manual_plugin [type='submit']",UNWANTEDHIDDENFIELDS:":input[value='_qf__force_multiselect_submission']"},k=function(a){this.contextid=a.contextid,this.initModal()};return k.prototype.courseid=0,k.prototype.modal=null,k.prototype.initModal=function(){var a=b(j.TRIGGERBUTTONS);b.when(c.get_strings([{key:"enroluserscohorts",component:"enrol_manual"},{key:"enrolusers",component:"enrol_manual"}]),f.create({type:f.types.SAVE_CANCEL,large:!0},a)).then(function(a,c){this.modal=c,c.setTitle(a[1]),c.setSaveButtonText(a[1]),c.getRoot().on(g.save,this.submitForm.bind(this)),c.getRoot().on("submit","form",this.submitFormAjax.bind(this)),c.getRoot().on(g.hidden,function(){c.setBody("")}),c.getRoot().on(g.shown,function(){var d=new i("enrol_manual/quickenrolment:initModal:shown"),f=this.getBody();f.then(function(d){var e=b(d).find(j.COHORTSELECT).length?0:1;c.setSaveButtonText(a[e])}).then(d.resolve)["catch"](e.exception),c.setBody(f)}.bind(this))}.bind(this)).fail(e.exception)},k.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},k.prototype.submitFormAjax=function(a){a.preventDefault();var c=this.modal.getRoot().find("form"),f=c.find(j.UNWANTEDHIDDENFIELDS);f.each(function(){b(this).remove()});var g=c.serialize();this.modal.hide();var h={type:"GET",processData:!1,contentType:"application/json"},i=d.wwwroot+"/enrol/manual/ajax.php?"+g;b.ajax(i,h).then(function(a){a.error?e.addNotification({message:a.error,type:"error"}):("undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload())}).fail(e.exception)},k.prototype.getBody=function(){return h.loadFragment("enrol_manual","enrol_users_form",this.contextid,{}).fail(e.exception)},k.prototype.getFooter=function(){return a.render("enrol_manual/enrol_modal_footer",{})},{init:function(a){new k(a)}}});
|
||||
@@ -28,8 +28,9 @@ define(['core/templates',
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core/fragment',
|
||||
'core/pending',
|
||||
],
|
||||
function(Template, $, Str, Config, Notification, ModalFactory, ModalEvents, Fragment) {
|
||||
function(Template, $, Str, Config, Notification, ModalFactory, ModalEvents, Fragment, Pending) {
|
||||
|
||||
/** @type {Object} The list of selectors for the quick enrolment modal. */
|
||||
var SELECTORS = {
|
||||
@@ -91,6 +92,7 @@ define(['core/templates',
|
||||
});
|
||||
|
||||
modal.getRoot().on(ModalEvents.shown, function() {
|
||||
var pendingPromise = new Pending('enrol_manual/quickenrolment:initModal:shown');
|
||||
var bodyPromise = this.getBody();
|
||||
bodyPromise.then(function(html) {
|
||||
var stringIndex = $(html).find(SELECTORS.COHORTSELECT).length ? 0 : 1;
|
||||
@@ -98,7 +100,8 @@ define(['core/templates',
|
||||
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(Notification.exception);
|
||||
|
||||
modal.setBody(bodyPromise);
|
||||
}.bind(this));
|
||||
|
||||
@@ -189,5 +189,17 @@ MathJax.Hub.Config({
|
||||
// Automatically generated Moodle v3.6.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2018120301) {
|
||||
// Update CDN url.
|
||||
$originalurl = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js';
|
||||
$newurl = 'https://cdn.jsdelivr.net/npm/mathjax@2.7.8/MathJax.js';
|
||||
$currenturl = get_config('filter_mathjaxloader', 'httpsurl');
|
||||
if ($currenturl == $originalurl) {
|
||||
set_config('httpsurl', $newurl, 'filter_mathjaxloader');
|
||||
}
|
||||
|
||||
upgrade_plugin_savepoint(true, 2018120301, 'filter', 'mathjaxloader');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Description of MathJAX library integration in Moodle
|
||||
====================================================
|
||||
|
||||
* Default MathJax version: 2.7.2
|
||||
* Default MathJax version: 2.7.8
|
||||
* License: Apache 2.0
|
||||
* Source: https://www.mathjax.org/
|
||||
|
||||
@@ -18,3 +18,8 @@ Upgrading the default MathJax version
|
||||
3. Check and eventually update the list of language mappings in filter.php.
|
||||
Also see the unit test for the language mappings.
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
* The MathJax 2.7.2 seems to have a possible security issue, the CDN default value have been
|
||||
updated to point to the recommended 2.7.8 version. See MDL-68430 for details.
|
||||
|
||||
@@ -33,7 +33,7 @@ if ($ADMIN->fulltree) {
|
||||
$item = new admin_setting_configtext('filter_mathjaxloader/httpsurl',
|
||||
new lang_string('httpsurl', 'filter_mathjaxloader'),
|
||||
new lang_string('httpsurl_help', 'filter_mathjaxloader'),
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js',
|
||||
'https://cdn.jsdelivr.net/npm/mathjax@2.7.8/MathJax.js',
|
||||
PARAM_RAW);
|
||||
$settings->add($item);
|
||||
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2018120300;
|
||||
$plugin->version = 2018120301;
|
||||
$plugin->requires = 2018112800; // Requires this Moodle version.
|
||||
$plugin->component= 'filter_mathjaxloader';
|
||||
|
||||
@@ -128,6 +128,7 @@ class helper {
|
||||
* @return array sql and params list
|
||||
*/
|
||||
protected static function get_users_sql_and_params($context, $search = '', $count = false) {
|
||||
global $DB, $USER;
|
||||
|
||||
// Fields we need from the user table.
|
||||
$extrafields = get_extra_user_fields($context);
|
||||
@@ -147,13 +148,33 @@ class helper {
|
||||
$select = "SELECT DISTINCT $ufields ";
|
||||
$orderby = " ORDER BY u.lastname ASC, u.firstname ASC";
|
||||
}
|
||||
|
||||
$groupjoinsql = '';
|
||||
$groupwheresql = '';
|
||||
$courseid = $context->instanceid;
|
||||
$groupmode = groups_get_course_groupmode(get_course($courseid));
|
||||
|
||||
// We're only interested in separate groups mode because it's the only group mode that requires the user to be a member of
|
||||
// specific group(s), except when they have the 'moodle/site:accessallgroups' capability.
|
||||
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context)) {
|
||||
// Fetch the groups that the user can see.
|
||||
$groups = groups_get_all_groups($courseid, $USER->id, 0, 'g.id');
|
||||
|
||||
// Add join condition to include users that only belong to the same group as the user.
|
||||
list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED, 'gid', true, 0);
|
||||
$groupjoinsql = " JOIN {groups_members} gm ON gm.userid = u.id ";
|
||||
$groupwheresql = " AND gm.groupid $insql ";
|
||||
$params = array_merge($params, $inparams);
|
||||
}
|
||||
|
||||
$sql = "$select
|
||||
FROM {user} u
|
||||
JOIN {grade_grades_history} ggh ON u.id = ggh.userid
|
||||
JOIN {grade_items} gi ON gi.id = ggh.itemid
|
||||
WHERE $filtersql gi.courseid = :courseid";
|
||||
$groupjoinsql
|
||||
WHERE $filtersql gi.courseid = :courseid $groupwheresql";
|
||||
$sql .= $orderby;
|
||||
$params['courseid'] = $context->instanceid;
|
||||
$params['courseid'] = $courseid;
|
||||
|
||||
return array($sql, $params);
|
||||
}
|
||||
@@ -166,18 +187,31 @@ class helper {
|
||||
* @return array list of graders.
|
||||
*/
|
||||
public static function get_graders($courseid) {
|
||||
global $DB;
|
||||
global $DB, $USER;
|
||||
|
||||
$groupjoinsql = $groupwheresql = '';
|
||||
$inparams = [];
|
||||
$groupmode = groups_get_course_groupmode(get_course($courseid));
|
||||
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', \context_course::instance($courseid))) {
|
||||
// Fetch the groups that the user can see.
|
||||
$groups = groups_get_all_groups($courseid, $USER->id, 0, 'g.id');
|
||||
// Add join condition to include users that only belong to the same group as the user.
|
||||
list($insql, $inparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED, 'gid', true, 0);
|
||||
$groupjoinsql = " JOIN {groups_members} gm ON gm.userid = u.id ";
|
||||
$groupwheresql = " AND gm.groupid $insql ";
|
||||
}
|
||||
|
||||
$ufields = get_all_user_name_fields(true, 'u');
|
||||
$sql = "SELECT u.id, $ufields
|
||||
FROM {user} u
|
||||
JOIN {grade_grades_history} ggh ON ggh.usermodified = u.id
|
||||
JOIN {grade_items} gi ON gi.id = ggh.itemid
|
||||
WHERE gi.courseid = :courseid
|
||||
$groupjoinsql
|
||||
WHERE gi.courseid = :courseid $groupwheresql
|
||||
GROUP BY u.id, $ufields
|
||||
ORDER BY u.lastname ASC, u.firstname ASC";
|
||||
|
||||
$graders = $DB->get_records_sql($sql, array('courseid' => $courseid));
|
||||
$graders = $DB->get_records_sql($sql, array('courseid' => $courseid) + $inparams);
|
||||
$return = array(0 => get_string('allgraders', 'gradereport_history'));
|
||||
foreach ($graders as $grader) {
|
||||
$return[$grader->id] = fullname($grader);
|
||||
|
||||
@@ -337,7 +337,7 @@ class tablelog extends \table_sql implements \renderable {
|
||||
* @return array containing sql to use and an array of params.
|
||||
*/
|
||||
protected function get_filters_sql_and_params() {
|
||||
global $DB;
|
||||
global $DB, $USER;
|
||||
|
||||
$coursecontext = $this->context;
|
||||
$filter = 'gi.courseid = :courseid';
|
||||
@@ -368,6 +368,16 @@ class tablelog extends \table_sql implements \renderable {
|
||||
$params += array('grader' => $this->filters->grader);
|
||||
}
|
||||
|
||||
// If the course is separate group mode and the current user is not allowed to see all groups make sure
|
||||
// that we display only users from the same groups as current user.
|
||||
$groupmode = get_course($coursecontext->instanceid)->groupmode;
|
||||
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) {
|
||||
$groupids = array_column(groups_get_all_groups($coursecontext->instanceid, $USER->id, 0, 'g.id'), 'id');
|
||||
list($gsql, $gparams) = $DB->get_in_or_equal($groupids, SQL_PARAMS_NAMED, 'gmuparam', true, 0);
|
||||
$filter .= " AND EXISTS (SELECT 1 FROM {groups_members} gmu WHERE gmu.userid=ggh.userid AND gmu.groupid $gsql)";
|
||||
$params += $gparams;
|
||||
}
|
||||
|
||||
return array($filter, $params);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
$u5 = $this->getDataGenerator()->create_user();
|
||||
$grader1 = $this->getDataGenerator()->create_user();
|
||||
$grader2 = $this->getDataGenerator()->create_user();
|
||||
self::getDataGenerator()->enrol_user($grader1->id, $c1->id, 'teacher');
|
||||
self::getDataGenerator()->enrol_user($grader2->id, $c1->id, 'teacher');
|
||||
self::getDataGenerator()->enrol_user($u2->id, $c1->id, 'student');
|
||||
self::getDataGenerator()->enrol_user($u3->id, $c1->id, 'student');
|
||||
self::getDataGenerator()->enrol_user($u4->id, $c1->id, 'student');
|
||||
self::getDataGenerator()->enrol_user($u5->id, $c1->id, 'student');
|
||||
|
||||
// Modules.
|
||||
$c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
|
||||
@@ -65,6 +71,8 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
$giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign');
|
||||
$grades = array();
|
||||
|
||||
$this->setUser($grader1);
|
||||
|
||||
$gi = grade_item::fetch($giparams + array('iteminstance' => $c1m1->id));
|
||||
$grades['c1m1u1'] = $this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id,
|
||||
'timemodified' => time() - 3600));
|
||||
@@ -163,6 +171,20 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
$this->assertEquals(null, $results[$grades['c2m2u1a']->id]->prevgrade);
|
||||
$this->assertEquals($grades['c2m2u1a']->finalgrade, $results[$grades['c2m2u1c']->id]->prevgrade);
|
||||
$this->assertEquals($grades['c2m2u1c']->finalgrade, $results[$grades['c2m2u1e']->id]->prevgrade);
|
||||
|
||||
// Put course in separate groups mode, add grader1 and two students to the same group.
|
||||
$c1->groupmode = SEPARATEGROUPS;
|
||||
update_course($c1);
|
||||
$this->assertFalse(has_capability('moodle/site:accessallgroups', \context_course::instance($c1->id)));
|
||||
$g1 = self::getDataGenerator()->create_group(['courseid' => $c1->id, 'name' => 'g1']);
|
||||
self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $grader1->id]);
|
||||
self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $u1->id]);
|
||||
self::getDataGenerator()->create_group_member(['groupid' => $g1->id, 'userid' => $u2->id]);
|
||||
$this->assertEquals(2, $this->get_tablelog_results($c1ctx, array(), true));
|
||||
|
||||
// Grader2 is not in any groups.
|
||||
$this->setUser($grader2);
|
||||
$this->assertEquals(0, $this->get_tablelog_results($c1ctx, array(), true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,6 +241,118 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
$this->assertEquals(1, \gradereport_history\helper::get_users_count($c1ctx, 'c'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider method for \gradereport_history_report_testcase::test_get_users_with_groups()
|
||||
*/
|
||||
public function get_users_provider() {
|
||||
return [
|
||||
'Visible groups, non-editing teacher, not in any group' => [
|
||||
VISIBLEGROUPS, 'teacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
|
||||
],
|
||||
'Visible groups, non-editing teacher' => [
|
||||
VISIBLEGROUPS, 'teacher', [], ['s1', 's2', 's3', 's4', 's5']
|
||||
],
|
||||
'Visible groups, editing teacher' => [
|
||||
VISIBLEGROUPS, 'editingteacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
|
||||
],
|
||||
'Separate groups, non-editing teacher' => [
|
||||
SEPARATEGROUPS, 'teacher', ['g1', 'g2'], ['s1', 's2']
|
||||
],
|
||||
'Separate groups, non-editing teacher, not in any group' => [
|
||||
SEPARATEGROUPS, 'teacher', [], []
|
||||
],
|
||||
'Separate groups, non-editing teacher and student share two groups' => [
|
||||
SEPARATEGROUPS, 'teacher', ['g4', 'g5'], ['s5']
|
||||
],
|
||||
'Separate groups, editing teacher' => [
|
||||
SEPARATEGROUPS, 'editingteacher', ['g1', 'g2'], ['s1', 's2', 's3', 's4', 's5']
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for helper::get_users() with course group mode set.
|
||||
*
|
||||
* @dataProvider get_users_provider
|
||||
* @param $groupmode
|
||||
* @param $teacherrole
|
||||
* @param $teachergroups
|
||||
* @param $expectedusers
|
||||
*/
|
||||
public function test_get_users_with_groups($groupmode, $teacherrole, $teachergroups, $expectedusers) {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
|
||||
// Create a test course.
|
||||
$course = $generator->create_course(['groupmode' => $groupmode]);
|
||||
|
||||
// Create an assignment module.
|
||||
$assign = $generator->create_module('assign', ['course' => $course]);
|
||||
|
||||
// Fetch roles.
|
||||
$role = $DB->get_record('role', ['shortname' => $teacherrole], '*', MUST_EXIST);
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
|
||||
|
||||
// Create users.
|
||||
$t1 = $generator->create_user(['username' => 't1', 'email' => 't1@example.com']);
|
||||
$s1 = $generator->create_user(['username' => 's1', 'email' => 's1@example.com']);
|
||||
$s2 = $generator->create_user(['username' => 's2', 'email' => 's2@example.com']);
|
||||
$s3 = $generator->create_user(['username' => 's3', 'email' => 's3@example.com']);
|
||||
$s4 = $generator->create_user(['username' => 's4', 'email' => 's4@example.com']);
|
||||
$s5 = $generator->create_user(['username' => 's5', 'email' => 's5@example.com']);
|
||||
|
||||
// Enrol users.
|
||||
$generator->enrol_user($t1->id, $course->id, $role->id);
|
||||
$generator->enrol_user($s1->id, $course->id, $studentrole->id);
|
||||
$generator->enrol_user($s2->id, $course->id, $studentrole->id);
|
||||
$generator->enrol_user($s3->id, $course->id, $studentrole->id);
|
||||
$generator->enrol_user($s4->id, $course->id, $studentrole->id);
|
||||
$generator->enrol_user($s5->id, $course->id, $studentrole->id);
|
||||
|
||||
// Create groups.
|
||||
$groups = [];
|
||||
$groups['g1'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g1']);
|
||||
$groups['g2'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g2']);
|
||||
$groups['g3'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g3']);
|
||||
$groups['g4'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g4']);
|
||||
$groups['g5'] = $generator->create_group(['courseid' => $course->id, 'name' => 'g5']);
|
||||
|
||||
// Add teacher to the assigned groups.
|
||||
foreach ($teachergroups as $groupname) {
|
||||
$group = $groups[$groupname];
|
||||
$generator->create_group_member(['groupid' => $group->id, 'userid' => $t1->id]);
|
||||
}
|
||||
|
||||
// Add students to groups.
|
||||
$generator->create_group_member(['groupid' => $groups['g1']->id, 'userid' => $s1->id]);
|
||||
$generator->create_group_member(['groupid' => $groups['g2']->id, 'userid' => $s2->id]);
|
||||
$generator->create_group_member(['groupid' => $groups['g3']->id, 'userid' => $s3->id]);
|
||||
$generator->create_group_member(['groupid' => $groups['g4']->id, 'userid' => $s5->id]);
|
||||
$generator->create_group_member(['groupid' => $groups['g5']->id, 'userid' => $s5->id]);
|
||||
|
||||
// Creating grade history for the students.
|
||||
$gi = grade_item::fetch(['iteminstance' => $assign->id, 'itemtype' => 'mod', 'itemmodule' => 'assign']);
|
||||
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s1->id]);
|
||||
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s2->id]);
|
||||
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s3->id]);
|
||||
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s4->id]);
|
||||
$this->create_grade_history(['itemid' => $gi->id, 'userid' => $s5->id]);
|
||||
|
||||
// Log in as the teacher.
|
||||
$this->setUser($t1);
|
||||
|
||||
// Fetch the users.
|
||||
$users = \gradereport_history\helper::get_users(context_course::instance($course->id));
|
||||
// Confirm that the number of users fetched is the same as the count of expected users.
|
||||
$this->assertCount(count($expectedusers), $users);
|
||||
foreach ($users as $user) {
|
||||
// Confirm that each user returned is in the list of expected users.
|
||||
$this->assertTrue(in_array($user->username, $expectedusers));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get graders helper method.
|
||||
*/
|
||||
@@ -228,9 +362,11 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
// Making the setup.
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$c3 = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS]);
|
||||
|
||||
$c1m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1));
|
||||
$c2m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c2));
|
||||
$c3m1 = $this->getDataGenerator()->create_module('assign', array('course' => $c3));
|
||||
|
||||
// Users.
|
||||
$u1 = $this->getDataGenerator()->create_user(array('firstname' => 'Eric', 'lastname' => 'Cartman'));
|
||||
@@ -238,6 +374,12 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
$u3 = $this->getDataGenerator()->create_user(array('firstname' => 'Kyle', 'lastname' => 'Broflovski'));
|
||||
$u4 = $this->getDataGenerator()->create_user(array('firstname' => 'Kenny', 'lastname' => 'McCormick'));
|
||||
|
||||
foreach ([$c1, $c2, $c3] as $course) {
|
||||
foreach ([$u1, $u2, $u3, $u4] as $user) {
|
||||
self::getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
||||
}
|
||||
}
|
||||
|
||||
// Creating grade history for some users.
|
||||
$gi = grade_item::fetch(array('iteminstance' => $c1m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
|
||||
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u1->id));
|
||||
@@ -247,6 +389,10 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
$gi = grade_item::fetch(array('iteminstance' => $c2m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
|
||||
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u4->id));
|
||||
|
||||
$gi = grade_item::fetch(array('iteminstance' => $c3m1->id, 'itemtype' => 'mod', 'itemmodule' => 'assign'));
|
||||
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u1->id, 'usermodified' => $u1->id));
|
||||
$this->create_grade_history(array('itemid' => $gi->id, 'userid' => $u2->id, 'usermodified' => $u2->id));
|
||||
|
||||
// Checking fetching some users.
|
||||
$graders = \gradereport_history\helper::get_graders($c1->id);
|
||||
$this->assertCount(4, $graders); // Including "all graders" .
|
||||
@@ -256,6 +402,17 @@ class gradereport_history_report_testcase extends advanced_testcase {
|
||||
$graders = \gradereport_history\helper::get_graders($c2->id);
|
||||
$this->assertCount(2, $graders); // Including "all graders" .
|
||||
$this->assertArrayHasKey($u4->id, $graders);
|
||||
|
||||
// Third course is in separate groups mode. Only graders from the same group will be returned.
|
||||
$g = self::getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'g1']);
|
||||
self::getDataGenerator()->create_group_member(['groupid' => $g->id, 'userid' => $u1->id]);
|
||||
self::getDataGenerator()->create_group_member(['groupid' => $g->id, 'userid' => $u2->id]);
|
||||
$this->setUser($u1);
|
||||
$graders = \gradereport_history\helper::get_graders($c3->id);
|
||||
$this->assertCount(3, $graders); // Including "all graders" .
|
||||
$this->setUser($u3);
|
||||
$graders = \gradereport_history\helper::get_graders($c3->id);
|
||||
$this->assertCount(1, $graders); // Including "all graders" .
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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'] = 'ar';
|
||||
$string['thislanguage'] = 'بالعربي workplace';
|
||||
@@ -46,7 +46,7 @@ $string['downloadedfilecheckfailed'] = 'Ha fallat la comprovació del fitxer bai
|
||||
$string['invalidmd5'] = 'L\'md5 no és vàlid. Torneu a provar-ho';
|
||||
$string['missingrequiredfield'] = 'Falta algun camp necessari';
|
||||
$string['remotedownloaderror'] = '<p>No s\'ha pogut baixar el component al vostre servidor. Verifiqueu els paràmetres del servidor intermediari. Es recomana vivament l\'extensió cURL de PHP.</p>
|
||||
<p>Haureu de baixar manualment el fitxer <a href="{$a->url}">{$a->url}</a>, copiar-lo a la ubicació «{$a->dest}» del vostre servidor i descomprimir-lo allí.</p>';
|
||||
<p>Haureu de baixar manualment el fitxer <a href="{$a->url}">{$a->url}</a>, copiar-lo a la ubicació «{$a->dest}» del vostre servidor i descomprimir-lo allà.</p>';
|
||||
$string['wrongdestpath'] = 'El camí de destinació és erroni';
|
||||
$string['wrongsourcebase'] = 'L\'adreça (URL) base de la font és errònia';
|
||||
$string['wrongzipfilename'] = 'El nom del fitxer ZIP és erroni';
|
||||
|
||||
@@ -45,7 +45,7 @@ $string['datarootpermission'] = 'Zugriffsrechte zum Datenverzeichnis';
|
||||
$string['dbprefix'] = 'Tabellen-Prefix';
|
||||
$string['dirroot'] = 'Moodle-Verzeichnis';
|
||||
$string['environmenthead'] = 'Installationsvoraussetzungen werden geprüft ...';
|
||||
$string['environmentsub2'] = 'Jede Version hat Mindestvoraussetzungen für der PHP-Version und für verbindliche PHP-Extensions. Vor einer Installation oder einer Aktualisierung wird eine vollständige Prüfung durchgeführt. Bitte fragen Sie den Administrator des Servers, wenn Sie mit der Installation einer neuen Version oder mit der Aktivierung von PHP-Extensions nicht weiterkommen.';
|
||||
$string['environmentsub2'] = 'Jede Version hat Mindestvoraussetzungen für der PHP-Version und für verbindliche PHP-Extensions. Vor einer Installation oder einer Aktualisierung wird eine vollständige Prüfung durchgeführt. Bitte fragen Sie die Administrator/innen des Servers, wenn Sie mit der Installation einer neuen Version oder mit der Aktivierung von PHP-Extensions nicht weiterkommen.';
|
||||
$string['errorsinenvironment'] = 'Fehler bei der Prüfung der Systemvoraussetzungen!';
|
||||
$string['installation'] = 'Installation';
|
||||
$string['langdownloaderror'] = 'Das Sprachpaket \'{$a}\' konnte nicht heruntergeladen werden. Die Installation wird in englischer Sprache fortgesetzt.';
|
||||
@@ -63,7 +63,7 @@ $string['pathserrcreatedataroot'] = 'Das Datenverzeichnis ({$a->dataroot}) kann
|
||||
$string['pathshead'] = 'Pfade bestätigen';
|
||||
$string['pathsrodataroot'] = 'Das Verzeichnis dataroot ist schreibgeschützt.';
|
||||
$string['pathsroparentdataroot'] = 'Das Verzeichnis ({$a->parent}) ist schreibgeschützt. Deswegen kann das Datenverzeichnis ({$a->dataroot}) vom Installer nicht angelegt werden.';
|
||||
$string['pathssubadmindir'] = 'Einige Webserver benutzen /admin als speziellen Link, um auf Einstellungsseiten oder Ähnliches zu verweisen. Unglücklicherweise kollidiert dies mit dem standardmäßigen Verzeichnis für die Moodle-Administration. Sie können dieses Problem beheben, indem Sie das Verzeichnis admin in Ihrer Moodle-Installation umbenennen und den neuen Namen hier eingeben (z.B. <em>moodleadmin</em>). Mit dieser Änderung werden alle Admin-Links korrigiert.';
|
||||
$string['pathssubadmindir'] = 'Einige Webserver benutzen /admin als speziellen Link, um auf Einstellungsseiten oder Ähnliches zu verweisen. Unglücklicherweise kollidiert dies mit dem Verzeichnis für die Moodle-Administration. Sie können dieses Problem beheben, indem Sie das Verzeichnis admin in Ihrer Moodle-Installation umbenennen und den neuen Namen hier eingeben (z.B. <em>moodleadmin</em>). Mit dieser Änderung werden alle Admin-Links korrigiert.';
|
||||
$string['pathssubdataroot'] = '<p>Sie benötigen einen Platz, wo Moodle hochgeladene Dateien abspeichern kann. </p><p>Dieses Verzeichnis muss Lese- und Schreibrechte für das Nutzerkonto besitzen, mit dem Ihr Webservers läuft (üblicherweise \'nobody\', \'apache\' oder \'www-data).</p><p> Außerdem sollte das Verzeichnis nicht direkt aus dem Internet erreichbar sein. </p><p>Das Intallationsskript wird versuchen, ein solches Verzeichnis zu erstellen, falls es nicht existiert.</p>';
|
||||
$string['pathssubdirroot'] = '<p>Vollständiger Pfad der Moodle-Installation.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>Webadresse, die ein Nutzer im Browser eingibt, um auf Moodle zuzugreifen.</p><p> Es ist nicht möglich, über unterschiedliche Adressen auf Moodle zuzugreifen. Sollte Ihre Website mehrere öffentliche Adressen verwenden, so müssen Sie eine Adresse festlegen und für die übrigen Adressen dauerhafte Weiterleitungen dorthin einrichten.</p>
|
||||
|
||||
@@ -35,11 +35,11 @@ $string['cliansweryes'] = 'ν';
|
||||
$string['cliincorrectvalueerror'] = 'Σφάλμα, λανθασμένη τιμή «{$a->value}» για το «{$a->option}»';
|
||||
$string['cliincorrectvalueretry'] = 'Λανθασμένη τιμή. Παρακαλούμε προσπαθήστε ξανά.';
|
||||
$string['clitypevalue'] = 'πληκτρολογήστε μια τιμή';
|
||||
$string['clitypevaluedefault'] = 'πληκτρολογήστε μια τιμή· πατήστε Enter για να χρησιμοποιήσετε τηνπροεπιλεγμένη τιμή ({$a})';
|
||||
$string['clitypevaluedefault'] = 'πληκτρολογήστε μια τιμή· πατήστε Enter για να χρησιμοποιήσετε την προεπιλεγμένη τιμή ({$a})';
|
||||
$string['cliunknowoption'] = 'Μη αναγνωρίσιμες επιλογές:
|
||||
{$a}<br />
|
||||
Παρακαλούμε χρησιμοποιήστε την επιλογή --help (βοήθεια)';
|
||||
$string['cliyesnoprompt'] = 'πατήστε y (σημαίνει yes=ναι) ή πατήστε n (σημαίνει no=όχι)';
|
||||
$string['environmentrequireinstall'] = 'απαιτείται να εγκατασταθεί/ ενεργοποιηθεί';
|
||||
$string['environmentrequireinstall'] = 'απαιτείται να εγκατασταθεί και να ενεργοποιηθεί';
|
||||
$string['environmentrequireversion'] = 'απαιτείται η έκδοση {$a->needed} ενώ εσείς έχετε την {$a->current}';
|
||||
$string['upgradekeyset'] = 'Κλειδί αναβάθμισης (αφήστε κενό για να μην το ορίσετε)';
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatedboninstall'] = '<p>Αδυναμία δημιουργίας βάσης δεδομένων.</p> <p>Η βάση δεδομένων που προσδιορίστηκε δεν υπάρχει και ο χρήστης που δόθηκε δεν έχει δικαίωμα να δημιουργήσει την βάση δεδομένων.</p> <p>Ο διαχειριστής του ιστοτόπου πρέπει να επαληθεύσει τις ρυθμίσεις της βάσης δεδομένων.</p>';
|
||||
$string['cannotcreatedboninstall'] = '<p>Αδυναμία δημιουργίας βάσης δεδομένων.</p><p>Η βάση δεδομένων που προσδιορίστηκε δεν υπάρχει και ο χρήστης που δόθηκε δεν έχει δικαίωμα να δημιουργήσει την βάση δεδομένων.</p><p>Ο διαχειριστής του ιστοτόπου πρέπει να επαληθεύσει τις ρυθμίσεις της βάσης δεδομένων.</p>';
|
||||
$string['cannotcreatelangdir'] = 'Δε δημιουργήθηκε φάκελος γλώσσας.';
|
||||
$string['cannotcreatetempdir'] = 'Αδυναμία δημιουργίας προσωρινού φακέλου';
|
||||
$string['cannotdownloadcomponents'] = 'Δεν μπορεί να γίνει λήψη των στοιχείων λογισμικού';
|
||||
@@ -44,7 +44,7 @@ $string['dmlexceptiononinstall'] = '<p>Παρουσιάστηκε κάποιο
|
||||
$string['downloadedfilecheckfailed'] = 'Αποτυχία ελέγχου αρχείου που έγινε λήψη';
|
||||
$string['invalidmd5'] = 'Η μεταβλητή ελέγχου ήταν λανθασμένη - δοκιμάστε ξανά';
|
||||
$string['missingrequiredfield'] = 'Κάποιο απαιτούμενο πεδίο λείπει';
|
||||
$string['remotedownloaderror'] = '<p>Απέτυχε η λήψη του τμήματος στον εξυπηρετητή σας. Παρακαλούμε επιβεβαιώστε τις ρυθμίσεις του διακομιστή μεσολάβησης (proxy)· η επέκταση PHP cURL συνιστάται θερμά.</p> <p>Πρέπει να κατεβάσετε το <a href="{$a->url}">{$a->url}</a> χειροκίνητα, να το αντιγράψετε στο «{$a->dest}» στον εξυπηρετητή σας και να το αποσυμπιέσετε εκεί.</p>';
|
||||
$string['wrongdestpath'] = 'Λανθασμένη διαδρομή προορισμού (πλήρες όνομα).';
|
||||
$string['remotedownloaderror'] = '<p>Η λήψη του στοιχείου λογισμικού στον εξυπηρετητή σας απέτυχε. Παρακαλούμε επαληθεύστε τις ρυθμίσεις του διακομιστή μεσολάβησης· η επέκταση PHP cURL συνιστάται ιδιαίτερα.</p><br /><p>Πρέπει να κατεβάσετε το αρχείο <a href="{$a->url}">{$a->url}</a> χειροκίνητα, να το αντιγράψετε στο «{$a->dest}» στον εξυπηρετητή σας και να το αποσυμπιέσετε εκεί.</p>';
|
||||
$string['wrongdestpath'] = 'Λανθασμένο μονοπάτι προορισμού.';
|
||||
$string['wrongsourcebase'] = 'Λανθασμένη βάση πηγής URL.';
|
||||
$string['wrongzipfilename'] = 'Λανθασμένo όνομα αρχείου ZIP.';
|
||||
$string['wrongzipfilename'] = 'Λανθασμένο όνομα αρχείου ZIP.';
|
||||
|
||||
@@ -54,7 +54,7 @@ $string['memorylimithelp'] = '<p>Το όριο μνήμης της PHP στον
|
||||
|
||||
<p>Αυτό μπορεί να προκαλέσει προβλήματα μνήμης στο Moodle στη συνέχεια, ειδικά αν έχετε πολλά ενεργοποιημένα αρθρώματα και/ή πολλούς χρήστες.</p>
|
||||
|
||||
<p>Προτείνεται η ρύθμιση της PHP με μεγαλύτερο όριο, αν αυτό είναι δυνατό, π.χ. 40M. Υπάρχουν πολλοί τρόποι να το κάνετε αυτό, τους οποίους μπορείτε να δοκιμάσετε:</p>
|
||||
<p>Συνιστάται η ρύθμιση της PHP με μεγαλύτερο όριο, αν αυτό είναι δυνατό, π.χ. 40M. Υπάρχουν πολλοί τρόποι να το κάνετε αυτό, τους οποίους μπορείτε να δοκιμάσετε:</p>
|
||||
<ol>
|
||||
<li>Αν έχετε τη δυνατότητα, κάνετε επαναμεταγλώττιση της PHP με την παράμετρο <i>--enable-memory-limit</i>. Αυτό θα επιτρέψει στο Moodle να ορίσει μόνο του το όριο μνήμης.</li>
|
||||
<li>Αν έχετε πρόσβαση στο αρχείο php.ini, μπορείτε να αλλάξετε τη ρύθμιση <b>memory_limit</b> σε 40M. Αν δεν έχετε πρόσβαση ζητήστε από το διαχειριστή να το κάνει για εσάς.</li>
|
||||
@@ -62,20 +62,20 @@ $string['memorylimithelp'] = '<p>Το όριο μνήμης της PHP στον
|
||||
<blockquote>php_value memory_limit 40M</div></blockquote>
|
||||
<p>Ωστόσο, σε κάποιους εξυπηρετητές αυτό θα εμποδίσει τη λειτουργία <b>όλων</b> των σελίδων PHP (θα βλέπετε σφάλματα όταν ανοίγετε τις σελίδες), οπότε θα πρέπει να διαγράψετε το αρχείο .htaccess.</p></li>
|
||||
</ol>';
|
||||
$string['paths'] = 'Διαδρομές';
|
||||
$string['pathserrcreatedataroot'] = 'Ο Φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργθεί από το πρόγραμμα εγκατάστασης.';
|
||||
$string['pathshead'] = 'Επιβεβαίωση Διαδρομών';
|
||||
$string['paths'] = 'Μονοπάτια';
|
||||
$string['pathserrcreatedataroot'] = 'Ο φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργηθεί από το πρόγραμμα εγκατάστασης.';
|
||||
$string['pathshead'] = 'Επιβεβαίωση μονοπατιών';
|
||||
$string['pathsrodataroot'] = 'Ο Φάκελος Δεδομένων δεν είναι εγγράψιμος.';
|
||||
$string['pathsroparentdataroot'] = 'Ο γονικός φάκελος ({$a->parent}) δεν είναι εγγράψιμος. Ο φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργθεί από το πρόγραμμα εγκατάστασης.';
|
||||
$string['pathsroparentdataroot'] = 'Ο γονικός φάκελος ({$a->parent}) δεν είναι εγγράψιμος. Ο φάκελος δεδομένων ({$a->dataroot}) δεν μπορεί να δημιουργηθεί από το πρόγραμμα εγκατάστασης.';
|
||||
$string['pathssubadmindir'] = 'Κάποιοι λίγοι κεντρικοί υπολογιστές ιστού χρησιμοποιούν το /admin ως ειδική διεύθυνση URL για την πρόσβαση σε κάποιο πίνακα ελέγχου ή κάτι τέτοιο. Δυστυχώς αυτό έρχεται σε αντίθεση με την τυπική τοποθεσία των σελίδων διαχείρισης (admin) του Moodle. Αυτό μπορεί να διορθωθεί με την μετονομασία του admin φακέλου στην εγκατάστασή σας, και βάζοντας αυτό το καινούργιο όνομα εδώ. Για παράδειγμα: <em>moodleadmin</em>. Αυτό θα διορθώσει όλους τους συνδέσμους με το admin στην διεύθυνσή τους σε όλη την εγκατάσταση του Moodle σας.';
|
||||
$string['pathssubdataroot'] = '<p>Ένας φάκελος όπου το Moodle θα αποθηκεύει όλα τα ανεβασμένα από τους χρήστες αρχεία.</p> <p>Αυτος ο φάκελος θα πρέπει να είναι αναγνώσιμος ΚΑΙ ΕΓΓΡΑΨΙΜΟΣ από τον χρήστη του εξυπηρετητή ιστού (συνήθως «nobody» ή «apache»).</p> <p>Δεν πρέπει να είναι προσβάσιμος κατευθείαν από τον ιστό.</p> <p>Αν ο φάκελος δεν υπάρχει, η διαδικασία εγκατάστασης θα προσπαθήσει να τον δημιουργήσει.</p>';
|
||||
$string['pathssubdataroot'] = '<p>Ένας φάκελος όπου το Moodle θα αποθηκεύει όλα τα ανεβασμένα από τους χρήστες αρχεία.</p><p>Αυτος ο φάκελος θα πρέπει να είναι αναγνώσιμος ΚΑΙ ΕΓΓΡΑΨΙΜΟΣ από τον χρήστη του εξυπηρετητή ιστού (συνήθως «nobody» ή «apache»).</p><p>Δεν πρέπει να είναι προσβάσιμος κατευθείαν από τον ιστό.</p><p>Αν ο φάκελος δεν υπάρχει, η διαδικασία εγκατάστασης θα προσπαθήσει να τον δημιουργήσει.</p>';
|
||||
$string['pathssubdirroot'] = '<p>Η πλήρης διαδρομή του φακέλου που περιέχει τα αρχεία κώδικα του Moodle.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>Η πλήρης διεύθυνση (ιστού) από την οποία θα γίνεται η πρόσβαση στο Moodle, δηλαδή η διεύθυνση που οι χρήστες θα εισάγουν στην γραμμή διεύθυνσης του περιηγητή, για να έχουν πρόσβαση στου Moodle.</p>
|
||||
$string['pathssubwwwroot'] = '<p>Η πλήρης διεύθυνση από την οποία θα γίνεται η πρόσβαση στο Moodle, δηλαδή η διεύθυνση που οι χρήστες θα εισάγουν στην γραμμή διεύθυνσης του περιηγητή, για να έχουν πρόσβαση στου Moodle.</p>
|
||||
<p>Δεν είναι δυνατόν να έχετε πρόβαση στο Moodle χρησιμοποιώντας πολλαπλές διευθύνσεις. Εάν ο ιστότοπος θα είναι προσβάσιμος μέσω πολλαπλών διευθύνσεων τότε επιλέξτε την ευκολότερη και εγκαταστήστε μια μόνιμη ανακατεύθυνση για καθεμία από τις άλλες διευθύνσεις.</p>
|
||||
<p>Εάν ο ιστότοπός σας είναι προσβάσιμος τόσο από το Διαδίκτυο όσο και από ένα εσωτερικό δίκτυο (που συχνά λέγεται intranet) τότε χρησιμοποιήστε εδώ την δημόσια διεύθυνση.</p>
|
||||
<p>Αν η τρέχουσα διεύθυνση δεν είναι σωστή, παρακαλούμε αλλάξτε την URL διεύθυνση στην γραμμή διευθύνσεων του περιηγητή σας και επανεκκινήστε την εγκατάσταση.</p>';
|
||||
$string['pathsunsecuredataroot'] = 'Η τοποθεσία του Φάκελου Δεδομένων δεν είναι ασφαλής';
|
||||
$string['pathswrongadmindir'] = 'Ο Φάκελος Admin δεν υπάρχει';
|
||||
$string['pathswrongadmindir'] = 'Ο φάκελος Admin δεν υπάρχει';
|
||||
$string['phpextension'] = 'Επέκταση {$a} της PHP';
|
||||
$string['phpversion'] = 'Έκδοση της PHP';
|
||||
$string['phpversionhelp'] = 'p>Το Moodle απαιτεί η έκδοση της PHP να είναι τουλάχιστον 5.6.5 ή 7.1 (η 7.0.x έχει κάποιους περιορισμούς στη μηχανή).</p>
|
||||
|
||||
@@ -34,4 +34,4 @@ $string['language'] = 'Γλώσσα';
|
||||
$string['moodlelogo'] = 'Λογότυπο Moodle';
|
||||
$string['next'] = 'Επόμενο';
|
||||
$string['previous'] = 'Προηγούμενο';
|
||||
$string['reload'] = 'Eπαναφόρτωση';
|
||||
$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'] = 'en_us';
|
||||
$string['thislanguage'] = 'English for Workplace - United States';
|
||||
@@ -31,4 +31,5 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'es';
|
||||
$string['thisdirection'] = 'ltr';
|
||||
$string['thislanguage'] = 'Español para la Empresa';
|
||||
|
||||
@@ -45,13 +45,13 @@ $string['datarootpermission'] = 'Datu-direktorioen baimena';
|
||||
$string['dbprefix'] = 'Taulen aurrizkia';
|
||||
$string['dirroot'] = 'Moodle direktorioa';
|
||||
$string['environmenthead'] = 'Zure ingurunea egiaztatzen...';
|
||||
$string['environmentsub2'] = 'Moodle-ko 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 eguneraketa 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.';
|
||||
$string['memorylimithelp'] = '<p>Zure zerbitzarian PHP memoria-muga {$a} da une honetan.</p>
|
||||
|
||||
<p>Aurrerago honek Moodle-ri arazoak sor diezaizkioke,
|
||||
<p>Aurrerago honek Moodleri arazoak sor diezaizkioke,
|
||||
bereziki modulu edo/eta erabiltzaile asko badituzu.</p>
|
||||
|
||||
<p>PHP memoria-muga ahal bezain altu konfiguratzea aholkatzen dizugu, ad. 40M.
|
||||
@@ -67,17 +67,17 @@ Horrek Moodle-k berak memoria-muga ezartzea ahalbidetzen du.</li>
|
||||
(orriak ikustean erroreak ere ikusiko dituzu). Kasu horretan, .htaccess fitxategia ezabatu beharko duzu.</p></li>
|
||||
</ol>';
|
||||
$string['paths'] = 'Bideak';
|
||||
$string['pathserrcreatedataroot'] = 'Instalatzaileak ezin du datu-karpeta ({$a->dataroot}) sortu.';
|
||||
$string['pathserrcreatedataroot'] = 'Instalatzaileak ezin du datu-direktorioa ({$a->dataroot}) sortu.';
|
||||
$string['pathshead'] = 'Egiaztatu bideak';
|
||||
$string['pathsrodataroot'] = 'Dataroot direktorioa ez da idazteko modukoa.';
|
||||
$string['pathsroparentdataroot'] = 'Goragoko karpeta ({$a->parent}) ez da idazteko modukoa. Instalatzaileak ezin du datu-karpeta ({$a->dataroot}) sortu.';
|
||||
$string['pathssubadmindir'] = 'Web ostalari gutxi batzuk /admin URL berezi gisa erabiltzen dute kontrol-panel edo antzekora sarbidea emateko. Zoritxarrez, honek Moodle-ren kudeatze-orrien lehenetsitako kokapenarekin gatazka sortzen du. Hau konpondu dezakezu zure instalazioko admin karpeta berrizendatuz, eta izen berria hemen sartuta. Adibidez <em>moodleadmin</em>. Honek Moodle-ko admin estekak konponduko du.';
|
||||
$string['pathssubdataroot'] = '<p>Moodle-k erabiltzaileek igotako fitxategien edukiak bilduko dituen direktorio bat.</p>
|
||||
$string['pathsroparentdataroot'] = 'Goragoko direktorioa ({$a->parent}) ez da idazteko modukoa. Instalatzaileak ezin du datu-direktorioa ({$a->dataroot}) sortu.';
|
||||
$string['pathssubadmindir'] = 'Web ostalari gutxi batzuk /admin URL berezi gisa erabiltzen dute kontrol-panel edo antzekora sarbidea emateko. Zoritxarrez, honek Moodleren kudeatze-orrien lehenetsitako kokapenarekin gatazka sortzen du. Hau konpondu dezakezu zure instalazioko admin direktorioa berrizendatuz, eta izen berria hemen sartuta. Adibidez <em>moodleadmin</em>. Honek Moodleko admin estekak konponduko du.';
|
||||
$string['pathssubdataroot'] = '<p>Moodlek erabiltzaileek igotako fitxategien edukiak bilduko dituen direktorio bat.</p>
|
||||
<p>Direktorio honetan web-zerbitzariaren erabiltzaileak irakurtzeko eta idazteko baimena izan beharko ditu (normalean \'www-data\', \'nobody\', edo \'apache\').</p>
|
||||
<p>Ez litzateke web bidez eskuragarri egon beharko.</p>
|
||||
<p>Direktorioa existitzen ez bada, instalazioan sortzeko saiakera egingo da.</p>';
|
||||
$string['pathssubdirroot'] = '<p>Moodle-ko kodea daukan direktorioaren helbide osoa.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>Moodle eskuragarri egongo den helbide osoa, hau da, erabiltzaileek Moodle-n sartzeko nabigatzaileareko helbide barran idatziko duten helbidea</p>
|
||||
$string['pathssubdirroot'] = '<p>Moodleko kodea daukan direktorioaren bide osoa.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>Moodle eskuragarri egongo den helbide osoa, hau da, erabiltzaileek Moodlen sartzeko nabigatzailearen helbide barran idatziko duten helbidea</p>
|
||||
<p>Moodle ezin da hainbat helbidetatik eskuragarri egon. Zure gunea hainbat helbidetatik eskuragarri badago errazena aukeratu eta bertara beste helbideetatik behin-betiko berbideraketak konfiguratu itzazu.</p>
|
||||
<p>Zure gunea Internetetik eta barne-sare batetik eskuragarri badago (batzuetan Intranet deitutakoa), hemen helbide publikoa erabili ezazu.</p>
|
||||
<p>Oraingo helbidea egokia ez bada, mesedez aldatu ezazu URLa zure nabigatzailean eta instalazioa berriz abiatu ezazu.</p>';
|
||||
@@ -85,7 +85,7 @@ $string['pathsunsecuredataroot'] = 'Dataroot-en kokapena ez da segurua';
|
||||
$string['pathswrongadmindir'] = 'Admin direktorioa ez da existitzen';
|
||||
$string['phpextension'] = '{$a} PHP hedapena';
|
||||
$string['phpversion'] = 'PHP bertsioa';
|
||||
$string['phpversionhelp'] = '<p>Moodle-k PHP 5.6.5 edo 7.1 bertsioetako bat behar du (7.0.x bertsioek muga batzuk dituzte). </p>
|
||||
$string['phpversionhelp'] = '<p>Moodlek PHP 5.6.5 edo 7.1 bertsioetako bat behar du (7.0.x bertsioek muga batzuk dituzte). </p>
|
||||
<p>Zure bertsioa: {$a}</p>
|
||||
<p>PHP eguneratu edo PHP bertsio berriagoa duen zerbitzari batera jo.</p>';
|
||||
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'Hizkuntza';
|
||||
$string['moodlelogo'] = 'Moodle-ren logoa';
|
||||
$string['moodlelogo'] = 'Moodleren logoa';
|
||||
$string['next'] = 'Hurrengoa';
|
||||
$string['previous'] = 'Aurrekoa';
|
||||
$string['reload'] = 'Berriz kargatu';
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatedboninstall'] = '<p>Impossible de créer la base de données.</p>
|
||||
<p>La base de données indiquées n\'existe pas et l\'utilisateur spécifié n\'a pas les autorisations permettant de créer une base de données.</p>.
|
||||
<p>La base de données indiquées n\'existe pas et l\'utilisateur spécifié n\'a pas les permissions requises pour créer une base de données.</p>.
|
||||
<p>L\'administrateur du site doit revoir la configuration de la base de données.</p>';
|
||||
$string['cannotcreatelangdir'] = 'Création du dossier lang impossible';
|
||||
$string['cannotcreatetempdir'] = 'Création du dossier temp impossible';
|
||||
|
||||
@@ -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'] = 'fr';
|
||||
$string['thislanguage'] = 'Français (écriture inclusive)';
|
||||
@@ -31,4 +31,5 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'he';
|
||||
$string['thisdirection'] = 'rtl';
|
||||
$string['thislanguage'] = 'עברית בתי־ספר';
|
||||
|
||||
@@ -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'] = 'ל';
|
||||
$string['cliansweryes'] = 'כ';
|
||||
$string['cliincorrectvalueretry'] = 'ערך לא תקין, נסו שוב בבקשה';
|
||||
@@ -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'] = 'ja';
|
||||
$string['thislanguage'] = 'Workplace準拠の日本語';
|
||||
@@ -30,5 +30,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['clianswerno'] = 'ទេ';
|
||||
$string['environmentrequireinstall'] = 'ត្រូវតែបានដំឡើង និងអនុញ្ញាត';
|
||||
$string['environmentrequireversion'] = 'ត្រូវការកំណែ {$a->needed} ហើយអ្នកកំពុងរត់ {$a->current}';
|
||||
|
||||
@@ -30,4 +30,4 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['thislanguage'] = 'myanma bhasa';
|
||||
$string['thislanguage'] = 'ဗမာစာ';
|
||||
|
||||
@@ -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'] = 'nl';
|
||||
$string['thislanguage'] = 'Nederlands Workplace';
|
||||
@@ -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['dirroot'] = 'Moodle baafataa';
|
||||
@@ -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'] = 'Afaan Oromoo';
|
||||
@@ -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['language'] = 'Qooqa ykn Afaan';
|
||||
$string['next'] = 'Kan itti aanuu';
|
||||
$string['previous'] = 'Kan duraa';
|
||||
@@ -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['parentlanguage'] = 'fa';
|
||||
$string['thisdirection'] = 'rtl';
|
||||
$string['thislanguage'] = 'دری';
|
||||
@@ -30,5 +30,5 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['thisdirection'] = 'ltr';
|
||||
$string['thisdirection'] = 'rtl';
|
||||
$string['thislanguage'] = 'پښتو';
|
||||
|
||||
@@ -34,7 +34,7 @@ $string['admindirname'] = 'Pasta de administração';
|
||||
$string['availablelangs'] = 'Pacotes linguísticos disponíveis';
|
||||
$string['chooselanguagehead'] = 'Selecione um idioma';
|
||||
$string['chooselanguagesub'] = 'Selecione o idioma a utilizar durante a instalação. Poderá depois selecionar outro(s) idioma(s) para o site e para os utilizadores.';
|
||||
$string['clialreadyconfigured'] = 'O ficheiro config.php já existe, use admin/cli/install_database.php para instalar o Moodle para este site.';
|
||||
$string['clialreadyconfigured'] = 'O ficheiro config.php já existe. Use \'admin/cli/install_database.php\' para instalar o Moodle para este site.';
|
||||
$string['clialreadyinstalled'] = 'O ficheiro config.php já existe, use admin/cli/install_database.php para atualizar o Moodle para este site.';
|
||||
$string['cliinstallheader'] = 'Programa para instalação do Moodle <b>{$a}</b> através da linha de comandos';
|
||||
$string['databasehost'] = 'Servidor da base de dados';
|
||||
|
||||
@@ -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'] = 'pt_br';
|
||||
$string['thislanguage'] = 'Português para Workplace';
|
||||
@@ -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'] = 'Sicilianu';
|
||||
@@ -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['thisdirection'] = 'rtl';
|
||||
$string['thislanguage'] = 'سنڌي';
|
||||
@@ -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['cannotsavemd5file'] = 'Ii sáhte vurket md5-fiilla.';
|
||||
$string['cannotsavezipfile'] = 'Ii sáhte vurket ZIP-fiilla.';
|
||||
$string['wrongzipfilename'] = 'Boasttu ZIP-fiilanamma.';
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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['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š.';
|
||||
$string['clialreadyconfigured'] = 'Fiila config.php gávdno jo. Geavat admin/cli/install_database.php jus áiggut installeret dán portála.';
|
||||
$string['clialreadyinstalled'] = 'Fiila config.php gávdno jo. Geavat admin/cli/install_database.php jus háliidat ođasmahttit Moodle dán portálas.';
|
||||
$string['databasename'] = 'Diehtovuođđonamma';
|
||||
$string['langdownloaderror'] = 'Dađibahábut ii installerejuvvon giellapáhkka "{$a}". Installašuvdnaproseassa joatká eaŋgalsgillii.';
|
||||
$string['phpextension'] = '{$a} PHP gilkor';
|
||||
@@ -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['language'] = 'Giella';
|
||||
$string['next'] = 'Boahtte';
|
||||
@@ -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['thisdirection'] = 'ltr';
|
||||
$string['thislanguage'] = 'Tok Pisin';
|
||||
@@ -31,5 +31,5 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = '';
|
||||
$string['thisdirection'] = 'ltr';
|
||||
$string['thisdirection'] = '符号(ltr)';
|
||||
$string['thislanguage'] = '简体中文';
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
GeoIP2-City-Test.mmdb:
|
||||
Copyright: maxmind (github.com/maxmind)
|
||||
|
||||
https://github.com/maxmind/MaxMind-DB/blob/master/test-data/GeoIP2-City-Test.mmdb
|
||||
|
||||
Licence: Creative Commons Attribution-ShareAlike 3.0 Unported License
|
||||
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
No changes have been made to this file.
|
||||
@@ -36,56 +36,15 @@ require_once("{$CFG->dirroot}/iplookup/lib.php");
|
||||
*/
|
||||
class core_iplookup_geoip_testcase extends advanced_testcase {
|
||||
public function setUp() {
|
||||
if (!PHPUNIT_LONGTEST) {
|
||||
// These tests are intensive and required downloads.
|
||||
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
|
||||
}
|
||||
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setup the GeoIP2File system.
|
||||
*/
|
||||
public function setup_geoip2file() {
|
||||
global $CFG;
|
||||
|
||||
// Store the file somewhere where it won't be wiped out..
|
||||
$gzfile = "$CFG->dataroot/phpunit/geoip/GeoLite2-City.mmdb.gz";
|
||||
check_dir_exists(dirname($gzfile));
|
||||
if (file_exists($gzfile) and (filemtime($gzfile) < time() - 60*60*24*30)) {
|
||||
// Delete file if older than 1 month.
|
||||
unlink($gzfile);
|
||||
}
|
||||
|
||||
if (!file_exists($gzfile)) {
|
||||
download_file_content('http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz',
|
||||
null, null, false, 300, 20, false, $gzfile);
|
||||
}
|
||||
|
||||
$this->assertTrue(file_exists($gzfile));
|
||||
|
||||
$geoipfile = str_replace('.gz', '', $gzfile);
|
||||
|
||||
// Open our files (in binary mode).
|
||||
$file = gzopen($gzfile, 'rb');
|
||||
$geoipfilebuf = fopen($geoipfile, 'wb');
|
||||
|
||||
// Keep repeating until the end of the input file.
|
||||
while (!gzeof($file)) {
|
||||
// Read buffer-size bytes.
|
||||
// Both fwrite and gzread and binary-safe.
|
||||
fwrite($geoipfilebuf, gzread($file, 4096));
|
||||
}
|
||||
|
||||
// Files are done, close files.
|
||||
fclose($geoipfilebuf);
|
||||
gzclose($file);
|
||||
|
||||
$this->assertTrue(file_exists($geoipfile));
|
||||
|
||||
$CFG->geoip2file = $geoipfile;
|
||||
$CFG->geoip2file = "$CFG->dirroot/iplookup/tests/fixtures/GeoIP2-City-Test.mmdb";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,9 +80,8 @@ class core_iplookup_geoip_testcase extends advanced_testcase {
|
||||
*/
|
||||
public function ip_provider() {
|
||||
return [
|
||||
'IPv4: Sample suggested by maxmind themselves' => ['24.24.24.24'],
|
||||
'IPv4: github.com' => ['192.30.255.112'],
|
||||
'IPv6: UCLA' => ['2607:f010:3fe:fff1::ff:fe00:25'],
|
||||
'IPv4: IPV4 test' => ['81.2.69.142'],
|
||||
'IPv6: IPV6 test' => ['2001:252:1::1:1:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -251,7 +251,7 @@ $string['configfrontpageloggedin'] = 'The items selected above will be displayed
|
||||
$string['configfullnamedisplay'] = 'This defines how names are shown when they are displayed in full. The default value, "language", leaves it to the string "fullnamedisplay" in the current language pack to decide. Some languages have different name display conventions.
|
||||
|
||||
For most mono-lingual sites the most efficient setting is "firstname lastname", but you may choose to hide surnames altogether. Placeholders that can be used are: firstname, lastname, firstnamephonetic, lastnamephonetic, middlename, and alternatename.';
|
||||
$string['configgeoipfile'] = 'Location of GeoLite2 City binary data file. This file is not part of Moodle distribution and must be obtained separately from <a href="https://www.maxmind.com/">MaxMind</a>. You can either buy a commercial version or use the free version. Simply download <a href="https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz" >https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz</a> and extract it into "{$a}" directory on your server.';
|
||||
$string['configgeoipfile'] = 'Location of GeoLite2 City binary data file. This file is not part of Moodle distribution and must be obtained separately from <a href="https://www.maxmind.com/">MaxMind</a>. You can either buy a commercial version or use the free version. You\'ll need to register to download the City database file, which you can do at <a href="https://dev.maxmind.com/geoip/geoip2/geolite2/" >https://dev.maxmind.com/geoip/geoip2/geolite2/</a>. Once you\'ve registered and downloaded the file, extract it into "{$a}" directory on your server.';
|
||||
$string['configgetremoteaddrconf'] = 'If your server is behind a reverse proxy, you can use this setting to specify which HTTP headers can be trusted to contain the remote IP address. The headers are read in order, using the first one that is available.';
|
||||
$string['configgradebookroles'] = 'This setting allows you to control who appears on the gradebook. Users need to have at least one of these roles in a course to be shown in the gradebook for that course.';
|
||||
$string['configgradeexport'] = 'Choose which gradebook export formats are your primary methods for exporting grades. Chosen plugins will then set and use a "last exported" field for every grade. For example, this might result in exported records being identified as being "new" or "updated". If you are not sure about this then leave everything unchecked.';
|
||||
@@ -319,6 +319,7 @@ $string['configrequestedstudentname'] = 'Word for student used in requested cour
|
||||
$string['configrequestedstudentsname'] = 'Word for students used in requested courses';
|
||||
$string['configrequestedteachername'] = 'Word for teacher used in requested courses';
|
||||
$string['configrequestedteachersname'] = 'Word for teachers used in requested courses';
|
||||
$string['configreverseproxyignore'] = 'If your server is behind multiple reverse proxies that append to the X-Forwarded-For header then you will need to specify a comma separated list of ip addresses or subnets of the reverse proxies to be ignored in order to find the users correct IP address.';
|
||||
$string['configuserquota'] = 'The maximum number of bytes that a user can store in their own private file area. {$a->bytes} bytes == {$a->displaysize}';
|
||||
$string['configsectioninterface'] = 'Interface';
|
||||
$string['configsectionmail'] = 'Mail';
|
||||
@@ -749,6 +750,7 @@ $string['maxeditingtime'] = 'Maximum time to edit posts';
|
||||
$string['maxusersperpage'] = ' Maximum users per page';
|
||||
$string['configmaxusersperpage'] = 'Maximum number of users displayed within user selector in course, group, cohort, webservice etc.';
|
||||
$string['mbstringrecommended'] = 'Installing the optional MBSTRING library is highly recommended in order to improve site performance, particularly if your site is supporting non-Latin languages.';
|
||||
$string['mbstringrequired'] = 'Installing the MBSTRING library is required in order to improve site performance, particularly if your site is supporting non-Latin languages.';
|
||||
$string['mediapluginavi'] = 'Enable .avi filter';
|
||||
$string['mediapluginflv'] = 'Enable .flv filter';
|
||||
$string['mediapluginimg'] = 'Enable auto-embedding of linked images';
|
||||
@@ -1032,6 +1034,7 @@ $string['restorecourse'] = 'Restore course';
|
||||
$string['restorernewroleid'] = 'Restorers\' role in courses';
|
||||
$string['restorernewroleid_help'] = 'If the user does not already have the permission to manage the newly restored course, the user is automatically assigned this role and enrolled if necessary. Select "None" if you do not want restorers to be able to manage every restored course.';
|
||||
$string['reverseproxy'] = 'Reverse proxy';
|
||||
$string['reverseproxyignore'] = 'Ignore reverse proxies';
|
||||
$string['riskconfig'] = 'Users could change site configuration and behaviour';
|
||||
$string['riskconfigshort'] = 'Configuration risk';
|
||||
$string['riskdataloss'] = 'Users could destroy large amounts of content or information';
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/modal_events","core/modal_registry","core/modal","core/modal_save_cancel","core/modal_confirm","core/modal_cancel","core/templates","core/notification","core/custom_interaction_events"],function(a,b,c,d,e,f,g,h,i,j){var k={DEFAULT:"core/modal",SAVE_CANCEL:"core/modal_save_cancel",CONFIRM:"core/modal_save_cancel",CANCEL:"core/modal_cancel"},l={DEFAULT:"DEFAULT",SAVE_CANCEL:"SAVE_CANCEL",CONFIRM:"CONFIRM",CANCEL:"CANCEL"};c.register(l.DEFAULT,d,k.DEFAULT),c.register(l.SAVE_CANCEL,e,k.SAVE_CANCEL),c.register(l.CONFIRM,f,k.CONFIRM),c.register(l.CANCEL,g,k.CANCEL);var m=function(c,d,e){var f=null,g="function"==typeof e.preShowCallback,h=function(b,d){f=a(b.currentTarget),c.then(function(a){return g&&e.preShowCallback(f,a),a.show(),a}),d.originalEvent.preventDefault()};if(Array.isArray(d)){var i=d[1];d=d[0],j.define(d,[j.events.activate]),d.on(j.events.activate,i,h)}else j.define(d,[j.events.activate]),d.on(j.events.activate,h);c.then(function(a){return a.getRoot().on(b.hidden,function(){null!==f&&f.focus()}),a})},n=function(b,c){c=a(c);var d=b.module,e=new d(c);return e},o=function(b,c){var d=b.template,e=h.render(d,c).then(function(c){var d=a(c);return n(b,d)}).fail(i.exception);return e},p=function(a,b){var d=a.type||l.DEFAULT,e=!!a.large,f=null,g={};f=c.get(d),f||i.exception({message:"Unable to find modal of type: "+d}),"undefined"!=typeof a.templateContext&&(g=a.templateContext);var h=o(f,g).then(function(b){return"undefined"!=typeof a.title&&b.setTitle(a.title),"undefined"!=typeof a.body&&b.setBody(a.body),"undefined"!=typeof a.footer&&b.setFooter(a.footer),e&&b.setLarge(),b});return"undefined"!=typeof b&&m(h,b,a),h};return{create:p,types:l}});
|
||||
define(["jquery","core/modal_events","core/modal_registry","core/modal","core/modal_save_cancel","core/modal_confirm","core/modal_cancel","core/templates","core/notification","core/custom_interaction_events","core/pending"],function(a,b,c,d,e,f,g,h,i,j,k){var l={DEFAULT:"core/modal",SAVE_CANCEL:"core/modal_save_cancel",CONFIRM:"core/modal_save_cancel",CANCEL:"core/modal_cancel"},m={DEFAULT:"DEFAULT",SAVE_CANCEL:"SAVE_CANCEL",CONFIRM:"CONFIRM",CANCEL:"CANCEL"};c.register(m.DEFAULT,d,l.DEFAULT),c.register(m.SAVE_CANCEL,e,l.SAVE_CANCEL),c.register(m.CONFIRM,f,l.CONFIRM),c.register(m.CANCEL,g,l.CANCEL);var n=function(c,d,e){var f=null,g="function"==typeof e.preShowCallback,h=function(b,d){var h=new k("core/modal_factory:setUpTrigger:triggeredCallback");f=a(b.currentTarget),c.then(function(a){return g&&e.preShowCallback(f,a),a.show(),a}).then(h.resolve),d.originalEvent.preventDefault()};if(Array.isArray(d)){var i=d[1];d=d[0],j.define(d,[j.events.activate]),d.on(j.events.activate,i,h)}else j.define(d,[j.events.activate]),d.on(j.events.activate,h);c.then(function(a){return a.getRoot().on(b.hidden,function(){null!==f&&f.focus()}),a})},o=function(b,c){c=a(c);var d=b.module,e=new d(c);return e},p=function(b,c){var d=b.template,e=h.render(d,c).then(function(c){var d=a(c);return o(b,d)}).fail(i.exception);return e},q=function(a,b){var d=a.type||m.DEFAULT,e=!!a.large,f=null,g={};f=c.get(d),f||i.exception({message:"Unable to find modal of type: "+d}),"undefined"!=typeof a.templateContext&&(g=a.templateContext);var h=p(f,g).then(function(b){return"undefined"!=typeof a.title&&b.setTitle(a.title),"undefined"!=typeof a.body&&b.setBody(a.body),"undefined"!=typeof a.footer&&b.setFooter(a.footer),e&&b.setLarge(),b});return"undefined"!=typeof b&&n(h,b,a),h};return{create:q,types:m}});
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["core/yui","jquery","core/log"],function(a,b,c){var d={types:{success:"core/notification_success",info:"core/notification_info",warning:"core/notification_warning",error:"core/notification_error"},fieldName:"user-notifications",fetchNotifications:function(){require(["core/ajax"],function(a){var b=a.call([{methodname:"core_fetch_notifications",args:{contextid:d.contextid}}]);b[0].done(d.addNotifications)})},addNotifications:function(a){a||(a=[]),b.each(a,function(a,b){d.renderNotification(b.template,b.variables)})},setupTargetRegion:function(){var a=b("#"+d.fieldName);if(a.length)return!1;var c=b("<span>").attr("id",d.fieldName);return a=b("#region-main"),a.length?a.prepend(c):(a=b('[role="main"]'),a.length?a.prepend(c):(a=b("body"),a.prepend(c)))},addNotification:function(a){var c=d.types.error;return a=b.extend({closebutton:!0,announce:!0,type:"error"},a),a.template?(c=a.template,delete a.template):a.type&&("undefined"!=typeof d.types[a.type]&&(c=d.types[a.type]),delete a.type),d.renderNotification(c,a)},renderNotification:function(a,e){return"undefined"!=typeof e.message&&e.message?void require(["core/templates"],function(c){c.render(a,e).done(function(a,e){b("#"+d.fieldName).prepend(a),c.runTemplateJS(e)}).fail(d.exception)}):void c.debug("Notification received without content. Skipping.")},alert:function(b,c,d){a.use("moodle-core-notification-alert",function(){var a=new M.core.alert({title:b,message:c,yesLabel:d});a.show()})},confirm:function(b,c,d,e,f,g){a.use("moodle-core-notification-confirm",function(){var a=new M.core.confirm({title:b,question:c,yesLabel:d,noLabel:e});a.on("complete-yes",function(){f()}),g&&a.on("complete-no",function(){g()}),a.show()})},exception:function(b){if("undefined"==typeof b.stack&&(b.stack=""),b.debuginfo&&(b.stack+=b.debuginfo+"\n"),!b.backtrace&&b.stacktrace&&(b.backtrace=b.stacktrace),b.backtrace){b.stack+=b.backtrace;var c=b.backtrace.match(/line ([^ ]*) of/),d=b.backtrace.match(/ of ([^:]*): /);c&&c[1]&&(b.lineNumber=c[1]),d&&d[1]&&(b.fileName=d[1],b.fileName.length>30&&(b.fileName="..."+b.fileName.substr(b.fileName.length-27)))}"undefined"==typeof b.name&&b.errorcode&&(b.name=b.errorcode),a.use("moodle-core-notification-exception",function(){var a=new M.core.exception(b);a.show()})}};return{init:function(a,b){d.contextid=a,d.setupTargetRegion(),d.addNotifications(b),d.fetchNotifications()},fetchNotifications:d.fetchNotifications,addNotification:d.addNotification,alert:d.alert,confirm:d.confirm,exception:d.exception}});
|
||||
define(["core/yui","jquery","core/log","core/pending"],function(a,b,c,d){var e={types:{success:"core/notification_success",info:"core/notification_info",warning:"core/notification_warning",error:"core/notification_error"},fieldName:"user-notifications",fetchNotifications:function(){var a=new d("core/notification:fetchNotifications");require(["core/ajax"],function(b){var c=b.call([{methodname:"core_fetch_notifications",args:{contextid:e.contextid}}]);c[0].then(e.addNotifications).always(a.resolve)})},addNotifications:function(a){var c=new d("core/notification:addNotifications");a||(a=[]),b.each(a,function(a,b){e.renderNotification(b.template,b.variables)}),c.resolve()},setupTargetRegion:function(){var a=b("#"+e.fieldName);if(a.length)return!1;var c=b("<span>").attr("id",e.fieldName);return a=b("#region-main"),a.length?a.prepend(c):(a=b('[role="main"]'),a.length?a.prepend(c):(a=b("body"),a.prepend(c)))},addNotification:function(a){var c=new d("core/notification:addNotifications"),f=e.types.error;return a=b.extend({closebutton:!0,announce:!0,type:"error"},a),a.template?(f=a.template,delete a.template):a.type&&("undefined"!=typeof e.types[a.type]&&(f=e.types[a.type]),delete a.type),c.resolve(),e.renderNotification(f,a)},renderNotification:function(a,f){if("undefined"==typeof f.message||!f.message)return void c.debug("Notification received without content. Skipping.");var g=new d("core/notification:renderNotification");require(["core/templates"],function(c){c.render(a,f).then(function(a,d){b("#"+e.fieldName).prepend(a),c.runTemplateJS(d)}).always(g.resolve)["catch"](e.exception)})},alert:function(b,c,e){var f=new d("core/notification:alert");a.use("moodle-core-notification-alert",function(){var a=new M.core.alert({title:b,message:c,yesLabel:e});a.show(),f.resolve()})},confirm:function(b,c,e,f,g,h){var i=new d("core/notification:confirm");a.use("moodle-core-notification-confirm",function(){var a=new M.core.confirm({title:b,question:c,yesLabel:e,noLabel:f});a.on("complete-yes",function(){g()}),h&&a.on("complete-no",function(){h()}),a.show(),i.resolve()})},exception:function(b){var c=new d("core/notification:addNotifications");if("undefined"==typeof b.stack&&(b.stack=""),b.debuginfo&&(b.stack+=b.debuginfo+"\n"),!b.backtrace&&b.stacktrace&&(b.backtrace=b.stacktrace),b.backtrace){b.stack+=b.backtrace;var e=b.backtrace.match(/line ([^ ]*) of/),f=b.backtrace.match(/ of ([^:]*): /);e&&e[1]&&(b.lineNumber=e[1]),f&&f[1]&&(b.fileName=f[1],b.fileName.length>30&&(b.fileName="..."+b.fileName.substr(b.fileName.length-27)))}"undefined"==typeof b.name&&b.errorcode&&(b.name=b.errorcode),a.use("moodle-core-notification-exception",function(){var a=new M.core.exception(b);a.show(),c.resolve()})}};return{init:function(a,b){e.contextid=a,e.setupTargetRegion(),e.addNotifications(b),e.fetchNotifications()},fetchNotifications:e.fetchNotifications,addNotification:e.addNotification,alert:e.alert,confirm:e.confirm,exception:e.exception}});
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+10
-4
@@ -23,8 +23,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
|
||||
'core/custom_interaction_events', 'core/modal_backdrop', 'core/event', 'core/modal_events'],
|
||||
function($, Templates, Notification, KeyCodes, CustomEvents, ModalBackdrop, Event, ModalEvents) {
|
||||
'core/custom_interaction_events', 'core/modal_backdrop', 'core/event', 'core/modal_events', 'core/pending'],
|
||||
function($, Templates, Notification, KeyCodes, CustomEvents, ModalBackdrop, Event, ModalEvents, Pending) {
|
||||
|
||||
var SELECTORS = {
|
||||
CONTAINER: '[data-region="modal-container"]',
|
||||
@@ -546,6 +546,8 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
|
||||
return;
|
||||
}
|
||||
|
||||
var pendingPromise = new Pending('core/modal:show');
|
||||
|
||||
if (this.hasFooterContent()) {
|
||||
this.showFooter();
|
||||
} else {
|
||||
@@ -556,7 +558,8 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
|
||||
this.attachToDOM();
|
||||
}
|
||||
|
||||
this.getBackdrop().done(function(backdrop) {
|
||||
this.getBackdrop()
|
||||
.then(function(backdrop) {
|
||||
var currentIndex = this.calculateZIndex();
|
||||
var newIndex = currentIndex + 2;
|
||||
var newBackdropIndex = newIndex - 1;
|
||||
@@ -569,7 +572,10 @@ define(['jquery', 'core/templates', 'core/notification', 'core/key_codes',
|
||||
this.getModal().focus();
|
||||
$('body').addClass('modal-open');
|
||||
this.root.trigger(ModalEvents.shown, this);
|
||||
}.bind(this));
|
||||
|
||||
return;
|
||||
}.bind(this))
|
||||
.then(pendingPromise.resolve);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
*/
|
||||
define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
|
||||
'core/modal_save_cancel', 'core/modal_confirm', 'core/modal_cancel',
|
||||
'core/templates', 'core/notification', 'core/custom_interaction_events'],
|
||||
'core/templates', 'core/notification', 'core/custom_interaction_events',
|
||||
'core/pending'],
|
||||
function($, ModalEvents, ModalRegistry, Modal, ModalSaveCancel, ModalConfirm,
|
||||
ModalCancel, Templates, Notification, CustomEvents) {
|
||||
ModalCancel, Templates, Notification, CustomEvents, Pending) {
|
||||
|
||||
// The templates for each type of modal.
|
||||
var TEMPLATES = {
|
||||
@@ -70,6 +71,7 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
|
||||
var hasPreShowCallback = (typeof modalConfig.preShowCallback == 'function');
|
||||
// Function to handle the trigger element being activated.
|
||||
var triggeredCallback = function(e, data) {
|
||||
var pendingPromise = new Pending('core/modal_factory:setUpTrigger:triggeredCallback');
|
||||
actualTriggerElement = $(e.currentTarget);
|
||||
modalPromise.then(function(modal) {
|
||||
if (hasPreShowCallback) {
|
||||
@@ -81,7 +83,8 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
|
||||
modal.show();
|
||||
|
||||
return modal;
|
||||
});
|
||||
})
|
||||
.then(pendingPromise.resolve);
|
||||
data.originalEvent.preventDefault();
|
||||
};
|
||||
|
||||
|
||||
+43
-14
@@ -26,8 +26,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 2.9
|
||||
*/
|
||||
define(['core/yui', 'jquery', 'core/log'],
|
||||
function(Y, $, log) {
|
||||
define(['core/yui', 'jquery', 'core/log', 'core/pending'],
|
||||
function(Y, $, log, Pending) {
|
||||
var notificationModule = {
|
||||
types: {
|
||||
'success': 'core/notification_success',
|
||||
@@ -39,6 +39,8 @@ function(Y, $, log) {
|
||||
fieldName: 'user-notifications',
|
||||
|
||||
fetchNotifications: function() {
|
||||
var pendingPromise = new Pending('core/notification:fetchNotifications');
|
||||
|
||||
require(['core/ajax'], function(ajax) {
|
||||
var promises = ajax.call([{
|
||||
methodname: 'core_fetch_notifications',
|
||||
@@ -47,13 +49,17 @@ function(Y, $, log) {
|
||||
}
|
||||
}]);
|
||||
|
||||
// This currently fails when not logged in.
|
||||
// eslint-disable-next-line promise/catch-or-return
|
||||
promises[0]
|
||||
.done(notificationModule.addNotifications)
|
||||
;
|
||||
.then(notificationModule.addNotifications)
|
||||
.always(pendingPromise.resolve);
|
||||
});
|
||||
},
|
||||
|
||||
addNotifications: function(notifications) {
|
||||
var pendingPromise = new Pending('core/notification:addNotifications');
|
||||
|
||||
if (!notifications) {
|
||||
notifications = [];
|
||||
}
|
||||
@@ -61,6 +67,8 @@ function(Y, $, log) {
|
||||
$.each(notifications, function(i, notification) {
|
||||
notificationModule.renderNotification(notification.template, notification.variables);
|
||||
});
|
||||
|
||||
pendingPromise.resolve();
|
||||
},
|
||||
|
||||
setupTargetRegion: function() {
|
||||
@@ -86,13 +94,15 @@ function(Y, $, log) {
|
||||
},
|
||||
|
||||
addNotification: function(notification) {
|
||||
var pendingPromise = new Pending('core/notification:addNotifications');
|
||||
|
||||
var template = notificationModule.types.error;
|
||||
|
||||
notification = $.extend({
|
||||
closebutton: true,
|
||||
announce: true,
|
||||
type: 'error'
|
||||
}, notification);
|
||||
closebutton: true,
|
||||
announce: true,
|
||||
type: 'error'
|
||||
}, notification);
|
||||
|
||||
if (notification.template) {
|
||||
template = notification.template;
|
||||
@@ -104,6 +114,8 @@ function(Y, $, log) {
|
||||
delete notification.type;
|
||||
}
|
||||
|
||||
pendingPromise.resolve();
|
||||
|
||||
return notificationModule.renderNotification(template, notification);
|
||||
},
|
||||
|
||||
@@ -112,18 +124,25 @@ function(Y, $, log) {
|
||||
log.debug('Notification received without content. Skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
var pendingPromise = new Pending('core/notification:renderNotification');
|
||||
|
||||
require(['core/templates'], function(templates) {
|
||||
templates.render(template, variables)
|
||||
.done(function(html, js) {
|
||||
$('#' + notificationModule.fieldName).prepend(html);
|
||||
templates.runTemplateJS(js);
|
||||
})
|
||||
.fail(notificationModule.exception)
|
||||
;
|
||||
.then(function(html, js) {
|
||||
$('#' + notificationModule.fieldName).prepend(html);
|
||||
templates.runTemplateJS(js);
|
||||
|
||||
return;
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notificationModule.exception);
|
||||
});
|
||||
},
|
||||
|
||||
alert: function(title, message, yesLabel) {
|
||||
var pendingPromise = new Pending('core/notification:alert');
|
||||
|
||||
// Here we are wrapping YUI. This allows us to start transitioning, but
|
||||
// wait for a good alternative without having inconsistent dialogues.
|
||||
Y.use('moodle-core-notification-alert', function() {
|
||||
@@ -134,10 +153,14 @@ function(Y, $, log) {
|
||||
});
|
||||
|
||||
alert.show();
|
||||
|
||||
pendingPromise.resolve();
|
||||
});
|
||||
},
|
||||
|
||||
confirm: function(title, question, yesLabel, noLabel, yesCallback, noCallback) {
|
||||
var pendingPromise = new Pending('core/notification:confirm');
|
||||
|
||||
// Here we are wrapping YUI. This allows us to start transitioning, but
|
||||
// wait for a good alternative without having inconsistent dialogues.
|
||||
Y.use('moodle-core-notification-confirm', function() {
|
||||
@@ -157,10 +180,14 @@ function(Y, $, log) {
|
||||
});
|
||||
}
|
||||
modal.show();
|
||||
|
||||
pendingPromise.resolve();
|
||||
});
|
||||
},
|
||||
|
||||
exception: function(ex) {
|
||||
var pendingPromise = new Pending('core/notification:addNotifications');
|
||||
|
||||
// Fudge some parameters.
|
||||
if (typeof ex.stack == 'undefined') {
|
||||
ex.stack = '';
|
||||
@@ -193,6 +220,8 @@ function(Y, $, log) {
|
||||
var modal = new M.core.exception(ex);
|
||||
|
||||
modal.show();
|
||||
|
||||
pendingPromise.resolve();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+164
-92
@@ -22,8 +22,25 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 3.0
|
||||
*/
|
||||
define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/modal_factory', 'core/modal_events'],
|
||||
function($, ajax, templates, notification, str, ModalFactory, ModalEvents) {
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/templates',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core/pending',
|
||||
], function(
|
||||
$,
|
||||
ajax,
|
||||
templates,
|
||||
notification,
|
||||
str,
|
||||
ModalFactory,
|
||||
ModalEvents,
|
||||
Pending
|
||||
) {
|
||||
return /** @alias module:core/tag */ {
|
||||
|
||||
/**
|
||||
@@ -34,27 +51,31 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
initTagindexPage: function() {
|
||||
// Click handler for changing tag type.
|
||||
$('body').delegate('.tagarea[data-ta] a[data-quickload=1]', 'click', function(e) {
|
||||
var pendingPromise = new Pending('core/tag:initTagindexPage');
|
||||
|
||||
e.preventDefault();
|
||||
var target = $(this),
|
||||
query = target[0].search.replace(/^\?/, ''),
|
||||
tagarea = target.closest('.tagarea[data-ta]'),
|
||||
args = query.split('&').reduce(function(s, c) {
|
||||
var target = $(this);
|
||||
var query = target[0].search.replace(/^\?/, '');
|
||||
var tagarea = target.closest('.tagarea[data-ta]');
|
||||
var args = query.split('&').reduce(function(s, c) {
|
||||
var t = c.split('=');
|
||||
s[t[0]] = decodeURIComponent(t[1]);
|
||||
return s;
|
||||
}, {});
|
||||
|
||||
var promises = ajax.call([{
|
||||
ajax.call([{
|
||||
methodname: 'core_tag_get_tagindex',
|
||||
args: {tagindex: args}
|
||||
}], true);
|
||||
|
||||
$.when.apply($, promises)
|
||||
.done(function(data) {
|
||||
templates.render('core_tag/index', data).done(function(html) {
|
||||
tagarea.replaceWith(html);
|
||||
});
|
||||
});
|
||||
}])[0]
|
||||
.then(function(data) {
|
||||
return templates.render('core_tag/index', data);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
templates.replaceNode(tagarea, html, js);
|
||||
return;
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -64,17 +85,29 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
* @method initManagePage
|
||||
*/
|
||||
initManagePage: function() {
|
||||
|
||||
// Set cell 'time modified' to 'now' when any of the element is updated in this row.
|
||||
$('body').on('updated', '[data-inplaceeditable]', function(e) {
|
||||
str.get_string('selecttag', 'core_tag', e.ajaxreturn.value)
|
||||
.then(function(s) {
|
||||
return $('label[for="tagselect' + e.ajaxreturn.itemid + '"]').html(s);
|
||||
})
|
||||
.fail(notification.exception);
|
||||
str.get_string('now').done(function(s) {
|
||||
$(e.target).closest('tr').find('td.col-timemodified').html(s);
|
||||
});
|
||||
var pendingPromise = new Pending('core/tag:initManagePage');
|
||||
|
||||
str.get_strings([
|
||||
{
|
||||
key: 'selecttag',
|
||||
component: 'core_tag',
|
||||
},
|
||||
{
|
||||
key: 'now',
|
||||
component: 'core',
|
||||
},
|
||||
])
|
||||
.then(function(result) {
|
||||
$('label[for="tagselect' + e.ajaxreturn.itemid + '"]').html(result[0]);
|
||||
$(e.target).closest('tr').find('td.col-timemodified').html(result[1]);
|
||||
|
||||
return;
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
|
||||
if (e.ajaxreturn.itemtype === 'tagflag') {
|
||||
var row = $(e.target).closest('tr');
|
||||
if (e.ajaxreturn.value === '0') {
|
||||
@@ -87,60 +120,76 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
|
||||
// Confirmation for single tag delete link.
|
||||
$('.tag-management-table').delegate('a.tagdelete', 'click', function(e) {
|
||||
var pendingPromise = new Pending('core/tag:tagdelete');
|
||||
|
||||
e.preventDefault();
|
||||
var href = $(this).attr('href');
|
||||
str.get_strings([
|
||||
{key: 'delete'},
|
||||
{key: 'confirmdeletetag', component: 'tag'},
|
||||
{key: 'yes'},
|
||||
{key: 'no'},
|
||||
]).done(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
window.location.href = href;
|
||||
});
|
||||
}
|
||||
);
|
||||
{key: 'delete', component: 'core'},
|
||||
{key: 'confirmdeletetag', component: 'tag'},
|
||||
{key: 'yes', component: 'core'},
|
||||
{key: 'no', component: 'core'},
|
||||
])
|
||||
.then(function(s) {
|
||||
return notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
window.location.href = href;
|
||||
});
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
|
||||
// Confirmation for bulk tag delete button.
|
||||
$("#tag-management-delete").click(function(e) {
|
||||
var form = $(this).closest('form').get(0),
|
||||
cnt = $(form).find("input[type=checkbox]:checked").length;
|
||||
var form = $(this).closest('form').get(0);
|
||||
|
||||
var cnt = $(form).find("input[type=checkbox]:checked").length;
|
||||
if (!cnt) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pendingPromise = new Pending('core/tag:tag-management-delete');
|
||||
var tempElement = $("<input type='hidden'/>").attr('name', this.name);
|
||||
e.preventDefault();
|
||||
str.get_strings([
|
||||
{key: 'delete'},
|
||||
{key: 'confirmdeletetags', component: 'tag'},
|
||||
{key: 'yes'},
|
||||
{key: 'no'},
|
||||
]).done(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
tempElement.appendTo(form);
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
);
|
||||
{key: 'delete', component: 'core'},
|
||||
{key: 'confirmdeletetags', component: 'tag'},
|
||||
{key: 'yes', component: 'core'},
|
||||
{key: 'no', component: 'core'},
|
||||
])
|
||||
.then(function(s) {
|
||||
return notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
tempElement.appendTo(form);
|
||||
form.submit();
|
||||
});
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
|
||||
// Confirmation for bulk tag combine button.
|
||||
$("#tag-management-combine").click(function(e) {
|
||||
var pendingPromise = new Pending('core/tag:tag-management-combine');
|
||||
|
||||
e.preventDefault();
|
||||
var form = $(this).closest('form').get(0),
|
||||
tags = $(form).find("input[type=checkbox]:checked");
|
||||
var form = $(this).closest('form').get(0);
|
||||
var tags = $(form).find("input[type=checkbox]:checked");
|
||||
|
||||
if (tags.length <= 1) {
|
||||
str.get_strings([
|
||||
{key: 'combineselected', component: 'tag'},
|
||||
{key: 'selectmultipletags', component: 'tag'},
|
||||
{key: 'ok'},
|
||||
]).done(function(s) {
|
||||
notification.alert(s[0], s[1], s[2]);
|
||||
}
|
||||
);
|
||||
])
|
||||
.then(function(s) {
|
||||
return notification.alert(s[0], s[1], s[2]);
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var tempElement = $("<input type='hidden'/>").attr('name', this.name);
|
||||
var saveButtonText = '';
|
||||
var tagOptions = [];
|
||||
@@ -155,8 +204,9 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
|
||||
str.get_strings([
|
||||
{key: 'combineselected', component: 'tag'},
|
||||
{key: 'continue'}
|
||||
]).then(function(langStrings) {
|
||||
{key: 'continue', component: 'core'}
|
||||
])
|
||||
.then(function(langStrings) {
|
||||
var modalTitle = langStrings[0];
|
||||
saveButtonText = langStrings[1];
|
||||
var templateContext = {
|
||||
@@ -167,9 +217,14 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
body: templates.render('core_tag/combine_tags', templateContext),
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
});
|
||||
}).then(function(modal) {
|
||||
})
|
||||
.then(function(modal) {
|
||||
modal.setSaveButtonText(saveButtonText);
|
||||
|
||||
return modal;
|
||||
})
|
||||
.then(function(modal) {
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function(e) {
|
||||
e.preventDefault();
|
||||
@@ -196,7 +251,9 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
|
||||
return;
|
||||
|
||||
}).catch(notification.exception);
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
|
||||
// When user changes tag name to some name that already exists suggest to combine the tags.
|
||||
@@ -205,43 +262,41 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
var newvalue = e.newvalue; // The value that user tried to udpated the element to.
|
||||
var tagid = $(e.target).attr('data-itemid');
|
||||
if (exception.errorcode === 'namesalreadybeeingused') {
|
||||
var pendingPromise = new Pending('core/tag:updatefailed');
|
||||
|
||||
e.preventDefault(); // This will prevent default error dialogue.
|
||||
str.get_strings([
|
||||
{key: 'nameuseddocombine', component: 'tag'},
|
||||
{key: 'yes'},
|
||||
{key: 'cancel'},
|
||||
]).done(function(s) {
|
||||
notification.confirm(e.message, s[0], s[1], s[2], function() {
|
||||
])
|
||||
.then(function(s) {
|
||||
return notification.confirm(e.message, s[0], s[1], s[2], function() {
|
||||
window.location.href = window.location.href + "&newname=" + encodeURIComponent(newvalue) +
|
||||
"&tagid=" + encodeURIComponent(tagid) +
|
||||
'&action=renamecombine&sesskey=' + M.cfg.sesskey;
|
||||
});
|
||||
});
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
}
|
||||
});
|
||||
|
||||
// Form for adding standard tags.
|
||||
$('body').on('click', 'a[data-action=addstandardtag]', function(e) {
|
||||
var pendingPromise = new Pending('core/tag:addstandardtag');
|
||||
e.preventDefault();
|
||||
|
||||
var saveButtonText = '';
|
||||
str.get_strings([
|
||||
{key: 'addotags', component: 'tag'},
|
||||
{key: 'continue'}
|
||||
]).then(function(langStrings) {
|
||||
var modalTitle = langStrings[0];
|
||||
saveButtonText = langStrings[1];
|
||||
var templateContext = {
|
||||
return ModalFactory.create({
|
||||
title: str.get_string('addotags', 'tag'),
|
||||
body: templates.render('core_tag/add_tags', {
|
||||
actionurl: window.location.href,
|
||||
sesskey: M.cfg.sesskey
|
||||
};
|
||||
return ModalFactory.create({
|
||||
title: modalTitle,
|
||||
body: templates.render('core_tag/add_tags', templateContext),
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
});
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(saveButtonText);
|
||||
}),
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
})
|
||||
.then(function(modal) {
|
||||
modal.setSaveButtonText(str.get_string('continue', 'core'));
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function(e) {
|
||||
@@ -285,7 +340,9 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
|
||||
return;
|
||||
|
||||
}).catch(notification.exception);
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -296,6 +353,8 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
*/
|
||||
initManageCollectionsPage: function() {
|
||||
$('body').on('updated', '[data-inplaceeditable]', function(e) {
|
||||
var pendingPromise = new Pending('core/tag:initManageCollectionsPage-updated');
|
||||
|
||||
var ajaxreturn = e.ajaxreturn,
|
||||
areaid, collid, isenabled;
|
||||
if (ajaxreturn.component === 'core_tag' && ajaxreturn.itemtype === 'tagareaenable') {
|
||||
@@ -319,9 +378,13 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
$(".tag-collections-table ul[data-collectionid=" + collid + "] li[data-areaid=" + areaid + "]").show();
|
||||
}
|
||||
}
|
||||
|
||||
pendingPromise.resolve();
|
||||
});
|
||||
|
||||
$('body').on('click', '.addtagcoll > a', function(e) {
|
||||
var pendingPromise = new Pending('core/tag:initManageCollectionsPage-addtagcoll');
|
||||
|
||||
e.preventDefault();
|
||||
var keys = [
|
||||
{
|
||||
@@ -329,13 +392,15 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
component: 'tag'
|
||||
},
|
||||
{
|
||||
key: 'create'
|
||||
key: 'create',
|
||||
component: 'core'
|
||||
}
|
||||
];
|
||||
|
||||
var href = $(this).attr('data-url');
|
||||
var saveButtonText = '';
|
||||
str.get_strings(keys).then(function(langStrings) {
|
||||
str.get_strings(keys)
|
||||
.then(function(langStrings) {
|
||||
var modalTitle = langStrings[0];
|
||||
saveButtonText = langStrings[1];
|
||||
var templateContext = {
|
||||
@@ -347,7 +412,8 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
body: templates.render('core_tag/add_tag_collection', templateContext),
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
});
|
||||
}).then(function(modal) {
|
||||
})
|
||||
.then(function(modal) {
|
||||
modal.setSaveButtonText(saveButtonText);
|
||||
|
||||
// Handle save event.
|
||||
@@ -388,25 +454,31 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
|
||||
modal.show();
|
||||
|
||||
return;
|
||||
return modal;
|
||||
|
||||
}).catch(notification.exception);
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
|
||||
$('body').on('click', '.tag-collections-table .action_delete', function(e) {
|
||||
var pendingPromise = new Pending('core/tag:initManageCollectionsPage-action_delete');
|
||||
|
||||
e.preventDefault();
|
||||
var href = $(this).attr('data-url') + '&sesskey=' + M.cfg.sesskey;
|
||||
str.get_strings([
|
||||
{key: 'delete'},
|
||||
{key: 'suredeletecoll', component: 'tag', param: $(this).attr('data-collname')},
|
||||
{key: 'yes'},
|
||||
{key: 'no'},
|
||||
]).done(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
window.location.href = href;
|
||||
});
|
||||
}
|
||||
);
|
||||
{key: 'delete'},
|
||||
{key: 'suredeletecoll', component: 'tag', param: $(this).attr('data-collname')},
|
||||
{key: 'yes'},
|
||||
{key: 'no'},
|
||||
])
|
||||
.then(function(s) {
|
||||
return notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
window.location.href = href;
|
||||
});
|
||||
})
|
||||
.always(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -328,20 +328,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
if (!$timeout) {
|
||||
$timeout = self::get_timeout();
|
||||
}
|
||||
if ($microsleep) {
|
||||
// Will sleep 1/10th of a second by default for self::get_timeout() seconds.
|
||||
$loops = $timeout * 10;
|
||||
} else {
|
||||
// Will sleep for self::get_timeout() seconds.
|
||||
$loops = $timeout;
|
||||
}
|
||||
|
||||
// DOM will never change on non-javascript case; do not wait or try again.
|
||||
if (!$this->running_javascript()) {
|
||||
$loops = 1;
|
||||
}
|
||||
$start = microtime(true);
|
||||
$end = $start + $timeout;
|
||||
|
||||
for ($i = 0; $i < $loops; $i++) {
|
||||
do {
|
||||
// We catch the exception thrown by the step definition to execute it again.
|
||||
try {
|
||||
// We don't check with !== because most of the time closures will return
|
||||
@@ -357,14 +348,13 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->running_javascript()) {
|
||||
if ($microsleep) {
|
||||
usleep(100000);
|
||||
} else {
|
||||
sleep(1);
|
||||
}
|
||||
if (!$this->running_javascript()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
|
||||
} while (microtime(true) < $end);
|
||||
|
||||
// Using coding_exception as is a development issue if no exception has been provided.
|
||||
if (!$exception) {
|
||||
|
||||
@@ -90,31 +90,54 @@ class behat_context_helper {
|
||||
* @return behat_base
|
||||
*/
|
||||
public static function get($classname) {
|
||||
if (self::$environment->hasContextClass($classname)) {
|
||||
return self::$environment->getContext($classname);
|
||||
$definedclassname = self::get_theme_override($classname);
|
||||
if ($definedclassname) {
|
||||
return self::$environment->getContext($definedclassname);
|
||||
}
|
||||
|
||||
// Just fall back on getContext to ensure that we throw the correct exception.
|
||||
return self::$environment->getContext($classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context for the specified component or subsystem.
|
||||
*
|
||||
* @param string $component The component or subsystem to find the context for
|
||||
* @return behat_base|null
|
||||
*/
|
||||
public static function get_component_context(string $component) {
|
||||
$component = str_replace('core_', '', $component);
|
||||
|
||||
if ($classname = self::get_theme_override("behat_{$component}")) {
|
||||
return self::get($classname);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for any theme override of the specified class name.
|
||||
*
|
||||
* @param string $classname
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function get_theme_override(string $classname) {
|
||||
$suitename = self::$environment->getSuite()->getName();
|
||||
// If default suite, then get the default theme name.
|
||||
if ($suitename == 'default') {
|
||||
$suitename = theme_config::DEFAULT_THEME;
|
||||
}
|
||||
$overridencontextname = 'behat_theme_'.$suitename.'_'.$classname;
|
||||
|
||||
// If contexts has not been checked before and doesn't exist then just use core one.
|
||||
if (!isset(self::$nonexistingcontexts[$overridencontextname])) {
|
||||
try {
|
||||
$subcontext = self::$environment->getContext($overridencontextname);
|
||||
|
||||
return $subcontext;
|
||||
} catch (Behat\Behat\Context\Exception\ContextNotFoundException $e) {
|
||||
// If context not found then it's not overridden.
|
||||
self::$nonexistingcontexts[$overridencontextname] = 1;
|
||||
}
|
||||
$overrideclassname = "behat_theme_{$suitename}_{$classname}";
|
||||
if (self::$environment->hasContextClass($overrideclassname)) {
|
||||
return $overrideclassname;
|
||||
}
|
||||
|
||||
// Get the actual context.
|
||||
return self::$environment->getContext($classname);
|
||||
if (self::$environment->hasContextClass($classname)) {
|
||||
return $classname;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,662 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data generators for acceptance testing.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2012 David Monllaó
|
||||
* @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.
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Behat data generator class for core entities.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2012 David Monllaó
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_core_generator extends behat_generator_base {
|
||||
|
||||
protected function get_creatable_entities(): array {
|
||||
return [
|
||||
'users' => [
|
||||
'datagenerator' => 'user',
|
||||
'required' => ['username'],
|
||||
],
|
||||
'categories' => [
|
||||
'datagenerator' => 'category',
|
||||
'required' => ['idnumber'],
|
||||
'switchids' => ['category' => 'parent'],
|
||||
],
|
||||
'courses' => [
|
||||
'datagenerator' => 'course',
|
||||
'required' => ['shortname'],
|
||||
'switchids' => ['category' => 'category'],
|
||||
],
|
||||
'groups' => [
|
||||
'datagenerator' => 'group',
|
||||
'required' => ['idnumber', 'course'],
|
||||
'switchids' => ['course' => 'courseid'],
|
||||
],
|
||||
'groupings' => [
|
||||
'datagenerator' => 'grouping',
|
||||
'required' => ['idnumber', 'course'],
|
||||
'switchids' => ['course' => 'courseid'],
|
||||
],
|
||||
'course enrolments' => [
|
||||
'datagenerator' => 'enrol_user',
|
||||
'required' => ['user', 'course', 'role'],
|
||||
'switchids' => ['user' => 'userid', 'course' => 'courseid', 'role' => 'roleid'],
|
||||
],
|
||||
'permission overrides' => [
|
||||
'datagenerator' => 'permission_override',
|
||||
'required' => ['capability', 'permission', 'role', 'contextlevel', 'reference'],
|
||||
'switchids' => ['role' => 'roleid'],
|
||||
],
|
||||
'system role assigns' => [
|
||||
'datagenerator' => 'system_role_assign',
|
||||
'required' => ['user', 'role'],
|
||||
'switchids' => ['user' => 'userid', 'role' => 'roleid'],
|
||||
],
|
||||
'role assigns' => [
|
||||
'datagenerator' => 'role_assign',
|
||||
'required' => ['user', 'role', 'contextlevel', 'reference'],
|
||||
'switchids' => ['user' => 'userid', 'role' => 'roleid'],
|
||||
],
|
||||
'activities' => [
|
||||
'datagenerator' => 'activity',
|
||||
'required' => ['activity', 'idnumber', 'course'],
|
||||
'switchids' => ['course' => 'course', 'gradecategory' => 'gradecat', 'grouping' => 'groupingid'],
|
||||
],
|
||||
'blocks' => [
|
||||
'datagenerator' => 'block_instance',
|
||||
'required' => ['blockname', 'contextlevel', 'reference'],
|
||||
],
|
||||
'group members' => [
|
||||
'datagenerator' => 'group_member',
|
||||
'required' => ['user', 'group'],
|
||||
'switchids' => ['user' => 'userid', 'group' => 'groupid'],
|
||||
],
|
||||
'grouping groups' => [
|
||||
'datagenerator' => 'grouping_group',
|
||||
'required' => ['grouping', 'group'],
|
||||
'switchids' => ['grouping' => 'groupingid', 'group' => 'groupid'],
|
||||
],
|
||||
'cohorts' => [
|
||||
'datagenerator' => 'cohort',
|
||||
'required' => ['idnumber'],
|
||||
],
|
||||
'cohort members' => [
|
||||
'datagenerator' => 'cohort_member',
|
||||
'required' => ['user', 'cohort'],
|
||||
'switchids' => ['user' => 'userid', 'cohort' => 'cohortid'],
|
||||
],
|
||||
'roles' => [
|
||||
'datagenerator' => 'role',
|
||||
'required' => ['shortname'],
|
||||
],
|
||||
'grade categories' => [
|
||||
'datagenerator' => 'grade_category',
|
||||
'required' => ['fullname', 'course'],
|
||||
'switchids' => ['course' => 'courseid', 'gradecategory' => 'parent'],
|
||||
],
|
||||
'grade items' => [
|
||||
'datagenerator' => 'grade_item',
|
||||
'required' => ['course'],
|
||||
'switchids' => [
|
||||
'scale' => 'scaleid',
|
||||
'outcome' => 'outcomeid',
|
||||
'course' => 'courseid',
|
||||
'gradecategory' => 'categoryid',
|
||||
],
|
||||
],
|
||||
'grade outcomes' => [
|
||||
'datagenerator' => 'grade_outcome',
|
||||
'required' => ['shortname', 'scale'],
|
||||
'switchids' => ['course' => 'courseid', 'gradecategory' => 'categoryid', 'scale' => 'scaleid'],
|
||||
],
|
||||
'scales' => [
|
||||
'datagenerator' => 'scale',
|
||||
'required' => ['name', 'scale'],
|
||||
'switchids' => ['course' => 'courseid'],
|
||||
],
|
||||
'question categories' => [
|
||||
'datagenerator' => 'question_category',
|
||||
'required' => ['name', 'contextlevel', 'reference'],
|
||||
'switchids' => ['questioncategory' => 'parent'],
|
||||
],
|
||||
'questions' => [
|
||||
'datagenerator' => 'question',
|
||||
'required' => ['qtype', 'questioncategory', 'name'],
|
||||
'switchids' => ['questioncategory' => 'category', 'user' => 'createdby'],
|
||||
],
|
||||
'tags' => [
|
||||
'datagenerator' => 'tag',
|
||||
'required' => ['name'],
|
||||
],
|
||||
'events' => [
|
||||
'datagenerator' => 'event',
|
||||
'required' => ['name', 'eventtype'],
|
||||
'switchids' => [
|
||||
'user' => 'userid',
|
||||
'course' => 'courseid',
|
||||
'category' => 'categoryid',
|
||||
],
|
||||
],
|
||||
'message contacts' => [
|
||||
'datagenerator' => 'message_contacts',
|
||||
'required' => ['user', 'contact'],
|
||||
'switchids' => ['user' => 'userid', 'contact' => 'contactid'],
|
||||
],
|
||||
'private messages' => [
|
||||
'datagenerator' => 'private_messages',
|
||||
'required' => ['user', 'contact', 'message'],
|
||||
'switchids' => ['user' => 'userid', 'contact' => 'contactid'],
|
||||
],
|
||||
'favourite conversations' => [
|
||||
'datagenerator' => 'favourite_conversations',
|
||||
'required' => ['user', 'contact'],
|
||||
'switchids' => ['user' => 'userid', 'contact' => 'contactid'],
|
||||
],
|
||||
'group messages' => [
|
||||
'datagenerator' => 'group_messages',
|
||||
'required' => ['user', 'group', 'message'],
|
||||
'switchids' => ['user' => 'userid', 'group' => 'groupid'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any empty custom fields, to avoid errors when creating the course.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function preprocess_course($data) {
|
||||
foreach ($data as $fieldname => $value) {
|
||||
if ($value === '' && strpos($fieldname, 'customfield_') === 0) {
|
||||
unset($data[$fieldname]);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* If password is not set it uses the username.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function preprocess_user($data) {
|
||||
if (!isset($data['password'])) {
|
||||
$data['password'] = $data['username'];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* If contextlevel and reference are specified for cohort, transform them to the contextid.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function preprocess_cohort($data) {
|
||||
if (isset($data['contextlevel'])) {
|
||||
if (!isset($data['reference'])) {
|
||||
throw new Exception('If field contextlevel is specified, field reference must also be present');
|
||||
}
|
||||
$context = $this->get_context($data['contextlevel'], $data['reference']);
|
||||
unset($data['contextlevel']);
|
||||
unset($data['reference']);
|
||||
$data['contextid'] = $context->id;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocesses the creation of a grade item. Converts gradetype text to a number.
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function preprocess_grade_item($data) {
|
||||
global $CFG;
|
||||
require_once("$CFG->libdir/grade/constants.php");
|
||||
|
||||
if (isset($data['gradetype'])) {
|
||||
$data['gradetype'] = constant("GRADE_TYPE_" . strtoupper($data['gradetype']));
|
||||
}
|
||||
|
||||
if (!empty($data['category']) && !empty($data['courseid'])) {
|
||||
$cat = grade_category::fetch(array('fullname' => $data['category'], 'courseid' => $data['courseid']));
|
||||
if (!$cat) {
|
||||
throw new Exception('Could not resolve category with name "' . $data['category'] . '"');
|
||||
}
|
||||
unset($data['category']);
|
||||
$data['categoryid'] = $cat->id;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter to modules generator.
|
||||
*
|
||||
* @throws Exception Custom exception for test writers
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_activity($data) {
|
||||
global $DB, $CFG;
|
||||
|
||||
// The the_following_exists() method checks that the field exists.
|
||||
$activityname = $data['activity'];
|
||||
unset($data['activity']);
|
||||
|
||||
// Convert scale name into scale id (negative number indicates using scale).
|
||||
if (isset($data['grade']) && strlen($data['grade']) && !is_number($data['grade'])) {
|
||||
$data['grade'] = - $this->get_scale_id($data['grade']);
|
||||
require_once("$CFG->libdir/grade/constants.php");
|
||||
|
||||
if (!isset($data['gradetype'])) {
|
||||
$data['gradetype'] = GRADE_TYPE_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
// We split $data in the activity $record and the course module $options.
|
||||
$cmoptions = array();
|
||||
$cmcolumns = $DB->get_columns('course_modules');
|
||||
foreach ($cmcolumns as $key => $value) {
|
||||
if (isset($data[$key])) {
|
||||
$cmoptions[$key] = $data[$key];
|
||||
}
|
||||
}
|
||||
|
||||
// Custom exception.
|
||||
try {
|
||||
$this->datagenerator->create_module($activityname, $data, $cmoptions);
|
||||
} catch (coding_exception $e) {
|
||||
throw new Exception('\'' . $activityname . '\' activity can not be added using this step,' .
|
||||
' use the step \'I add a "ACTIVITY_OR_RESOURCE_NAME_STRING" to section "SECTION_NUMBER"\' instead');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a block to a page.
|
||||
*
|
||||
* @param array $data should mostly match the fields of the block_instances table.
|
||||
* The block type is specified by blockname.
|
||||
* The parentcontextid is set from contextlevel and reference.
|
||||
* Missing values are filled in by testing_block_generator::prepare_record.
|
||||
* $data is passed to create_block as both $record and $options. Normally
|
||||
* the keys are different, so this is a way to let people set values in either place.
|
||||
*/
|
||||
protected function process_block_instance($data) {
|
||||
|
||||
if (empty($data['blockname'])) {
|
||||
throw new Exception('\'blocks\' requires the field \'block\' type to be specified');
|
||||
}
|
||||
|
||||
if (empty($data['contextlevel'])) {
|
||||
throw new Exception('\'blocks\' requires the field \'contextlevel\' to be specified');
|
||||
}
|
||||
|
||||
if (!isset($data['reference'])) {
|
||||
throw new Exception('\'blocks\' requires the field \'reference\' to be specified');
|
||||
}
|
||||
|
||||
$context = $this->get_context($data['contextlevel'], $data['reference']);
|
||||
$data['parentcontextid'] = $context->id;
|
||||
|
||||
// Pass $data as both $record and $options. I think that is unlikely to
|
||||
// cause problems since the relevant key names are different.
|
||||
// $options is not used in most blocks I have seen, but where it is, it is necessary.
|
||||
$this->datagenerator->create_block($data['blockname'], $data, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter to enrol_user() data generator.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_enrol_user($data) {
|
||||
global $SITE;
|
||||
|
||||
if (empty($data['roleid'])) {
|
||||
throw new Exception('\'course enrolments\' requires the field \'role\' to be specified');
|
||||
}
|
||||
|
||||
if (!isset($data['userid'])) {
|
||||
throw new Exception('\'course enrolments\' requires the field \'user\' to be specified');
|
||||
}
|
||||
|
||||
if (!isset($data['courseid'])) {
|
||||
throw new Exception('\'course enrolments\' requires the field \'course\' to be specified');
|
||||
}
|
||||
|
||||
if (!isset($data['enrol'])) {
|
||||
$data['enrol'] = 'manual';
|
||||
}
|
||||
|
||||
if (!isset($data['timestart'])) {
|
||||
$data['timestart'] = 0;
|
||||
}
|
||||
|
||||
if (!isset($data['timeend'])) {
|
||||
$data['timeend'] = 0;
|
||||
}
|
||||
|
||||
if (!isset($data['status'])) {
|
||||
$data['status'] = null;
|
||||
}
|
||||
|
||||
// If the provided course shortname is the site shortname we consider it a system role assign.
|
||||
if ($data['courseid'] == $SITE->id) {
|
||||
// Frontpage course assign.
|
||||
$context = context_course::instance($data['courseid']);
|
||||
role_assign($data['roleid'], $data['userid'], $context->id);
|
||||
|
||||
} else {
|
||||
// Course assign.
|
||||
$this->datagenerator->enrol_user($data['userid'], $data['courseid'], $data['roleid'], $data['enrol'],
|
||||
$data['timestart'], $data['timeend'], $data['status']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows/denies a capability at the specified context
|
||||
*
|
||||
* @throws Exception
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_permission_override($data) {
|
||||
|
||||
// Will throw an exception if it does not exist.
|
||||
$context = $this->get_context($data['contextlevel'], $data['reference']);
|
||||
|
||||
switch ($data['permission']) {
|
||||
case get_string('allow', 'role'):
|
||||
$permission = CAP_ALLOW;
|
||||
break;
|
||||
case get_string('prevent', 'role'):
|
||||
$permission = CAP_PREVENT;
|
||||
break;
|
||||
case get_string('prohibit', 'role'):
|
||||
$permission = CAP_PROHIBIT;
|
||||
break;
|
||||
default:
|
||||
throw new Exception('The \'' . $data['permission'] . '\' permission does not exist');
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_null(get_capability_info($data['capability']))) {
|
||||
throw new Exception('The \'' . $data['capability'] . '\' capability does not exist');
|
||||
}
|
||||
|
||||
role_change_permission($data['roleid'], $context, $data['capability'], $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a role to a user at system context
|
||||
*
|
||||
* Used by "system role assigns" can be deleted when
|
||||
* system role assign will be deprecated in favour of
|
||||
* "role assigns"
|
||||
*
|
||||
* @throws Exception
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_system_role_assign($data) {
|
||||
|
||||
if (empty($data['roleid'])) {
|
||||
throw new Exception('\'system role assigns\' requires the field \'role\' to be specified');
|
||||
}
|
||||
|
||||
if (!isset($data['userid'])) {
|
||||
throw new Exception('\'system role assigns\' requires the field \'user\' to be specified');
|
||||
}
|
||||
|
||||
$context = context_system::instance();
|
||||
|
||||
$this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a role to a user at the specified context
|
||||
*
|
||||
* @throws Exception
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_role_assign($data) {
|
||||
|
||||
if (empty($data['roleid'])) {
|
||||
throw new Exception('\'role assigns\' requires the field \'role\' to be specified');
|
||||
}
|
||||
|
||||
if (!isset($data['userid'])) {
|
||||
throw new Exception('\'role assigns\' requires the field \'user\' to be specified');
|
||||
}
|
||||
|
||||
if (empty($data['contextlevel'])) {
|
||||
throw new Exception('\'role assigns\' requires the field \'contextlevel\' to be specified');
|
||||
}
|
||||
|
||||
if (!isset($data['reference'])) {
|
||||
throw new Exception('\'role assigns\' requires the field \'reference\' to be specified');
|
||||
}
|
||||
|
||||
// Getting the context id.
|
||||
$context = $this->get_context($data['contextlevel'], $data['reference']);
|
||||
|
||||
$this->datagenerator->role_assign($data['roleid'], $data['userid'], $context->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a role.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_role($data) {
|
||||
|
||||
// We require the user to fill the role shortname.
|
||||
if (empty($data['shortname'])) {
|
||||
throw new Exception('\'role\' requires the field \'shortname\' to be specified');
|
||||
}
|
||||
|
||||
$this->datagenerator->create_role($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds members to cohorts
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_cohort_member($data) {
|
||||
cohort_add_member($data['cohortid'], $data['userid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a question category.
|
||||
*
|
||||
* @param array $data the row of data from the behat script.
|
||||
*/
|
||||
protected function process_question_category($data) {
|
||||
global $DB;
|
||||
|
||||
$context = $this->get_context($data['contextlevel'], $data['reference']);
|
||||
|
||||
// The way this class works, we have already looked up the given parent category
|
||||
// name and found a matching category. However, it is possible, particularly
|
||||
// for the 'top' category, for there to be several categories with the
|
||||
// same name. So far one will have been picked at random, but we need
|
||||
// the one from the right context. So, if we have the wrong category, try again.
|
||||
// (Just fixing it here, rather than getting it right first time, is a bit
|
||||
// of a bodge, but in general this class assumes that names are unique,
|
||||
// and normally they are, so this was the easiest fix.)
|
||||
if (!empty($data['parent'])) {
|
||||
$foundparent = $DB->get_record('question_categories', ['id' => $data['parent']], '*', MUST_EXIST);
|
||||
if ($foundparent->contextid != $context->id) {
|
||||
$rightparentid = $DB->get_field('question_categories', 'id',
|
||||
['contextid' => $context->id, 'name' => $foundparent->name]);
|
||||
if (!$rightparentid) {
|
||||
throw new Exception('The specified question category with name "' . $foundparent->name .
|
||||
'" does not exist in context "' . $context->get_context_name() . '"."');
|
||||
}
|
||||
$data['parent'] = $rightparentid;
|
||||
}
|
||||
}
|
||||
|
||||
$data['contextid'] = $context->id;
|
||||
$this->datagenerator->get_plugin_generator('core_question')->create_question_category($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a question.
|
||||
*
|
||||
* Creating questions relies on the question/type/.../tests/helper.php mechanism.
|
||||
* We start with test_question_maker::get_question_form_data($data['qtype'], $data['template'])
|
||||
* and then overlay the values from any other fields of $data that are set.
|
||||
*
|
||||
* @param array $data the row of data from the behat script.
|
||||
*/
|
||||
protected function process_question($data) {
|
||||
if (array_key_exists('questiontext', $data)) {
|
||||
$data['questiontext'] = array(
|
||||
'text' => $data['questiontext'],
|
||||
'format' => FORMAT_HTML,
|
||||
);
|
||||
}
|
||||
|
||||
if (array_key_exists('generalfeedback', $data)) {
|
||||
$data['generalfeedback'] = array(
|
||||
'text' => $data['generalfeedback'],
|
||||
'format' => FORMAT_HTML,
|
||||
);
|
||||
}
|
||||
|
||||
$which = null;
|
||||
if (!empty($data['template'])) {
|
||||
$which = $data['template'];
|
||||
}
|
||||
|
||||
$this->datagenerator->get_plugin_generator('core_question')->create_question($data['qtype'], $which, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds user to contacts
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_message_contacts($data) {
|
||||
\core_message\api::add_contact($data['userid'], $data['contactid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a new message from user to contact in a private conversation
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_private_messages(array $data) {
|
||||
if (empty($data['format'])) {
|
||||
$data['format'] = 'FORMAT_PLAIN';
|
||||
}
|
||||
|
||||
if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) {
|
||||
$conversation = \core_message\api::create_conversation(
|
||||
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
|
||||
[$data['userid'], $data['contactid']]
|
||||
);
|
||||
$conversationid = $conversation->id;
|
||||
}
|
||||
\core_message\api::send_message_to_conversation(
|
||||
$data['userid'],
|
||||
$conversationid,
|
||||
$data['message'],
|
||||
constant($data['format'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a new message from user to a group conversation
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_group_messages(array $data) {
|
||||
global $DB;
|
||||
|
||||
if (empty($data['format'])) {
|
||||
$data['format'] = 'FORMAT_PLAIN';
|
||||
}
|
||||
|
||||
$group = $DB->get_record('groups', ['id' => $data['groupid']]);
|
||||
$coursecontext = context_course::instance($group->courseid);
|
||||
if (!$conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $data['groupid'],
|
||||
$coursecontext->id)) {
|
||||
$members = $DB->get_records_menu('groups_members', ['groupid' => $data['groupid']], '', 'userid, id');
|
||||
$conversation = \core_message\api::create_conversation(
|
||||
\core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP,
|
||||
array_keys($members),
|
||||
$group->name,
|
||||
\core_message\api::MESSAGE_CONVERSATION_ENABLED,
|
||||
'core_group',
|
||||
'groups',
|
||||
$group->id,
|
||||
$coursecontext->id);
|
||||
}
|
||||
\core_message\api::send_message_to_conversation(
|
||||
$data['userid'],
|
||||
$conversation->id,
|
||||
$data['message'],
|
||||
constant($data['format'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a private conversation as favourite for user
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function process_favourite_conversations(array $data) {
|
||||
if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) {
|
||||
$conversation = \core_message\api::create_conversation(
|
||||
\core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
|
||||
[$data['userid'], $data['contactid']]
|
||||
);
|
||||
$conversationid = $conversation->id;
|
||||
}
|
||||
\core_message\api::set_favourite_conversation($conversationid, $data['userid']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Base class for data generators component support for acceptance testing.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2012 David Monllaó
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use Behat\Gherkin\Node\TableNode as TableNode;
|
||||
use Behat\Behat\Tester\Exception\PendingException as PendingException;
|
||||
|
||||
|
||||
/**
|
||||
* Class to quickly create Behat test data using component data generators.
|
||||
*
|
||||
* There is a subclass of class for each component that wants to be able to
|
||||
* generate entities using the Behat step
|
||||
* Given the following "entity types" exist:
|
||||
* | test | data |
|
||||
*
|
||||
* For core entities, the entity type is like "courses" or "users" and
|
||||
* generating those is handled by behat_core_generator. For other components
|
||||
* the entity type is like "mod_quiz > User override" and that is handled by
|
||||
* behat_mod_quiz_generator defined in mod/quiz/tests/generator/behat_mod_quiz_generator.php.
|
||||
*
|
||||
* The types of entities that can be generated are described by the array returned
|
||||
* by the {@link get_generateable_entities()} method. The list in
|
||||
* {@link behat_core_generator} is a good (if complex) example.
|
||||
*
|
||||
* How things work is best explained with a few examples. All this is implemented
|
||||
* in the {@link generate_items()} method below, if you want to see every detail of
|
||||
* how it works.
|
||||
*
|
||||
* Simple example from behat_core_generator:
|
||||
* 'users' => [
|
||||
* 'datagenerator' => 'user',
|
||||
* 'required' => ['username'],
|
||||
* ],
|
||||
* The steps performed are:
|
||||
*
|
||||
* 1. 'datagenerator' => 'user' means that the word used in the method names below is 'user'.
|
||||
*
|
||||
* 2. Because 'required' is present, check the supplied data exists 'username' column is present
|
||||
* in the supplied data table and if not display an error.
|
||||
*
|
||||
* 3. Then for each row in the table as an array $elementdata (array keys are column names)
|
||||
* and process it as follows
|
||||
*
|
||||
* 4. (Not used in this example.)
|
||||
*
|
||||
* 5. If the method 'preprocess_user' exists, then call it to update $elementdata.
|
||||
* (It does, in this case it sets the password to the username, if password was not given.)
|
||||
*
|
||||
* We then do one of 4 things:
|
||||
*
|
||||
* 6a. If there is a method 'process_user' we call it. (It doesn't for user,
|
||||
* but there are other examples like process_enrol_user() in behat_core_generator.)
|
||||
*
|
||||
* 6b. (Not used in this example.)
|
||||
*
|
||||
* 6c. Else, if testing_data_generator::create_user exists, we call it with $elementdata. (it does.)
|
||||
*
|
||||
* 6d. If none of these three things work. an error is thrown.
|
||||
*
|
||||
* To understand the missing steps above, consider the example from behat_mod_quiz_generator:
|
||||
* 'group override' => [
|
||||
* 'datagenerator' => 'override',
|
||||
* 'required' => ['quiz', 'group'],
|
||||
* 'switchids' => ['quiz' => 'quiz', 'group' => 'groupid'],
|
||||
* ],
|
||||
* Processing is as above, except that:
|
||||
*
|
||||
* 1. Note 'datagenerator' is 'override' (not group_override). 'user override' maps to the
|
||||
* same datagenerator. This works fine.
|
||||
*
|
||||
* 4. Because 'switchids' is present, human-readable data in the table gets converted to ids.
|
||||
* They array key 'group' refers to a column which may be present in the table (it will be
|
||||
* here because it is required, but it does not have to be in general). If that column
|
||||
* is present and contains a value, then the method matching name like get_group_id() is
|
||||
* called with the value from that column in the data table. You must implement this
|
||||
* method. You can see several examples of this sort of method below.
|
||||
*
|
||||
* If that method returns a group id, then $elementdata['group'] is unset and
|
||||
* $elementdata['groupid'] is set to the result of the get_group_id() call. 'groupid' here
|
||||
* because of the definition is 'switchids' => [..., 'group' => 'groupid'].
|
||||
* If get_group_id() cannot find the group, it should throw a helpful exception.
|
||||
*
|
||||
* Similarly, 'quiz' (the quiz name) is looked up with a call to get_quiz_id(). Here, the
|
||||
* new array key set matches the old one removed. This is fine.
|
||||
*
|
||||
* 6b. We are in a plugin, so before checking whether testing_data_generator::create_override
|
||||
* exists we first check whether mod_quiz_generator::create_override() exists. It does,
|
||||
* and this is what gets called.
|
||||
*
|
||||
* This second example shows why the get_..._id methods for core entities are in this base
|
||||
* class, not in behat_core_generator. Plugins may need to look up the ids of
|
||||
* core entities.
|
||||
*
|
||||
* behat_core_generator is defined in lib/behat/classes/behat_core_generator.php
|
||||
* and for components, behat_..._generator is defined in tests/generator/behat_..._generator.php
|
||||
* inside the plugin. For example behat_mod_quiz_generator is defined in
|
||||
* mod/quiz/tests/generator/behat_mod_quiz_generator.php.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2012 David Monllaó
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class behat_generator_base {
|
||||
|
||||
/**
|
||||
* @var string the name of the component we belong to.
|
||||
*
|
||||
* This should probably only be used to make error messages clearer.
|
||||
*/
|
||||
protected $component;
|
||||
|
||||
/**
|
||||
* @var testing_data_generator the core data generator
|
||||
*/
|
||||
protected $datagenerator;
|
||||
|
||||
/**
|
||||
* @var testing_data_generator the data generator for this component.
|
||||
*/
|
||||
protected $componentdatagenerator;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $component component name, to make error messages more readable.
|
||||
*/
|
||||
public function __construct(string $component) {
|
||||
$this->component = $component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the entities that can be created for this component.
|
||||
*
|
||||
* This function must be overridden in subclasses. See class comment
|
||||
* above for a description of the data structure.
|
||||
* See {@link behat_core_generator} for an example.
|
||||
*
|
||||
* @return array entity name => information about how to generate.
|
||||
*/
|
||||
protected abstract function get_creatable_entities(): array;
|
||||
|
||||
/**
|
||||
* Do the work to generate an entity.
|
||||
*
|
||||
* This is called by {@link behat_data_generators::the_following_entities_exist()}.
|
||||
*
|
||||
* @param string $generatortype The name of the entity to create.
|
||||
* @param TableNode $data from the step.
|
||||
*/
|
||||
public function generate_items(string $generatortype, TableNode $data) {
|
||||
// Now that we need them require the data generators.
|
||||
require_once(__DIR__ . '/../../testing/generator/lib.php');
|
||||
|
||||
$elements = $this->get_creatable_entities();
|
||||
|
||||
if (!isset($elements[$generatortype])) {
|
||||
throw new PendingException($this->name_for_errors($generatortype) .
|
||||
' is not a known type of entity that can be generated.');
|
||||
}
|
||||
$entityinfo = $elements[$generatortype];
|
||||
|
||||
$this->datagenerator = testing_util::get_data_generator();
|
||||
if ($this->component === 'core') {
|
||||
$this->componentdatagenerator = $this->datagenerator;
|
||||
} else {
|
||||
$this->componentdatagenerator = $this->datagenerator->get_plugin_generator($this->component);
|
||||
}
|
||||
|
||||
$generatortype = $entityinfo['datagenerator'];
|
||||
|
||||
foreach ($data->getHash() as $elementdata) {
|
||||
|
||||
// Check if all the required fields are there.
|
||||
foreach ($entityinfo['required'] as $requiredfield) {
|
||||
if (!isset($elementdata[$requiredfield])) {
|
||||
throw new Exception($this->name_for_errors($generatortype) .
|
||||
' requires the field ' . $requiredfield . ' to be specified');
|
||||
}
|
||||
}
|
||||
|
||||
// Switch from human-friendly references to ids.
|
||||
if (!empty($entityinfo['switchids'])) {
|
||||
foreach ($entityinfo['switchids'] as $element => $field) {
|
||||
$methodname = 'get_' . $element . '_id';
|
||||
|
||||
// Not all the switch fields are required, default vars will be assigned by data generators.
|
||||
if (isset($elementdata[$element])) {
|
||||
if (!method_exists($this, $methodname)) {
|
||||
throw new coding_exception('The generator for ' .
|
||||
$this->name_for_errors($generatortype) .
|
||||
' entities specifies \'switchids\' => [..., \'' . $element .
|
||||
'\' => \'' . $field . '\', ...] but the required method ' .
|
||||
$methodname . '() has not been defined in ' .
|
||||
get_class($this) . '.');
|
||||
}
|
||||
// Temp $id var to avoid problems when $element == $field.
|
||||
$id = $this->{$methodname}($elementdata[$element]);
|
||||
unset($elementdata[$element]);
|
||||
$elementdata[$field] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preprocess the entities that requires a special treatment.
|
||||
if (method_exists($this, 'preprocess_' . $generatortype)) {
|
||||
$elementdata = $this->{'preprocess_' . $generatortype}($elementdata);
|
||||
}
|
||||
|
||||
// Creates element.
|
||||
if (method_exists($this, 'process_' . $generatortype)) {
|
||||
// Use a method on this class to do the work.
|
||||
$this->{'process_' . $generatortype}($elementdata);
|
||||
|
||||
} else if (method_exists($this->componentdatagenerator, 'create_' . $generatortype)) {
|
||||
// Using the component't own data generator if it exists.
|
||||
$this->componentdatagenerator->{'create_' . $generatortype}($elementdata);
|
||||
|
||||
} else if (method_exists($this->datagenerator, 'create_' . $generatortype)) {
|
||||
// Use a method on the core data geneator, if there is one.
|
||||
$this->datagenerator->{'create_' . $generatortype}($elementdata);
|
||||
|
||||
} else {
|
||||
// Give up.
|
||||
throw new PendingException($this->name_for_errors($generatortype) .
|
||||
' data generator is not implemented');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for formatting error messages.
|
||||
*
|
||||
* @param string $entitytype entity type without prefix, e.g. 'frog'.
|
||||
* @return string either 'frog' for core entities, or 'mod_mymod > frog' for components.
|
||||
*/
|
||||
protected function name_for_errors(string $entitytype): string {
|
||||
if ($this->component === 'core') {
|
||||
return '"' . $entitytype . '"';
|
||||
} else {
|
||||
return '"' . $this->component . ' > ' . $entitytype . '"';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the grade category id from the grade category fullname
|
||||
*
|
||||
* @param string $fullname the grade category name.
|
||||
* @return int corresponding id.
|
||||
*/
|
||||
protected function get_gradecategory_id($fullname) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('grade_categories', 'id', array('fullname' => $fullname))) {
|
||||
throw new Exception('The specified grade category with fullname "' . $fullname . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user id from it's username.
|
||||
* @throws Exception
|
||||
* @param string $username
|
||||
* @return int
|
||||
*/
|
||||
protected function get_user_id($username) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('user', 'id', array('username' => $username))) {
|
||||
throw new Exception('The specified user with username "' . $username . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the role id from it's shortname.
|
||||
* @throws Exception
|
||||
* @param string $roleshortname
|
||||
* @return int
|
||||
*/
|
||||
protected function get_role_id($roleshortname) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('role', 'id', array('shortname' => $roleshortname))) {
|
||||
throw new Exception('The specified role with shortname "' . $roleshortname . '" does not exist');
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the category id from it's idnumber.
|
||||
* @throws Exception
|
||||
* @param string $idnumber
|
||||
* @return int
|
||||
*/
|
||||
protected function get_category_id($idnumber) {
|
||||
global $DB;
|
||||
|
||||
// If no category was specified use the data generator one.
|
||||
if ($idnumber == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$id = $DB->get_field('course_categories', 'id', array('idnumber' => $idnumber))) {
|
||||
throw new Exception('The specified category with idnumber "' . $idnumber . '" does not exist');
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the course id from it's shortname.
|
||||
* @throws Exception
|
||||
* @param string $shortname
|
||||
* @return int
|
||||
*/
|
||||
protected function get_course_id($shortname) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('course', 'id', array('shortname' => $shortname))) {
|
||||
throw new Exception('The specified course with shortname "' . $shortname . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the group id from it's idnumber.
|
||||
* @throws Exception
|
||||
* @param string $idnumber
|
||||
* @return int
|
||||
*/
|
||||
protected function get_group_id($idnumber) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('groups', 'id', array('idnumber' => $idnumber))) {
|
||||
throw new Exception('The specified group with idnumber "' . $idnumber . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the grouping id from it's idnumber.
|
||||
* @throws Exception
|
||||
* @param string $idnumber
|
||||
* @return int
|
||||
*/
|
||||
protected function get_grouping_id($idnumber) {
|
||||
global $DB;
|
||||
|
||||
// Do not fetch grouping ID for empty grouping idnumber.
|
||||
if (empty($idnumber)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$id = $DB->get_field('groupings', 'id', array('idnumber' => $idnumber))) {
|
||||
throw new Exception('The specified grouping with idnumber "' . $idnumber . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cohort id from it's idnumber.
|
||||
* @throws Exception
|
||||
* @param string $idnumber
|
||||
* @return int
|
||||
*/
|
||||
protected function get_cohort_id($idnumber) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('cohort', 'id', array('idnumber' => $idnumber))) {
|
||||
throw new Exception('The specified cohort with idnumber "' . $idnumber . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the outcome item id from its shortname.
|
||||
* @throws Exception
|
||||
* @param string $shortname
|
||||
* @return int
|
||||
*/
|
||||
protected function get_outcome_id($shortname) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('grade_outcomes', 'id', array('shortname' => $shortname))) {
|
||||
throw new Exception('The specified outcome with shortname "' . $shortname . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of a named scale.
|
||||
* @param string $name the name of the scale.
|
||||
* @return int the scale id.
|
||||
*/
|
||||
protected function get_scale_id($name) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('scale', 'id', array('name' => $name))) {
|
||||
throw new Exception('The specified scale with name "' . $name . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of a named question category (must be globally unique).
|
||||
* Note that 'Top' is a special value, used when setting the parent of another
|
||||
* category, meaning top-level.
|
||||
*
|
||||
* @param string $name the question category name.
|
||||
* @return int the question category id.
|
||||
*/
|
||||
protected function get_questioncategory_id($name) {
|
||||
global $DB;
|
||||
|
||||
if ($name == 'Top') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$id = $DB->get_field('question_categories', 'id', array('name' => $name))) {
|
||||
throw new Exception('The specified question category with name "' . $name . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal context id from the context reference.
|
||||
*
|
||||
* The context reference changes depending on the context
|
||||
* level, it can be the system, a user, a category, a course or
|
||||
* a module.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param string $levelname The context level string introduced by the test writer
|
||||
* @param string $contextref The context reference introduced by the test writer
|
||||
* @return context
|
||||
*/
|
||||
protected function get_context($levelname, $contextref) {
|
||||
global $DB;
|
||||
|
||||
// Getting context levels and names (we will be using the English ones as it is the test site language).
|
||||
$contextlevels = context_helper::get_all_levels();
|
||||
$contextnames = array();
|
||||
foreach ($contextlevels as $level => $classname) {
|
||||
$contextnames[context_helper::get_level_name($level)] = $level;
|
||||
}
|
||||
|
||||
if (empty($contextnames[$levelname])) {
|
||||
throw new Exception('The specified "' . $levelname . '" context level does not exist');
|
||||
}
|
||||
$contextlevel = $contextnames[$levelname];
|
||||
|
||||
// Return it, we don't need to look for other internal ids.
|
||||
if ($contextlevel == CONTEXT_SYSTEM) {
|
||||
return context_system::instance();
|
||||
}
|
||||
|
||||
switch ($contextlevel) {
|
||||
|
||||
case CONTEXT_USER:
|
||||
$instanceid = $DB->get_field('user', 'id', array('username' => $contextref));
|
||||
break;
|
||||
|
||||
case CONTEXT_COURSECAT:
|
||||
$instanceid = $DB->get_field('course_categories', 'id', array('idnumber' => $contextref));
|
||||
break;
|
||||
|
||||
case CONTEXT_COURSE:
|
||||
$instanceid = $DB->get_field('course', 'id', array('shortname' => $contextref));
|
||||
break;
|
||||
|
||||
case CONTEXT_MODULE:
|
||||
$instanceid = $DB->get_field('course_modules', 'id', array('idnumber' => $contextref));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$contextclass = $contextlevels[$contextlevel];
|
||||
if (!$context = $contextclass::instance($instanceid, IGNORE_MISSING)) {
|
||||
throw new Exception('The specified "' . $contextref . '" context reference does not exist');
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contact id from it's username.
|
||||
* @throws Exception
|
||||
* @param string $username
|
||||
* @return int
|
||||
*/
|
||||
protected function get_contact_id($username) {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('user', 'id', array('username' => $username))) {
|
||||
throw new Exception('The specified user with username "' . $username . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ require_once(__DIR__ . '/../../filelib.php');
|
||||
require_once(__DIR__ . '/../../clilib.php');
|
||||
|
||||
use Behat\Mink\Session;
|
||||
use Behat\Mink\Exception\ExpectationException;
|
||||
|
||||
/**
|
||||
* Init/reset utilities for Behat database and dataroot
|
||||
|
||||
@@ -86,6 +86,7 @@ class behat_form_autocomplete extends behat_form_text {
|
||||
|
||||
$this->wait_for_pending_js();
|
||||
$this->key_press(27);
|
||||
$this->wait_for_pending_js();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,4 +224,25 @@ final class ip_utils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an ip in a given list of subnets?
|
||||
*
|
||||
* @param string $ip - the IP to test against the list
|
||||
* @param string $list - the list of IP subnets
|
||||
* @param string $delim a delimiter of the list
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_ip_in_subnet_list($ip, $list, $delim = "\n") {
|
||||
$list = explode($delim, $list);
|
||||
foreach ($list as $line) {
|
||||
$tokens = explode('#', $line);
|
||||
$subnet = trim($tokens[0]);
|
||||
if (address_in_subnet($ip, $subnet)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -260,7 +260,27 @@ class manager {
|
||||
|
||||
// Set configuration.
|
||||
session_name($sessionname);
|
||||
session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $cookiesecure, $CFG->cookiehttponly);
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
|
||||
$sessionoptions = [
|
||||
'lifetime' => 0,
|
||||
'path' => $CFG->sessioncookiepath,
|
||||
'domain' => $CFG->sessioncookiedomain,
|
||||
'secure' => $cookiesecure,
|
||||
'httponly' => $CFG->cookiehttponly,
|
||||
];
|
||||
|
||||
if (self::should_use_samesite_none()) {
|
||||
// If $samesite is empty, we don't want there to be any SameSite attribute.
|
||||
$sessionoptions['samesite'] = 'None';
|
||||
}
|
||||
|
||||
session_set_cookie_params($sessionoptions);
|
||||
} else {
|
||||
// Once PHP 7.3 becomes our minimum, drop this in favour of the alternative call to session_set_cookie_params above,
|
||||
// as that does not require a hack to work with same site settings on cookies.
|
||||
session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $cookiesecure, $CFG->cookiehttponly);
|
||||
}
|
||||
ini_set('session.use_trans_sid', '0');
|
||||
ini_set('session.use_only_cookies', '1');
|
||||
ini_set('session.hash_function', '0'); // For now MD5 - we do not have room for sha-1 in sessions table.
|
||||
@@ -420,6 +440,8 @@ class manager {
|
||||
if ($timedout) {
|
||||
$_SESSION['SESSION']->has_timed_out = true;
|
||||
}
|
||||
|
||||
self::append_samesite_cookie_attribute();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,6 +509,61 @@ class manager {
|
||||
|
||||
// Setup $USER object.
|
||||
self::set_user($user);
|
||||
self::append_samesite_cookie_attribute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid setting for the SameSite cookie attribute.
|
||||
*
|
||||
* @return string The desired setting for the SameSite attribute on the cookie. Empty string indicates the SameSite attribute
|
||||
* should not be set at all.
|
||||
*/
|
||||
private static function should_use_samesite_none() {
|
||||
// We only want None or no attribute at this point. When we have cookie handling compatible with Lax,
|
||||
// we can look at checking a setting.
|
||||
|
||||
// Browser support for none is not consistent yet. There are known issues with Safari, and IE11.
|
||||
// Things are stablising, however as they're not stable yet we will deal specifically with the version of chrome
|
||||
// that introduces a default of lax, setting it to none for the current version of chrome (2 releases before the change).
|
||||
// We also check you are using secure cookies and HTTPS because if you are not running over HTTPS
|
||||
// then setting SameSite=None will cause your session cookie to be rejected.
|
||||
if (\core_useragent::is_chrome() && \core_useragent::check_chrome_version('78') && is_moodle_cookie_secure()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally append the SameSite attribute to the session cookie if necessary.
|
||||
*
|
||||
* Contains a hack for versions of PHP lower than 7.3 as there is no API built into PHP cookie API
|
||||
* for adding the SameSite setting.
|
||||
*
|
||||
* This won't change the Set-Cookie headers if:
|
||||
* - PHP 7.3 or higher is being used. That already adds the SameSite attribute without any hacks.
|
||||
* - If the samesite setting is empty.
|
||||
* - If the samesite setting is None but the browser is not compatible with that setting.
|
||||
*/
|
||||
private static function append_samesite_cookie_attribute() {
|
||||
if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
|
||||
// This hack is only necessary if we weren't able to set the samesite flag via the session_set_cookie_params API.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self::should_use_samesite_none()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cookies = headers_list();
|
||||
header_remove('Set-Cookie');
|
||||
$setcookiesession = 'Set-Cookie: ' . session_name() . '=';
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
if (strpos($cookie, $setcookiesession) === 0) {
|
||||
$cookie .= '; SameSite=None';
|
||||
}
|
||||
header($cookie, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -523,6 +600,8 @@ class manager {
|
||||
self::add_session_record($_SESSION['USER']->id); // Do not use $USER here because it may not be set up yet.
|
||||
session_write_close();
|
||||
self::$sessionactive = false;
|
||||
|
||||
self::append_samesite_cookie_attribute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,4 +27,4 @@ unzip v1.1.3.zip
|
||||
mv GeoIP2-php-2.6.0/src/ /path/to/moodle/lib/maxmind/GeoIp2/
|
||||
mv MaxMind-DB-Reader-php-1.1.3/src/MaxMind/ /path/to/moodle/lib/maxmind/MaxMind/
|
||||
|
||||
4) Run unit tests on iplookup/tests/geoip_test.php with PHPUNIT_LONGTEST defined.
|
||||
4) Run unit tests on iplookup/tests/geoip_test.php.
|
||||
|
||||
+10
-13
@@ -9053,24 +9053,13 @@ function cleardoubleslashes ($path) {
|
||||
* @return bool
|
||||
*/
|
||||
function remoteip_in_list($list) {
|
||||
$inlist = false;
|
||||
$clientip = getremoteaddr(null);
|
||||
|
||||
if (!$clientip) {
|
||||
// Ensure access on cli.
|
||||
return true;
|
||||
}
|
||||
|
||||
$list = explode("\n", $list);
|
||||
foreach ($list as $line) {
|
||||
$tokens = explode('#', $line);
|
||||
$subnet = trim($tokens[0]);
|
||||
if (address_in_subnet($clientip, $subnet)) {
|
||||
$inlist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $inlist;
|
||||
return \core\ip_utils::is_ip_in_subnet_list($clientip, $list);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -9098,7 +9087,15 @@ function getremoteaddr($default='0.0.0.0') {
|
||||
if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$forwardedaddresses = explode(",", $_SERVER['HTTP_X_FORWARDED_FOR']);
|
||||
$address = $forwardedaddresses[0];
|
||||
|
||||
$forwardedaddresses = array_filter($forwardedaddresses, function($ip) {
|
||||
global $CFG;
|
||||
return !\core\ip_utils::is_ip_in_subnet_list($ip, $CFG->reverseproxyignore ?? '', ',');
|
||||
});
|
||||
|
||||
// Multiple proxies can append values to this header including an
|
||||
// untrusted original request header so we must only trust the last ip.
|
||||
$address = end($forwardedaddresses);
|
||||
|
||||
if (substr_count($address, ":") > 1) {
|
||||
// Remove port and brackets from IPv6.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -990,11 +990,7 @@ EOF;
|
||||
// Using the spin method as we want a reduced timeout but there is no need for a 0.1 seconds interval
|
||||
// because in the optimistic case we will timeout.
|
||||
// If all goes good it will throw an ElementNotFoundExceptionn that we will catch.
|
||||
$this->spin(
|
||||
function($context, $args) use ($selectortype, $element) {
|
||||
return $this->find($selectortype, $element);
|
||||
}, [], behat_base::get_reduced_timeout(), $exception, false
|
||||
);
|
||||
return $this->find($selectortype, $element, $exception, false, behat_base::get_reduced_timeout());
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// We expect the element to not be found.
|
||||
return;
|
||||
|
||||
@@ -687,20 +687,12 @@ class behat_hooks extends behat_base {
|
||||
* @param string $component
|
||||
*/
|
||||
public function register_component_selectors_for_component(string $component) {
|
||||
$componentclassname = "behat_{$component}";
|
||||
$context = behat_context_helper::get_component_context($component);
|
||||
|
||||
if (!behat_context_helper::has_context($componentclassname)) {
|
||||
if ("core_" === substr($component, 0, 5)) {
|
||||
$componentclassname = "behat_" . substr($component, 5);
|
||||
if (!behat_context_helper::has_context($componentclassname)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if ($context === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$context = behat_context_helper::get($componentclassname);
|
||||
$namedpartial = $this->getSession()->getSelectorsHandler()->getSelector('named_partial');
|
||||
$namedexact = $this->getSession()->getSelectorsHandler()->getSelector('named_exact');
|
||||
|
||||
|
||||
@@ -376,4 +376,32 @@ class core_ip_utils_testcase extends basic_testcase {
|
||||
[false, 'trouble.com.au'] // The allowed domain (above) has a space at the front and so will return false.
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_is_ip_in_subnet_list.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function data_is_ip_in_subnet_list() {
|
||||
return [
|
||||
[true, '1.1.1.1', '1.1.1.1', "\n"],
|
||||
[false, '1.1.1.1', '2.2.2.2', "\n"],
|
||||
[true, '1.1.1.1', "1.1.1.5\n1.1.1.1", "\n"],
|
||||
[true, '1.1.1.1', "1.1.1.5,1.1.1.1", ","],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test checking ips against a list of allowed domains.
|
||||
*
|
||||
* @param bool $expected Expected result
|
||||
* @param string $ip IP address
|
||||
* @param string $list list of IP subnets
|
||||
* @param string $delim delimiter of list
|
||||
* @dataProvider data_is_ip_in_subnet_list
|
||||
*/
|
||||
public function test_is_ip_in_subnet_list($expected, $ip, $list, $delim) {
|
||||
$this->assertEquals($expected, \core\ip_utils::is_ip_in_subnet_list($ip, $list, $delim));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3601,6 +3601,10 @@ class core_moodlelib_testcase extends advanced_testcase {
|
||||
* Tests the getremoteaddr() function.
|
||||
*/
|
||||
public function test_getremoteaddr() {
|
||||
global $CFG;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$CFG->getremoteaddrconf = GETREMOTEADDR_SKIP_HTTP_CLIENT_IP;
|
||||
$xforwardedfor = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : null;
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '';
|
||||
@@ -3617,27 +3621,27 @@ class core_moodlelib_testcase extends advanced_testcase {
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2';
|
||||
$twoip = getremoteaddr();
|
||||
$this->assertEquals('127.0.0.1', $twoip);
|
||||
$this->assertEquals('127.0.0.2', $twoip);
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2, 127.0.0.3';
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2,127.0.0.3';
|
||||
$threeip = getremoteaddr();
|
||||
$this->assertEquals('127.0.0.1', $threeip);
|
||||
$this->assertEquals('127.0.0.3', $threeip);
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1:65535,127.0.0.2';
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,127.0.0.2:65535';
|
||||
$portip = getremoteaddr();
|
||||
$this->assertEquals('127.0.0.1', $portip);
|
||||
$this->assertEquals('127.0.0.2', $portip);
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '0:0:0:0:0:0:0:1,127.0.0.2';
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,0:0:0:0:0:0:0:2';
|
||||
$portip = getremoteaddr();
|
||||
$this->assertEquals('0:0:0:0:0:0:0:1', $portip);
|
||||
$this->assertEquals('0:0:0:0:0:0:0:2', $portip);
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '0::1,127.0.0.2';
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,0::2';
|
||||
$portip = getremoteaddr();
|
||||
$this->assertEquals('0:0:0:0:0:0:0:1', $portip);
|
||||
$this->assertEquals('0:0:0:0:0:0:0:2', $portip);
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '[0:0:0:0:0:0:0:1]:65535,127.0.0.2';
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,[0:0:0:0:0:0:0:2]:65535';
|
||||
$portip = getremoteaddr();
|
||||
$this->assertEquals('0:0:0:0:0:0:0:1', $portip);
|
||||
$this->assertEquals('0:0:0:0:0:0:0:2', $portip);
|
||||
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = $xforwardedfor;
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
This files describes API changes in core libraries and APIs,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.6.8 ===
|
||||
|
||||
* behat_data_generators::the_following_exist() has been removed, please use
|
||||
behat_data_generators::the_following_entities_exist() instead. See MDL-67691 for more info.
|
||||
|
||||
=== 3.6.4 ===
|
||||
|
||||
* Behat timeout constants behat_base::TIMEOUT, EXTENDED_TIMEOUT, and REDUCED_TIMEOUT will be
|
||||
|
||||
@@ -142,11 +142,11 @@ Feature: Assign group override
|
||||
| id_cutoffdate_enabled | 1 |
|
||||
| cutoffdate[day] | 1 |
|
||||
| cutoffdate[month] | January |
|
||||
| cutoffdate[year] | 2020 |
|
||||
| cutoffdate[year] | 2030 |
|
||||
| cutoffdate[hour] | 08 |
|
||||
| cutoffdate[minute] | 00 |
|
||||
And I press "Save"
|
||||
And I should see "Wednesday, 1 January 2020, 8:00"
|
||||
And I should see "Tuesday, 1 January 2030, 8:00"
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
@@ -169,7 +169,7 @@ Feature: Assign group override
|
||||
| id_cutoffdate_enabled | 0 |
|
||||
| allowsubmissionsfromdate[day] | 1 |
|
||||
| allowsubmissionsfromdate[month] | January |
|
||||
| allowsubmissionsfromdate[year] | 2020 |
|
||||
| allowsubmissionsfromdate[year] | 2030 |
|
||||
| allowsubmissionsfromdate[hour] | 08 |
|
||||
| allowsubmissionsfromdate[minute] | 00 |
|
||||
And I press "Save and display"
|
||||
@@ -189,13 +189,13 @@ Feature: Assign group override
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
Then I should see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00"
|
||||
Then I should see "This assignment will accept submissions from Tuesday, 1 January 2030, 8:00"
|
||||
And I should not see "Add submission"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
And I should not see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00"
|
||||
And I should not see "This assignment will accept submissions from Tuesday, 1 January 2030, 8:00"
|
||||
|
||||
@javascript
|
||||
Scenario: Add both a user and group override and verify that both are applied correctly
|
||||
@@ -209,7 +209,7 @@ Feature: Assign group override
|
||||
| id_cutoffdate_enabled | 0 |
|
||||
| allowsubmissionsfromdate[day] | 1 |
|
||||
| allowsubmissionsfromdate[month] | January |
|
||||
| allowsubmissionsfromdate[year] | 2030 |
|
||||
| allowsubmissionsfromdate[year] | 2040 |
|
||||
| allowsubmissionsfromdate[hour] | 08 |
|
||||
| allowsubmissionsfromdate[minute] | 00 |
|
||||
And I press "Save and display"
|
||||
@@ -220,11 +220,11 @@ Feature: Assign group override
|
||||
| id_allowsubmissionsfromdate_enabled | 1 |
|
||||
| allowsubmissionsfromdate[day] | 1 |
|
||||
| allowsubmissionsfromdate[month] | January |
|
||||
| allowsubmissionsfromdate[year] | 2020 |
|
||||
| allowsubmissionsfromdate[year] | 2030 |
|
||||
| allowsubmissionsfromdate[hour] | 08 |
|
||||
| allowsubmissionsfromdate[minute] | 00 |
|
||||
And I press "Save"
|
||||
And I should see "Wednesday, 1 January 2020, 8:00"
|
||||
And I should see "Tuesday, 1 January 2030, 8:00"
|
||||
And I follow "Test assignment name"
|
||||
And I navigate to "User overrides" in current page administration
|
||||
And I press "Add user override"
|
||||
@@ -233,26 +233,26 @@ Feature: Assign group override
|
||||
| id_allowsubmissionsfromdate_enabled | 1 |
|
||||
| allowsubmissionsfromdate[day] | 1 |
|
||||
| allowsubmissionsfromdate[month] | January |
|
||||
| allowsubmissionsfromdate[year] | 2021 |
|
||||
| allowsubmissionsfromdate[year] | 2031 |
|
||||
| allowsubmissionsfromdate[hour] | 08 |
|
||||
| allowsubmissionsfromdate[minute] | 00 |
|
||||
And I press "Save"
|
||||
And I should see "Friday, 1 January 2021, 8:00"
|
||||
And I should see "Wednesday, 1 January 2031, 8:00"
|
||||
And I log out
|
||||
Then I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
And I should see "This assignment will accept submissions from Friday, 1 January 2021, 8:00"
|
||||
And I should see "This assignment will accept submissions from Wednesday, 1 January 2031, 8:00"
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
And I should see "This assignment will accept submissions from Tuesday, 1 January 2030, 8:00"
|
||||
And I should see "This assignment will accept submissions from Sunday, 1 January 2040, 8:00"
|
||||
And I log out
|
||||
And I log in as "student3"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
And I should see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00"
|
||||
And I should see "This assignment will accept submissions from Tuesday, 1 January 2030, 8:00"
|
||||
|
||||
Scenario: Override a group when teacher is in no group, and does not have accessallgroups permission, and the activity's group mode is "separate groups"
|
||||
Given the following "permission overrides" exist:
|
||||
|
||||
@@ -135,11 +135,11 @@ Feature: Assign user override
|
||||
| id_cutoffdate_enabled | 1 |
|
||||
| cutoffdate[day] | 1 |
|
||||
| cutoffdate[month] | January |
|
||||
| cutoffdate[year] | 2020 |
|
||||
| cutoffdate[year] | 2030 |
|
||||
| cutoffdate[hour] | 08 |
|
||||
| cutoffdate[minute] | 00 |
|
||||
And I press "Save"
|
||||
And I should see "Wednesday, 1 January 2020, 8:00"
|
||||
And I should see "Tuesday, 1 January 2030, 8:00"
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
@@ -163,7 +163,7 @@ Feature: Assign user override
|
||||
| id_cutoffdate_enabled | 0 |
|
||||
| allowsubmissionsfromdate[day] | 1 |
|
||||
| allowsubmissionsfromdate[month] | January |
|
||||
| allowsubmissionsfromdate[year] | 2020 |
|
||||
| allowsubmissionsfromdate[year] | 2030 |
|
||||
| allowsubmissionsfromdate[hour] | 08 |
|
||||
| allowsubmissionsfromdate[minute] | 00 |
|
||||
And I press "Save and display"
|
||||
@@ -183,12 +183,12 @@ Feature: Assign user override
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
Then I should see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00"
|
||||
Then I should see "This assignment will accept submissions from Tuesday, 1 January 2030, 8:00"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
And I should not see "This assignment will accept submissions from Wednesday, 1 January 2020, 8:00"
|
||||
And I should not see "This assignment will accept submissions from Tuesday, 1 January 2030, 8:00"
|
||||
|
||||
Scenario: Override a user when teacher is in no group, and does not have accessallgroups permission, and the activity's group mode is "separate groups"
|
||||
Given the following "permission overrides" exist:
|
||||
|
||||
@@ -123,11 +123,11 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
|
||||
| id_duedate_enabled | 1 |
|
||||
| duedate[day] | 1 |
|
||||
| duedate[month] | January |
|
||||
| duedate[year] | 2020 |
|
||||
| duedate[year] | 2030 |
|
||||
| duedate[hour] | 08 |
|
||||
| duedate[minute] | 00 |
|
||||
And I press "Save"
|
||||
And I should see "Wednesday, 1 January 2020, 8:00"
|
||||
And I should see "Tuesday, 1 January 2030, 8:00"
|
||||
And I follow "Test assignment name"
|
||||
And I navigate to "View all submissions" in current page administration
|
||||
And I should see "In review" in the "I'm the student's first submission" "table_row"
|
||||
|
||||
@@ -44,9 +44,8 @@ Feature: Students can edit or delete their forum posts within a set time limit
|
||||
Scenario: Time limit expires
|
||||
Given I log out
|
||||
And I log in as "admin"
|
||||
And I navigate to "Security > Site security settings" in site administration
|
||||
And I set the field "Maximum time to edit posts" to "1 minutes"
|
||||
And I press "Save changes"
|
||||
And the following config values are set as admin:
|
||||
| maxeditingtime | 1 |
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add the "Recent activity" block
|
||||
And I log out
|
||||
@@ -54,7 +53,7 @@ Feature: Students can edit or delete their forum posts within a set time limit
|
||||
And I am on "Course 1" course homepage
|
||||
And I should see "New forum posts:" in the "Recent activity" "block"
|
||||
And I should see "Forum post subject" in the "Recent activity" "block"
|
||||
When I wait "61" seconds
|
||||
When I wait "2" seconds
|
||||
And I follow "Forum post subject"
|
||||
Then I should not see "Edit" in the "region-main" "region"
|
||||
And I should not see "Delete" in the "region-main" "region"
|
||||
|
||||
@@ -4,7 +4,6 @@ Feature: A teacher can set whether glossary entries are always editable or not
|
||||
As a teacher
|
||||
I need to prevent entries to be always editable
|
||||
|
||||
@javascript
|
||||
Scenario: Glossary entries are not always editable
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
@@ -18,7 +17,7 @@ Feature: A teacher can set whether glossary entries are always editable or not
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following config values are set as admin:
|
||||
| maxeditingtime | 60 |
|
||||
| maxeditingtime | 5 |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Glossary" to section "1" and I fill the form with:
|
||||
@@ -34,7 +33,7 @@ Feature: A teacher can set whether glossary entries are always editable or not
|
||||
| Definition | Test concept description |
|
||||
Then "Delete entry: Test concept name" "link" should exist
|
||||
And "Edit entry: Test concept name" "link" should exist
|
||||
And I wait "65" seconds
|
||||
And I wait "6" seconds
|
||||
And I reload the page
|
||||
Then "Delete entry: Test concept name" "link" should not exist
|
||||
And "Delete entry: Test concept name" "link" should not exist
|
||||
And "Edit entry: Test concept name" "link" should not exist
|
||||
|
||||
@@ -28,7 +28,7 @@ Feature: A teacher can set available from and deadline dates to access a lesson
|
||||
| Description | Test lesson description |
|
||||
| available[day] | 1 |
|
||||
| available[month] | January |
|
||||
| available[year] | 2020 |
|
||||
| available[year] | 2030 |
|
||||
| available[hour] | 08 |
|
||||
| available[minute] | 00 |
|
||||
And I press "Save and display"
|
||||
@@ -43,7 +43,7 @@ Feature: A teacher can set available from and deadline dates to access a lesson
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Test lesson"
|
||||
Then I should see "This lesson will be open on Wednesday, 1 January 2020, 8:00"
|
||||
Then I should see "This lesson will be open on Tuesday, 1 January 2030, 8:00"
|
||||
And I should not see "First page contents"
|
||||
|
||||
Scenario: Forbidding lesson accesses until a specified date
|
||||
|
||||
@@ -207,7 +207,7 @@ Feature: Lesson group override
|
||||
| id_deadline_enabled | 1 |
|
||||
| deadline[day] | 1 |
|
||||
| deadline[month] | January |
|
||||
| deadline[year] | 2020 |
|
||||
| deadline[year] | 2030 |
|
||||
| deadline[hour] | 08 |
|
||||
| deadline[minute] | 00 |
|
||||
And I press "Save"
|
||||
@@ -233,7 +233,7 @@ Feature: Lesson group override
|
||||
| id_available_enabled | 1 |
|
||||
| available[day] | 1 |
|
||||
| available[month] | January |
|
||||
| available[year] | 2020 |
|
||||
| available[year] | 2030 |
|
||||
| available[hour] | 08 |
|
||||
| available[minute] | 00 |
|
||||
And I press "Save and display"
|
||||
@@ -253,7 +253,7 @@ Feature: Lesson group override
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson"
|
||||
Then I should see "This lesson will be open on Wednesday, 1 January 2020, 8:00"
|
||||
Then I should see "This lesson will be open on Tuesday, 1 January 2030, 8:00"
|
||||
And I should not see "Cat is an amphibian"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
@@ -312,7 +312,7 @@ Feature: Lesson group override
|
||||
| id_available_enabled | 1 |
|
||||
| available[day] | 1 |
|
||||
| available[month] | January |
|
||||
| available[year] | 2030 |
|
||||
| available[year] | 2040 |
|
||||
| available[hour] | 08 |
|
||||
| available[minute] | 00 |
|
||||
And I press "Save and display"
|
||||
@@ -324,11 +324,11 @@ Feature: Lesson group override
|
||||
| id_available_enabled | 1 |
|
||||
| available[day] | 1 |
|
||||
| available[month] | January |
|
||||
| available[year] | 2020 |
|
||||
| available[year] | 2030 |
|
||||
| available[hour] | 08 |
|
||||
| available[minute] | 00 |
|
||||
And I press "Save"
|
||||
And I should see "Wednesday, 1 January 2020, 8:00"
|
||||
And I should see "Tuesday, 1 January 2030, 8:00"
|
||||
And I follow "Test lesson name"
|
||||
And I navigate to "User overrides" in current page administration
|
||||
And I press "Add user override"
|
||||
@@ -337,26 +337,26 @@ Feature: Lesson group override
|
||||
| id_available_enabled | 1 |
|
||||
| available[day] | 1 |
|
||||
| available[month] | January |
|
||||
| available[year] | 2021 |
|
||||
| available[year] | 2031 |
|
||||
| available[hour] | 08 |
|
||||
| available[minute] | 00 |
|
||||
And I press "Save"
|
||||
And I should see "Friday, 1 January 2021, 8:00"
|
||||
And I should see "Wednesday, 1 January 2031, 8:00"
|
||||
And I log out
|
||||
Then I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson"
|
||||
And I should see "This lesson will be open on Friday, 1 January 2021, 8:00"
|
||||
And I should see "This lesson will be open on Wednesday, 1 January 2031, 8:00"
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson"
|
||||
And I should see "This lesson will be open on Tuesday, 1 January 2030, 8:00"
|
||||
And I should see "This lesson will be open on Sunday, 1 January 2040, 8:00"
|
||||
And I log out
|
||||
And I log in as "student3"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson"
|
||||
And I should see "This lesson will be open on Wednesday, 1 January 2020, 8:00"
|
||||
And I should see "This lesson will be open on Tuesday, 1 January 2030, 8:00"
|
||||
|
||||
Scenario: Override a group when teacher is in no group, and does not have accessallgroups permission, and the activity's group mode is 'separate groups'
|
||||
Given the following "permission overrides" exist:
|
||||
|
||||
@@ -201,7 +201,7 @@ Feature: Lesson user override
|
||||
| id_deadline_enabled | 1 |
|
||||
| deadline[day] | 1 |
|
||||
| deadline[month] | January |
|
||||
| deadline[year] | 2020 |
|
||||
| deadline[year] | 2030 |
|
||||
| deadline[hour] | 08 |
|
||||
| deadline[minute] | 00 |
|
||||
And I press "Save"
|
||||
@@ -228,7 +228,7 @@ Feature: Lesson user override
|
||||
| id_available_enabled | 1 |
|
||||
| available[day] | 1 |
|
||||
| available[month] | January |
|
||||
| available[year] | 2020 |
|
||||
| available[year] | 2030 |
|
||||
| available[hour] | 08 |
|
||||
| available[minute] | 00 |
|
||||
And I press "Save and display"
|
||||
@@ -248,7 +248,7 @@ Feature: Lesson user override
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson"
|
||||
Then I should see "This lesson will be open on Wednesday, 1 January 2020, 8:00"
|
||||
Then I should see "This lesson will be open on Tuesday, 1 January 2030, 8:00"
|
||||
And I should not see "Cat is an amphibian"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
|
||||
@@ -47,32 +47,24 @@ Feature: Quiz reset
|
||||
And I am on the "Test quiz name" "mod_quiz > Grades report" page
|
||||
Then I should see "Attempts: 0"
|
||||
|
||||
@javascript
|
||||
Scenario: Use course reset to remove user overrides.
|
||||
When I am on the "Test quiz name" "mod_quiz > User overrides" page logged in as "teacher1"
|
||||
And I press "Add user override"
|
||||
And I set the following fields to these values:
|
||||
| Override user | Student1 |
|
||||
| Attempts allowed | 2 |
|
||||
And I press "Save"
|
||||
And I should see "Sam1 Student1"
|
||||
Given the following "mod_quiz > user overrides" exist:
|
||||
| quiz | user | attempts |
|
||||
| Test quiz name | student1 | 2 |
|
||||
When I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Reset" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Delete all user overrides | 1 |
|
||||
And I set the field "Delete all user overrides" to "1"
|
||||
And I press "Reset course"
|
||||
And I press "Continue"
|
||||
And I am on the "Test quiz name" "mod_quiz > User overrides" page
|
||||
Then I should not see "Sam1 Student1"
|
||||
|
||||
Scenario: Use course reset to remove group overrides.
|
||||
When I am on the "Test quiz name" "mod_quiz > Group overrides" page logged in as "teacher1"
|
||||
And I press "Add group override"
|
||||
And I set the following fields to these values:
|
||||
| Override group | Group 1 |
|
||||
| Attempts allowed | 2 |
|
||||
And I press "Save"
|
||||
And I should see "Group 1"
|
||||
Given the following "mod_quiz > group overrides" exist:
|
||||
| quiz | group | attempts |
|
||||
| Test quiz name | G1 | 2 |
|
||||
When I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Reset" in current page administration
|
||||
And I set the following fields to these values:
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Behat data generator for mod_quiz.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category test
|
||||
* @copyright 2019 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Behat data generator for mod_quiz.
|
||||
*
|
||||
* @copyright 2019 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_mod_quiz_generator extends behat_generator_base {
|
||||
|
||||
protected function get_creatable_entities(): array {
|
||||
return [
|
||||
'group overrides' => [
|
||||
'datagenerator' => 'override',
|
||||
'required' => ['quiz', 'group'],
|
||||
'switchids' => ['quiz' => 'quiz', 'group' => 'groupid'],
|
||||
],
|
||||
'user overrides' => [
|
||||
'datagenerator' => 'override',
|
||||
'required' => ['quiz', 'user'],
|
||||
'switchids' => ['quiz' => 'quiz', 'user' => 'userid'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the id of a quiz from its name.
|
||||
*
|
||||
* @param string $quizname the quiz name, for example 'Test quiz'.
|
||||
* @return int corresponding id.
|
||||
*/
|
||||
protected function get_quiz_id(string $quizname): int {
|
||||
global $DB;
|
||||
|
||||
if (!$id = $DB->get_field('quiz', 'id', ['name' => $quizname])) {
|
||||
throw new Exception('There is no quiz with name "' . $quizname . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
@@ -170,4 +170,27 @@ class mod_quiz_generator extends testing_module_generator {
|
||||
$attemptobj->process_finish(time(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a quiz override (either user or group).
|
||||
*
|
||||
* @param array $data must specify quizid, and one of userid or groupid.
|
||||
*/
|
||||
public function create_override(array $data) {
|
||||
global $DB;
|
||||
|
||||
if (!isset($data['quiz'])) {
|
||||
throw new coding_exception('Must specify quiz (id) when creating a quiz override.');
|
||||
}
|
||||
|
||||
if (!isset($data['userid']) && !isset($data['groupid'])) {
|
||||
throw new coding_exception('Must specify one of userid or groupid when creating a quiz override.');
|
||||
}
|
||||
|
||||
if (isset($data['userid']) && isset($data['groupid'])) {
|
||||
throw new coding_exception('Cannot specify both userid and groupid when creating a quiz override.');
|
||||
}
|
||||
|
||||
$DB->insert_record('quiz_overrides', (object) $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,6 +508,7 @@ function AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, vi
|
||||
}
|
||||
|
||||
function StoreData(data,storetotaltime) {
|
||||
var datastring = '';
|
||||
if (storetotaltime) {
|
||||
if (cmi.core.lesson_mode == 'normal') {
|
||||
if (cmi.core.credit == 'credit') {
|
||||
@@ -531,9 +532,13 @@ function AICCapi(def, cmiobj, scormauto, cfgwwwroot, scormid, scoid, attempt, vi
|
||||
datastring = CollectData(data,'cmi');
|
||||
}
|
||||
|
||||
//popupwin(datastring);
|
||||
var myRequest = NewHttpReq();
|
||||
result = DoRequest(myRequest,datamodelurl,datamodelurlparams + datastring);
|
||||
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
|
||||
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
results = String(result).split('\n');
|
||||
errorCode = results[1];
|
||||
return results[0];
|
||||
|
||||
@@ -619,6 +619,7 @@ function SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebu
|
||||
}
|
||||
|
||||
function StoreData(data,storetotaltime) {
|
||||
var datastring = '';
|
||||
if (storetotaltime) {
|
||||
if (cmi.core.lesson_status == 'not attempted') {
|
||||
cmi.core.lesson_status = 'completed';
|
||||
@@ -647,7 +648,12 @@ function SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebu
|
||||
|
||||
var myRequest = NewHttpReq();
|
||||
//alert('going to:' + "<?php p($CFG->wwwroot) ?>/mod/scorm/datamodel.php" + "id=<?php p($id) ?>&a=<?php p($a) ?>&sesskey=<?php echo sesskey() ?>"+datastring);
|
||||
result = DoRequest(myRequest,datamodelurl,datamodelurlparams + datastring);
|
||||
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
|
||||
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
results = String(result).split('\n');
|
||||
errorCode = results[1];
|
||||
return results[0];
|
||||
|
||||
@@ -1216,7 +1216,11 @@ function SCORMapi1_3(def, cmiobj, cmiint, cmicommentsuser, cmicommentslms, scorm
|
||||
datastring += navrequest;
|
||||
|
||||
var myRequest = NewHttpReq();
|
||||
result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
|
||||
var result = DoRequest(myRequest, datamodelurl, datamodelurlparams + datastring);
|
||||
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var results = String(result).split('\n');
|
||||
if ((results.length > 2) && (navrequest != '')) {
|
||||
|
||||
@@ -239,6 +239,7 @@ $string['trackweight'] = 'Weight';
|
||||
$string['trackweight_help'] = 'Weight assigned to the question when calculating score.';
|
||||
$string['invalidactivity'] = 'SCORM activity is incorrect';
|
||||
$string['invalidmanifestname'] = 'Only imsmanifest.xml or .zip files may be selected';
|
||||
$string['invalidstatus'] = 'Invalid status';
|
||||
$string['invalidurl'] = 'Invalid URL specified';
|
||||
$string['invalidurlhttpcheck'] = 'Invalid URL specified. Debug message:<pre>{$a->cmsg}</pre>';
|
||||
$string['invalidhacpsession'] = 'Invalid HACP session';
|
||||
|
||||
+14
-5
@@ -1582,10 +1582,18 @@ function scorm_get_toc_object($user, $scorm, $currentorg='', $scoid='', $mode='n
|
||||
|
||||
if (isset($usertracks[$sco->identifier])) {
|
||||
$usertrack = $usertracks[$sco->identifier];
|
||||
$strstatus = get_string($usertrack->status, 'scorm');
|
||||
|
||||
// Check we have a valid status string identifier.
|
||||
if ($statusstringexists = get_string_manager()->string_exists($usertrack->status, 'scorm')) {
|
||||
$strstatus = get_string($usertrack->status, 'scorm');
|
||||
} else {
|
||||
$strstatus = get_string('invalidstatus', 'scorm');
|
||||
}
|
||||
|
||||
if ($sco->scormtype == 'sco') {
|
||||
$statusicon = $OUTPUT->pix_icon($usertrack->status, $strstatus, 'scorm');
|
||||
// Assume if we didn't get a valid status string, we don't have an icon either.
|
||||
$statusicon = $OUTPUT->pix_icon($statusstringexists ? $usertrack->status : 'incomplete',
|
||||
$strstatus, 'scorm');
|
||||
} else {
|
||||
$statusicon = $OUTPUT->pix_icon('asset', get_string('assetlaunched', 'scorm'), 'scorm');
|
||||
}
|
||||
@@ -2368,12 +2376,13 @@ function scorm_eval_prerequisites($prerequisites, $usertracks) {
|
||||
if (isset($statuses[$value])) {
|
||||
$value = $statuses[$value];
|
||||
}
|
||||
|
||||
$elementprerequisitematch = (strcmp($usertracks[$element]->status, $value) == 0);
|
||||
if ($matches[2] == '<>') {
|
||||
$oper = '!=';
|
||||
$element = $elementprerequisitematch ? 'false' : 'true';
|
||||
} else {
|
||||
$oper = '==';
|
||||
$element = $elementprerequisitematch ? 'true' : 'false';
|
||||
}
|
||||
$element = '(\''.$usertracks[$element]->status.'\' '.$oper.' \''.$value.'\')';
|
||||
} else {
|
||||
$element = 'false';
|
||||
}
|
||||
|
||||
+19
-5
@@ -460,9 +460,16 @@ M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, h
|
||||
if (scoes_nav[launch_sco].flow === 1) {
|
||||
var datastring = scoes_nav[launch_sco].url + '&function=scorm_seq_flow&request=backward';
|
||||
result = scorm_ajax_request(M.cfg.wwwroot + '/mod/scorm/datamodels/sequencinghandler.php?', datastring);
|
||||
mod_scorm_seq = encodeURIComponent(result);
|
||||
result = Y.JSON.parse (result);
|
||||
if (typeof result.nextactivity.id != undefined) {
|
||||
|
||||
if (result === false) {
|
||||
// Either the outcome was a failure, or we are unloading and simply just don't know
|
||||
// what the outcome actually was.
|
||||
result = {};
|
||||
} else {
|
||||
result = Y.JSON.parse(result);
|
||||
}
|
||||
|
||||
if (typeof result.nextactivity !== 'undefined' && typeof result.nextactivity.id !== 'undefined') {
|
||||
var node = scorm_prev(scorm_tree_node.getSelectedNodes()[0]);
|
||||
if (node == null) {
|
||||
// Avoid use of TreeView for Navigation.
|
||||
@@ -492,8 +499,15 @@ M.mod_scorm.init = function(Y, nav_display, navposition_left, navposition_top, h
|
||||
if (scoes_nav[launch_sco].flow === 1) {
|
||||
var datastring = scoes_nav[launch_sco].url + '&function=scorm_seq_flow&request=forward';
|
||||
result = scorm_ajax_request(M.cfg.wwwroot + '/mod/scorm/datamodels/sequencinghandler.php?', datastring);
|
||||
mod_scorm_seq = encodeURIComponent(result);
|
||||
result = Y.JSON.parse (result);
|
||||
|
||||
if (result === false) {
|
||||
// Either the outcome was a failure, or we are unloading and simply just don't know
|
||||
// what the outcome actually was.
|
||||
result = {};
|
||||
} else {
|
||||
result = Y.JSON.parse(result);
|
||||
}
|
||||
|
||||
if (typeof result.nextactivity !== 'undefined' && typeof result.nextactivity.id !== 'undefined') {
|
||||
var node = scorm_next(scorm_tree_node.getSelectedNodes()[0]);
|
||||
if (node === null) {
|
||||
|
||||
@@ -37,6 +37,34 @@ function NewHttpReq() {
|
||||
|
||||
function DoRequest(httpReq,url,param) {
|
||||
|
||||
// If we are unloading, and we can use sendBeacon then do that, Chrome does not permit synchronous XHR requests on unload.
|
||||
if (window.mod_scorm_is_window_closing && navigator && navigator.sendBeacon && FormData) {
|
||||
// Ok, old API alert, the param is a URI encoded string. We need to split it and convert it to a supported format.
|
||||
// I've chosen FormData and FormData.append as they are compatible with our supported browsers:
|
||||
// - https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
|
||||
// - https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
|
||||
|
||||
var vars = param.split('&'),
|
||||
i = 0,
|
||||
pair,
|
||||
key,
|
||||
value,
|
||||
formData = new FormData();
|
||||
for (i = 0; i < vars.length; i++) {
|
||||
pair = vars[i].split('=');
|
||||
key = decodeURIComponent(pair[0]);
|
||||
value = decodeURIComponent(pair[1]);
|
||||
formData.append(key, value);
|
||||
}
|
||||
// We'll also inform it that we are unloading, potentially useful in the future.
|
||||
formData.append('unloading', '1');
|
||||
|
||||
// The results is true or false, we don't get the response from the server. Make it look like it was a success.
|
||||
navigator.sendBeacon(url, formData);
|
||||
// This is what a success looks like when it comes back from the server.
|
||||
return "true\n0";
|
||||
}
|
||||
|
||||
// httpReq.open (Method("get","post"), URL(string), Asyncronous(true,false))
|
||||
//popupwin(url+"\n"+param);
|
||||
httpReq.open("POST", url,false);
|
||||
@@ -60,3 +88,24 @@ function popupwin(content) {
|
||||
op.document.write(content);
|
||||
op.document.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* We wire up a small marker for the unload events triggered when the user is navigating away or closing the tab.
|
||||
* This is done because Chrome does not allow synchronous XHR requests on the following unload events:
|
||||
* - beforeunload
|
||||
* - unload
|
||||
* - pagehide
|
||||
* - visibilitychange
|
||||
*/
|
||||
(function() {
|
||||
// Set up a global var. Sorry about this, old code ... old ways.
|
||||
window.mod_scorm_is_window_closing = false;
|
||||
var toggle = function() {
|
||||
window.mod_scorm_is_window_closing = true;
|
||||
};
|
||||
// Listen to the four events known to represent an unload operation.
|
||||
window.addEventListener('beforeunload', toggle);
|
||||
window.addEventListener('unload', toggle);
|
||||
window.addEventListener('pagehide', toggle);
|
||||
window.addEventListener('visibilitychange', toggle);
|
||||
})();
|
||||
@@ -0,0 +1,51 @@
|
||||
@mod @mod_scorm @_file_upload @_switch_iframe @_alert
|
||||
Feature: Confirm progress gets saved on unload events
|
||||
In order to let students access a scorm package
|
||||
As a teacher
|
||||
I need to add scorm activity to a course
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario: Test progress gets saved correctly when the user navigates away from the scorm activity
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "SCORM package" to section "1"
|
||||
And I set the following fields to these values:
|
||||
| Name | Runtime Basic Calls SCORM 2004 3rd Edition package |
|
||||
| Description | Description |
|
||||
And I upload "mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip" file to "Package file" filemanager
|
||||
And I click on "Save and display" "button"
|
||||
And I should see "Runtime Basic Calls SCORM 2004 3rd Edition package"
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Runtime Basic Calls SCORM 2004 3rd Edition package"
|
||||
Then I should see "Normal"
|
||||
And I press "Enter"
|
||||
And I switch to "scorm_object" iframe
|
||||
And I press "Next"
|
||||
And I press "Next"
|
||||
And I switch to "contentFrame" iframe
|
||||
And I should see "Scoring"
|
||||
And I switch to the main frame
|
||||
And I follow "C1"
|
||||
And I follow "Runtime Basic Calls SCORM 2004 3rd Edition package"
|
||||
And I should see "Normal"
|
||||
And I click on "Enter" "button" confirming the dialogue
|
||||
And I switch to "scorm_object" iframe
|
||||
And I switch to "contentFrame" iframe
|
||||
And I should see "Scoring"
|
||||
And I switch to the main frame
|
||||
# Go away from the scorm to stop background requests
|
||||
And I am on homepage
|
||||
@@ -62,7 +62,11 @@ function loadContent(datafile, callback) {
|
||||
}
|
||||
|
||||
newscript.type = 'text/javascript';
|
||||
newscript.src = encodeURIComponent(data);
|
||||
var dataParts = data.split('/');
|
||||
dataParts.forEach(function(part, index) {
|
||||
this[index] = encodeURIComponent(part);
|
||||
}, dataParts);
|
||||
newscript.src = dataParts.join('/');
|
||||
newscript.charset = 'utf-8';
|
||||
document.getElementsByTagName("head")[0].appendChild(newscript);
|
||||
|
||||
|
||||
+2
-2
@@ -29,11 +29,11 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2018120307.00; // 20181203 = branching date YYYYMMDD - do not modify!
|
||||
$version = 2018120310.00; // 20181203 = branching date YYYYMMDD - do not modify!
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
$release = '3.6.7 (Build: 20191111)'; // Human-friendly version name
|
||||
$release = '3.6.10 (Build: 20200511)'; // Human-friendly version name
|
||||
|
||||
$branch = '36'; // This version's branch.
|
||||
$maturity = MATURITY_STABLE; // This version's maturity level.
|
||||
|
||||
Reference in New Issue
Block a user