Compare commits
313 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25c9c6336b | |||
| a88baa553a | |||
| 2b0e7a4ea9 | |||
| 46cb3afcfe | |||
| b7bb837d2b | |||
| bf1e1b917f | |||
| 35c54f8ea8 | |||
| ffc5dc3245 | |||
| 64f1c9d901 | |||
| b7ee5cb2ea | |||
| 0581a99241 | |||
| d084e4194c | |||
| 56f3b3f412 | |||
| 0ecc1a8eab | |||
| 6cb4bd65d3 | |||
| 034729a5ce | |||
| 4676d5ad35 | |||
| a722b44a09 | |||
| 8d111d5136 | |||
| a423038589 | |||
| cff7de72ed | |||
| 18b60b3d2b | |||
| fb9553cac0 | |||
| 2b746fc34a | |||
| 87c1819e2f | |||
| 2e4684f51c | |||
| f68e327afa | |||
| 7471e92601 | |||
| 5dbf43b0de | |||
| 98c155a433 | |||
| 7c2d448bf8 | |||
| fb5ed6a5ca | |||
| beed18a8e9 | |||
| 79d23bb46b | |||
| 145c57f2f9 | |||
| 385e7e0c26 | |||
| e848d21f9e | |||
| e8014f4833 | |||
| 66b2dcd7be | |||
| f41a30229e | |||
| d8a7f2aeb7 | |||
| e7cdd0e77c | |||
| cf889e4fbd | |||
| bb519b05b1 | |||
| ad796334cb | |||
| 0e903c3f7f | |||
| 63c0acc5e9 | |||
| 307600962c | |||
| d0cf886bf8 | |||
| 0c8ad6a113 | |||
| bff4d30a79 | |||
| 3d9b0a9b1b | |||
| 5570436ef3 | |||
| 96d6f625f3 | |||
| 9c8a6c45f9 | |||
| be328441a2 | |||
| e282b1f1fc | |||
| 28ccb81e87 | |||
| bf84c3f075 | |||
| 429de834fa | |||
| 77c0308e74 | |||
| 991ac49b61 | |||
| 56a0e6ed87 | |||
| 710bfa00e7 | |||
| 5d70a8d5ad | |||
| 39a390f1f5 | |||
| 978ed33fb1 | |||
| 666d420194 | |||
| bdf64b7eec | |||
| 413b321344 | |||
| 4497ed3938 | |||
| fc992c94af | |||
| 7b04b19e06 | |||
| 7dfa4d625b | |||
| 648ec6857b | |||
| f6f931728a | |||
| de254adfd3 | |||
| bf0a0ed189 | |||
| 76ebea9add | |||
| 4fef7d1c87 | |||
| 582a3baf70 | |||
| 9961411a12 | |||
| ace0fcf5b4 | |||
| d3750e40ae | |||
| 8e506aa53c | |||
| 687e3b53c0 | |||
| 285bc3e242 | |||
| 8dc826354d | |||
| d5409002be | |||
| 3e1e159efa | |||
| a4ed555b2f | |||
| 07b3e80ed1 | |||
| 023ebdb1af | |||
| b09d19d5bd | |||
| 3acc7fba61 | |||
| 66039dbc91 | |||
| 90766fb12b | |||
| 3984d03fda | |||
| 0a9eae8b7a | |||
| aac11a3c4b | |||
| 84801cd656 | |||
| 542e7b52f2 | |||
| cbc680eb33 | |||
| 9a9542a238 | |||
| 5b3308939d | |||
| da348e315a | |||
| ba8bc23906 | |||
| 9c1f81775e | |||
| c6e6c935af | |||
| 474159da70 | |||
| 9ccc9ec175 | |||
| 2903b1ae22 | |||
| 5316b73d41 | |||
| 2669dd7963 | |||
| 4705bff0e1 | |||
| 19bd02eabd | |||
| 0701f2f08c | |||
| d18f809ec4 | |||
| 87252eae2c | |||
| df143c2923 | |||
| 621e8888e5 | |||
| ff76a9d590 | |||
| a9a9fd455a | |||
| 81bbf95756 | |||
| 30702cc0bd | |||
| 5eb80c4af6 | |||
| 1f19dc8237 | |||
| ca42dbc0dd | |||
| 04ed69e387 | |||
| b30c16ec86 | |||
| 532b7c9462 | |||
| d09616d194 | |||
| 6c06678c34 | |||
| b089becae9 | |||
| 703d8c2784 | |||
| 2a7ae663c5 | |||
| b303604bf1 | |||
| 9f0a5cf2f5 | |||
| 75232c7d34 | |||
| 0e57a3dede | |||
| d9dc216897 | |||
| 814f15a05d | |||
| 50cd119caa | |||
| caaf2427a7 | |||
| 88c69c2dcf | |||
| 4398cdace0 | |||
| 519142291c | |||
| 20b96798d7 | |||
| 3816bd8776 | |||
| 760ffcd6bf | |||
| 76bd81c69c | |||
| 4a8e6db894 | |||
| f5fbfb4d6b | |||
| 621f04de6a | |||
| 93d309cce2 | |||
| 53cc513636 | |||
| bf4f5df33e | |||
| d784d3751c | |||
| 7ef7de84a2 | |||
| 259bbd1960 | |||
| 765579d021 | |||
| a7e0f274f5 | |||
| bdef5416c3 | |||
| ad35c1ca2f | |||
| 958b331ada | |||
| 470ba08790 | |||
| 7ac967501b | |||
| 4bb12ff74d | |||
| 0ffb67352b | |||
| 713a5cdb97 | |||
| bf61d3f2b7 | |||
| f7ea6feec7 | |||
| b46eb4266d | |||
| e8bc91383d | |||
| 75bb4372ca | |||
| 1d0dc3102a | |||
| 8b89ab5798 | |||
| 67ccb85794 | |||
| 60e2e906f1 | |||
| aeccf1b188 | |||
| 512320cd6c | |||
| aefc7f7f70 | |||
| 2ab788bcf8 | |||
| 2ecdf811f3 | |||
| 8a6d5c8c88 | |||
| 765ef31f00 | |||
| e011fc3dfb | |||
| b08e192757 | |||
| a4ec901a4d | |||
| a99105cc33 | |||
| 8278f79219 | |||
| 5fa3dccf4b | |||
| b306a159de | |||
| 14d1be23c1 | |||
| 3f4935dfda | |||
| 3958f4a302 | |||
| 3c7f5e8472 | |||
| 7db4bf3b34 | |||
| baa5130e0a | |||
| 01d1ae9a5b | |||
| 1fa5fe2104 | |||
| c91312545d | |||
| 68e8070295 | |||
| c2ba01fe9e | |||
| 6714c137c8 | |||
| 31f8f75672 | |||
| 8d268a8d04 | |||
| ac210ad583 | |||
| 29d555ab48 | |||
| 86fa1ca83d | |||
| 8a5783c2be | |||
| fc71bf1e4a | |||
| 0845113531 | |||
| c131927ae5 | |||
| f1b71b0199 | |||
| 952b0caf57 | |||
| 2366f12757 | |||
| 3135b6f694 | |||
| 4be324b42a | |||
| 2d887636e6 | |||
| 4e0b890955 | |||
| ee1fac1fd8 | |||
| 0a397e3fd2 | |||
| f4397e3098 | |||
| cc2932aefa | |||
| ce20bd7a73 | |||
| f5f5bfbdf7 | |||
| e3e8ee312b | |||
| 5f2dd1bd50 | |||
| 26c79027ff | |||
| cfbcdc97e5 | |||
| 6bdfddc463 | |||
| 481d6f6387 | |||
| a174ed3736 | |||
| eb77088991 | |||
| a9c3e2f95e | |||
| 9cab0bb465 | |||
| 53c04bafc2 | |||
| 409b3bbd81 | |||
| fcad36339a | |||
| 10892d50d2 | |||
| 69aca65995 | |||
| ec5aca167c | |||
| 59cf70e977 | |||
| 1c8d4b0bce | |||
| ed401f9445 | |||
| a80c888892 | |||
| 6354c24aed | |||
| 5d21bf1b16 | |||
| 4a3f3ee3e4 | |||
| b2d9aaa126 | |||
| 5e17f2faeb | |||
| 92e745c293 | |||
| f4670d9e85 | |||
| cc1968a042 | |||
| 7e107b3a98 | |||
| 6c15dc2f3c | |||
| 072c1ad68e | |||
| 0590e71eeb | |||
| 3536e97dc8 | |||
| f5cb0ec042 | |||
| 40ed8aa97a | |||
| 309550be69 | |||
| 535caf12c6 | |||
| e4bb1b0d75 | |||
| bd3ce90c13 | |||
| 1542213b00 | |||
| 6a838cd4a0 | |||
| c0858f7068 | |||
| 1128545ebf | |||
| 484606eb08 | |||
| 2d28f15a3a | |||
| e6f2757f3c | |||
| f525ed2bdc | |||
| df991a4d53 | |||
| d72a6fc04c | |||
| 83ecf18e34 | |||
| 49b92a4a70 | |||
| d540a0ae0b | |||
| a2f1de61a0 | |||
| ae89df663a | |||
| ee8d64f6ef | |||
| 11ac4acdb1 | |||
| c3fbb0278b | |||
| 375c1b8e20 | |||
| 868cd1eacb | |||
| 4996652352 | |||
| 3857644a5c | |||
| eb8ecb04b6 | |||
| 8e6fdc1cdb | |||
| 733a5b75e9 | |||
| af58905893 | |||
| af0fd8128f | |||
| b559117706 | |||
| 4e6831d51c | |||
| e60183e163 | |||
| 59b30e3dae | |||
| 87cc8008f4 | |||
| 35a8f83e2c | |||
| a31372df3f | |||
| 252cc45466 | |||
| b652d2afc7 | |||
| 070453243b | |||
| 991a4c0fd1 | |||
| 8faaa5da65 | |||
| f6dd23d9a2 | |||
| b82faf7eda | |||
| 191c5c293a | |||
| 8d2bae85ed | |||
| 4f32de51a9 | |||
| 341c68c779 | |||
| d2154bafdb | |||
| bed95cdef1 |
+97
-88
@@ -514,100 +514,105 @@ if ($interactive) {
|
||||
$database = $databases[$CFG->dbtype];
|
||||
|
||||
|
||||
// ask for db host
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasehost', 'install'));
|
||||
if ($options['dbhost'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbhost']);
|
||||
// We cannot do any validation until all DB connection data is provided.
|
||||
$hintdatabase = '';
|
||||
do {
|
||||
echo $hintdatabase;
|
||||
|
||||
// Ask for db host.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasehost', 'install'));
|
||||
if ($options['dbhost'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbhost']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
}
|
||||
$CFG->dbhost = cli_input($prompt, $options['dbhost']);
|
||||
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
$CFG->dbhost = $options['dbhost'];
|
||||
}
|
||||
$CFG->dbhost = cli_input($prompt, $options['dbhost']);
|
||||
|
||||
} else {
|
||||
$CFG->dbhost = $options['dbhost'];
|
||||
}
|
||||
// Ask for db name.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasename', 'install'));
|
||||
if ($options['dbname'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbname']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
}
|
||||
$CFG->dbname = cli_input($prompt, $options['dbname']);
|
||||
|
||||
// ask for db name
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasename', 'install'));
|
||||
if ($options['dbname'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbname']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
$CFG->dbname = $options['dbname'];
|
||||
}
|
||||
$CFG->dbname = cli_input($prompt, $options['dbname']);
|
||||
|
||||
} else {
|
||||
$CFG->dbname = $options['dbname'];
|
||||
}
|
||||
// Ask for db prefix.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('dbprefix', 'install'));
|
||||
//TODO: solve somehow the prefix trouble for oci.
|
||||
if ($options['prefix'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['prefix']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
}
|
||||
$CFG->prefix = cli_input($prompt, $options['prefix']);
|
||||
|
||||
// ask for db prefix
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('dbprefix', 'install'));
|
||||
//TODO: solve somehow the prefix trouble for oci
|
||||
if ($options['prefix'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['prefix']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
$CFG->prefix = $options['prefix'];
|
||||
}
|
||||
$CFG->prefix = cli_input($prompt, $options['prefix']);
|
||||
|
||||
} else {
|
||||
$CFG->prefix = $options['prefix'];
|
||||
}
|
||||
// Ask for db port.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databaseport', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbport']);
|
||||
$CFG->dboptions['dbport'] = (int) cli_input($prompt, $options['dbport']);
|
||||
|
||||
// ask for db port
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databaseport', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbport']);
|
||||
$CFG->dboptions['dbport'] = (int)cli_input($prompt, $options['dbport']);
|
||||
|
||||
} else {
|
||||
$CFG->dboptions['dbport'] = (int)$options['dbport'];
|
||||
}
|
||||
if ($CFG->dboptions['dbport'] <= 0) {
|
||||
$CFG->dboptions['dbport'] = '';
|
||||
}
|
||||
|
||||
// ask for db socket
|
||||
if ($CFG->ostype === 'WINDOWS') {
|
||||
$CFG->dboptions['dbsocket'] = '';
|
||||
|
||||
} else if ($interactive and empty($CFG->dboptions['dbport'])) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasesocket', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbsocket']);
|
||||
$CFG->dboptions['dbsocket'] = cli_input($prompt, $options['dbsocket']);
|
||||
|
||||
} else {
|
||||
$CFG->dboptions['dbsocket'] = $options['dbsocket'];
|
||||
}
|
||||
|
||||
// ask for db user
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databaseuser', 'install'));
|
||||
if ($options['dbuser'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbuser']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
$CFG->dboptions['dbport'] = (int) $options['dbport'];
|
||||
}
|
||||
if ($CFG->dboptions['dbport'] <= 0) {
|
||||
$CFG->dboptions['dbport'] = '';
|
||||
}
|
||||
$CFG->dbuser = cli_input($prompt, $options['dbuser']);
|
||||
|
||||
} else {
|
||||
$CFG->dbuser = $options['dbuser'];
|
||||
}
|
||||
// Ask for db socket.
|
||||
if ($CFG->ostype === 'WINDOWS') {
|
||||
$CFG->dboptions['dbsocket'] = '';
|
||||
|
||||
} else if ($interactive and empty($CFG->dboptions['dbport'])) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasesocket', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbsocket']);
|
||||
$CFG->dboptions['dbsocket'] = cli_input($prompt, $options['dbsocket']);
|
||||
|
||||
} else {
|
||||
$CFG->dboptions['dbsocket'] = $options['dbsocket'];
|
||||
}
|
||||
|
||||
// Ask for db user.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databaseuser', 'install'));
|
||||
if ($options['dbuser'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbuser']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
}
|
||||
$CFG->dbuser = cli_input($prompt, $options['dbuser']);
|
||||
|
||||
} else {
|
||||
$CFG->dbuser = $options['dbuser'];
|
||||
}
|
||||
|
||||
// Ask for db password.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasepass', 'install'));
|
||||
|
||||
// ask for db password
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('databasepass', 'install'));
|
||||
do {
|
||||
if ($options['dbpass'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['dbpass']);
|
||||
} else {
|
||||
@@ -615,19 +620,23 @@ if ($interactive) {
|
||||
}
|
||||
|
||||
$CFG->dbpass = cli_input($prompt, $options['dbpass']);
|
||||
if (function_exists('distro_pre_create_db')) { // Hook for distros needing to do something before DB creation
|
||||
$distro = distro_pre_create_db($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbport'=>$CFG->dboptions['dbport'], 'dbsocket'=>$CFG->dboptions['dbsocket']), $distro);
|
||||
if (function_exists('distro_pre_create_db')) { // Hook for distros needing to do something before DB creation.
|
||||
$distro = distro_pre_create_db($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix,
|
||||
array('dbpersist' => 0, 'dbport' => $CFG->dboptions['dbport'], 'dbsocket' => $CFG->dboptions['dbsocket']),
|
||||
$distro);
|
||||
}
|
||||
$hint_database = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbport'=>$CFG->dboptions['dbport'], 'dbsocket'=>$CFG->dboptions['dbsocket']));
|
||||
} while ($hint_database !== '');
|
||||
$hintdatabase = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix,
|
||||
array('dbpersist' => 0, 'dbport' => $CFG->dboptions['dbport'], 'dbsocket' => $CFG->dboptions['dbsocket']));
|
||||
|
||||
} else {
|
||||
$CFG->dbpass = $options['dbpass'];
|
||||
$hint_database = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbport'=>$CFG->dboptions['dbport'], 'dbsocket'=>$CFG->dboptions['dbsocket']));
|
||||
if ($hint_database !== '') {
|
||||
cli_error(get_string('dbconnectionerror', 'install'));
|
||||
} else {
|
||||
$CFG->dbpass = $options['dbpass'];
|
||||
$hintdatabase = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix,
|
||||
array('dbpersist' => 0, 'dbport' => $CFG->dboptions['dbport'], 'dbsocket' => $CFG->dboptions['dbsocket']));
|
||||
if ($hintdatabase !== '') {
|
||||
cli_error(get_string('dbconnectionerror', 'install'));
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ($hintdatabase !== '');
|
||||
|
||||
// ask for fullname
|
||||
if ($interactive) {
|
||||
|
||||
@@ -2451,6 +2451,7 @@
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_74" message="unsupportedphpversion74" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -2632,6 +2633,7 @@
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.1.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_74" message="unsupportedphpversion74" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
|
||||
@@ -46,7 +46,7 @@ $error = optional_param('error', '', PARAM_ALPHANUM);
|
||||
admin_externalpage_setup('registrationmoodleorg');
|
||||
|
||||
if ($url !== HUB_MOODLEORGHUBURL) {
|
||||
// Allow other plugins to confirm registration on hubs other than moodle.net . Plugins implementing this
|
||||
// Allow other plugins to confirm registration on custom hubs. Plugins implementing this
|
||||
// callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
|
||||
$callbacks = get_plugins_with_function('hub_registration');
|
||||
foreach ($callbacks as $plugintype => $plugins) {
|
||||
|
||||
@@ -32,5 +32,4 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
debugging('Support for alternative hubs has been removed from Moodle in 3.4. For communication with moodle.net ' .
|
||||
'see lib/classes/hub/ .', DEBUG_DEVELOPER);
|
||||
debugging('Support for alternative hubs has been removed from Moodle in 3.4.', DEBUG_DEVELOPER);
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
|
||||
*
|
||||
* This page displays the site registration form for Moodle.net.
|
||||
* This page displays the site registration form.
|
||||
* It handles redirection to the hub to continue the registration workflow process.
|
||||
* It also handles update operation by web service.
|
||||
*/
|
||||
@@ -48,7 +48,7 @@ if ($unregistration && \core\hub\registration::is_registered()) {
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('unregisterfrom', 'hub', 'Moodle.net'), 3, 'main');
|
||||
echo $OUTPUT->heading(get_string('registerwithmoodleorgremove', 'core_hub'), 3, 'main');
|
||||
$siteunregistrationform->display();
|
||||
echo $OUTPUT->footer();
|
||||
exit;
|
||||
@@ -82,7 +82,7 @@ if ($fromform = $siteregistrationform->get_data()) {
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
// Current status of registration on Moodle.net.
|
||||
// Current status of registration.
|
||||
|
||||
$notificationtype = \core\output\notification::NOTIFY_ERROR;
|
||||
if (\core\hub\registration::is_registered()) {
|
||||
@@ -104,11 +104,11 @@ if (\core\hub\registration::is_registered()) {
|
||||
|
||||
// Heading.
|
||||
if (\core\hub\registration::is_registered()) {
|
||||
echo $OUTPUT->heading(get_string('updatesite', 'hub', 'Moodle.net'));
|
||||
echo $OUTPUT->heading(get_string('registerwithmoodleorgupdate', 'core_hub'));
|
||||
} else if ($isinitialregistration) {
|
||||
echo $OUTPUT->heading(get_string('completeregistration', 'hub'));
|
||||
echo $OUTPUT->heading(get_string('registerwithmoodleorgcomplete', 'core_hub'));
|
||||
} else {
|
||||
echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'admin'));
|
||||
echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'core_hub'));
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('core', 'admin');
|
||||
|
||||
@@ -40,7 +40,7 @@ $token = optional_param('token', '', PARAM_TEXT);
|
||||
admin_externalpage_setup('registrationmoodleorg');
|
||||
|
||||
if ($url !== HUB_MOODLEORGHUBURL) {
|
||||
// Allow other plugins to renew registration on hubs other than moodle.net . Plugins implementing this
|
||||
// Allow other plugins to renew registration on custom hubs. Plugins implementing this
|
||||
// callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
|
||||
$callbacks = get_plugins_with_function('hub_registration');
|
||||
foreach ($callbacks as $plugintype => $plugins) {
|
||||
@@ -56,7 +56,7 @@ if ($url !== HUB_MOODLEORGHUBURL) {
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('renewregistration', 'hub'), 3, 'main');
|
||||
$hublink = html_writer::tag('a', 'Moodle.net', array('href' => HUB_MOODLEORGHUBURL));
|
||||
$hublink = html_writer::tag('a', HUB_MOODLEORGHUBURL, array('href' => HUB_MOODLEORGHUBURL));
|
||||
|
||||
$deletedregmsg = get_string('previousregistrationdeleted', 'hub', $hublink);
|
||||
|
||||
|
||||
+27
-4
@@ -1469,9 +1469,11 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$class = 'requires-failed';
|
||||
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-danger');
|
||||
}
|
||||
$requires[] = html_writer::tag('li',
|
||||
html_writer::span(get_string('moodleversion', 'core_plugin', $plugin->versionrequires), 'dep dep-core').
|
||||
' '.$label, array('class' => $class));
|
||||
if ($reqinfo->reqver != ANY_VERSION) {
|
||||
$requires[] = html_writer::tag('li',
|
||||
html_writer::span(get_string('moodleversion', 'core_plugin', $plugin->versionrequires), 'dep dep-core').
|
||||
' '.$label, array('class' => $class));
|
||||
}
|
||||
|
||||
} else {
|
||||
$actions = array();
|
||||
@@ -2110,6 +2112,27 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_HTML, ['noclean' => true]);
|
||||
|
||||
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub'), FORMAT_MARKDOWN);
|
||||
|
||||
$out .= html_writer::link(
|
||||
new moodle_url('/admin/settings.php', ['section' => 'moodleservices']),
|
||||
$this->output->pix_icon('i/info', '').' '.get_string('registerwithmoodleorginfoapp', 'core_hub'),
|
||||
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
|
||||
);
|
||||
|
||||
$out .= html_writer::link(
|
||||
HUB_MOODLEORGHUBURL,
|
||||
$this->output->pix_icon('i/stats', '').' '.get_string('registerwithmoodleorginfostats', 'core_hub'),
|
||||
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
|
||||
);
|
||||
|
||||
$out .= html_writer::link(
|
||||
HUB_MOODLEORGHUBURL.'/sites',
|
||||
$this->output->pix_icon('i/location', '').' '.get_string('registerwithmoodleorginfosites', 'core_hub'),
|
||||
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
|
||||
);
|
||||
|
||||
return $this->output->box($out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,12 @@ if (!is_siteadmin()) {
|
||||
}
|
||||
|
||||
$admisselector = new core_role_admins_existing_selector();
|
||||
$admisselector->set_extra_fields(array('username', 'email'));
|
||||
|
||||
$potentialadmisselector = new core_role_admins_potential_selector();
|
||||
$potentialadmisselector->set_extra_fields(array('username', 'email'));
|
||||
|
||||
if (optional_param('add', false, PARAM_BOOL) and confirm_sesskey()) {
|
||||
if ($userstoadd = $potentialadmisselector->get_selected_users()) {
|
||||
$user = reset($userstoadd);
|
||||
$username = fullname($user) . " ($user->username, $user->email)";
|
||||
$username = $potentialadmisselector->output_user($user);
|
||||
echo $OUTPUT->header();
|
||||
$yesurl = new moodle_url('/admin/roles/admins.php', array('confirmadd'=>$user->id, 'sesskey'=>sesskey()));
|
||||
echo $OUTPUT->confirm(get_string('confirmaddadmin', 'core_role', $username), $yesurl, $PAGE->url);
|
||||
@@ -58,7 +55,7 @@ if (optional_param('add', false, PARAM_BOOL) and confirm_sesskey()) {
|
||||
if ($USER->id == $user->id) {
|
||||
// Can not remove self.
|
||||
} else {
|
||||
$username = fullname($user) . " ($user->username, $user->email)";
|
||||
$username = $admisselector->output_user($user);
|
||||
echo $OUTPUT->header();
|
||||
$yesurl = new moodle_url('/admin/roles/admins.php', array('confirmdel'=>$user->id, 'sesskey'=>sesskey()));
|
||||
echo $OUTPUT->confirm(get_string('confirmdeladmin', 'core_role', $username), $yesurl, $PAGE->url);
|
||||
|
||||
@@ -52,7 +52,7 @@ if ($data) {
|
||||
// Manage Moodle debugging options.
|
||||
$debuglevel = $CFG->debug;
|
||||
$debugdisplay = $CFG->debugdisplay;
|
||||
$debugsmtp = $CFG->debugsmtp;
|
||||
$debugsmtp = $CFG->debugsmtp ?? null; // This might not be set as it's optional.
|
||||
$CFG->debugdisplay = true;
|
||||
$CFG->debugsmtp = true;
|
||||
$CFG->debug = 15;
|
||||
@@ -66,7 +66,12 @@ if ($data) {
|
||||
// Restore Moodle debugging options.
|
||||
$CFG->debug = $debuglevel;
|
||||
$CFG->debugdisplay = $debugdisplay;
|
||||
$CFG->debugsmtp = $debugsmtp;
|
||||
|
||||
// Restore the debugsmtp config, if it was set originally.
|
||||
unset($CFG->debugsmtp);
|
||||
if (!is_null($debugsmtp)) {
|
||||
$CFG->debugsmtp = $debugsmtp;
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$msgparams = new stdClass();
|
||||
|
||||
@@ -48,13 +48,11 @@ class behat_admin extends behat_base {
|
||||
* @param TableNode $table
|
||||
*/
|
||||
public function i_set_the_following_administration_settings_values(TableNode $table) {
|
||||
|
||||
if (!$data = $table->getRowsHash()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($data as $label => $value) {
|
||||
|
||||
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]);
|
||||
|
||||
// Search by label.
|
||||
@@ -76,38 +74,17 @@ class behat_admin extends behat_base {
|
||||
"@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@for]";
|
||||
$fieldnode = $this->find('xpath', $fieldxpath, $exception);
|
||||
|
||||
$formfieldtypenode = $this->find('xpath', $fieldxpath .
|
||||
"/ancestor::div[contains(concat(' ', @class, ' '), ' form-setting ')]" .
|
||||
"/child::div[contains(concat(' ', @class, ' '), ' form-')]/child::*/parent::div");
|
||||
|
||||
} catch (ElementNotFoundException $e) {
|
||||
|
||||
// Multi element settings, interacting only the first one.
|
||||
$fieldxpath = "//*[label[contains(., $label)]|span[contains(., $label)]]" .
|
||||
"/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
|
||||
"/descendant::div[contains(concat(' ', @class, ' '), ' form-group ')]" .
|
||||
"/descendant::*[self::input | self::textarea | self::select]" .
|
||||
"[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]";
|
||||
$fieldnode = $this->find('xpath', $fieldxpath);
|
||||
|
||||
// It is the same one that contains the type.
|
||||
$formfieldtypenode = $fieldnode;
|
||||
}
|
||||
|
||||
// Getting the class which contains the field type.
|
||||
$classes = explode(' ', $formfieldtypenode->getAttribute('class'));
|
||||
$type = false;
|
||||
foreach ($classes as $class) {
|
||||
if (substr($class, 0, 5) == 'form-') {
|
||||
$type = substr($class, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiating the appropiate field type.
|
||||
$field = behat_field_manager::get_field_instance($type, $fieldnode, $this->getSession());
|
||||
$field->set_value($value);
|
||||
|
||||
$this->find_button(get_string('savechanges'))->press();
|
||||
$this->execute('behat_forms::i_set_the_field_with_xpath_to', [$fieldxpath, $value]);
|
||||
$this->execute("behat_general::i_click_on", [get_string('savechanges'), 'button']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,9 @@ class predict_models extends \core\task\scheduled_task {
|
||||
}
|
||||
|
||||
foreach ($models as $model) {
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_analytics');
|
||||
|
||||
$result = $model->predict();
|
||||
|
||||
// Reset the page as some indicators may call external functions that overwrite the page context.
|
||||
@@ -66,7 +69,6 @@ class predict_models extends \core\task\scheduled_task {
|
||||
|
||||
if ($result) {
|
||||
echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
|
||||
$renderer = $PAGE->get_renderer('tool_analytics');
|
||||
echo $renderer->render_get_predictions_results(false, array(), $result, $model->get_analyser()->get_logs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ class train_models extends \core\task\scheduled_task {
|
||||
continue;
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_analytics');
|
||||
|
||||
$result = $model->train();
|
||||
|
||||
// Reset the page as some indicators may call external functions that overwrite the page context.
|
||||
@@ -77,8 +79,6 @@ class train_models extends \core\task\scheduled_task {
|
||||
|
||||
if ($result) {
|
||||
echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_analytics');
|
||||
echo $renderer->render_get_predictions_results($result, $model->get_analyser()->get_logs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,8 @@ if ($options['reuse-prev-analysed']) {
|
||||
mtrace(get_string('evaluationinbatches', 'tool_analytics'));
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_analytics');
|
||||
|
||||
$analyseroptions = array(
|
||||
'filter' => $options['filter'],
|
||||
'timesplitting' => $options['timesplitting'],
|
||||
@@ -114,7 +116,6 @@ $results = $model->evaluate($analyseroptions);
|
||||
// Reset the page as some indicators may call external functions that overwrite the page context.
|
||||
\tool_analytics\output\helper::reset_page();
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_analytics');
|
||||
echo $renderer->render_evaluate_results($results, $model->get_analyser()->get_logs());
|
||||
|
||||
// Check that we have, at leasa,t 1 valid dataset (not necessarily good) to use.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@tool_behat
|
||||
Feature: Behat steps for interacting with form work
|
||||
In order to test my Moodle code
|
||||
As a developer
|
||||
I need the Behat steps for form elements to work reliably
|
||||
|
||||
@javascript
|
||||
Scenario: Test fields in containers
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
When I log in as "admin"
|
||||
And I am on "Course 1" course homepage
|
||||
# Just get to any form.
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the field "Course full name" in the "General" "fieldset" to "Frog"
|
||||
And I set the following fields in the "Appearance" "fieldset" to these values:
|
||||
| Show activity reports | Yes |
|
||||
| Number of announcements | 1 |
|
||||
Then the field "Show activity reports" in the "Appearance" "fieldset" matches value "Yes"
|
||||
And the field "Show activity reports" in the "Appearance" "fieldset" does not match value "No"
|
||||
And the following fields in the "region-main" "region" match these values:
|
||||
| Course full name | Frog |
|
||||
| Number of announcements | 1 |
|
||||
And the following fields in the "region-main" "region" do not match these values:
|
||||
| Course full name | Course 1 |
|
||||
| Number of announcements | 5 |
|
||||
@@ -222,6 +222,48 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean the legacy role assignments which are stale.
|
||||
$paramsclean['usercontext'] = CONTEXT_USER;
|
||||
$paramsclean['component'] = 'tool_cohortroles';
|
||||
$sql = 'SELECT DISTINCT(ra.id), ra.roleid, ra.userid, ra.contextid, ctx.instanceid
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx ON ctx.id = ra.contextid AND ctx.contextlevel = :usercontext
|
||||
JOIN {cohort_members} cm ON cm.userid = ctx.instanceid
|
||||
LEFT JOIN {tool_cohortroles} tc ON tc.cohortid = cm.cohortid
|
||||
AND tc.userid = ra.userid
|
||||
AND tc.roleid = ra.roleid
|
||||
WHERE ra.component = :component
|
||||
AND tc.id is null';
|
||||
if ($candidatelegacyassignments = $DB->get_records_sql($sql, $paramsclean)) {
|
||||
$sql = 'SELECT DISTINCT(ra.id)
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx ON ctx.id = ra.contextid AND ctx.contextlevel = :usercontext
|
||||
JOIN {cohort_members} cm ON cm.userid = ctx.instanceid
|
||||
JOIN {tool_cohortroles} tc ON tc.cohortid = cm.cohortid AND tc.userid = ra.userid
|
||||
WHERE ra.component = :component';
|
||||
if ($currentvalidroleassignments = $DB->get_records_sql($sql, $paramsclean)) {
|
||||
foreach ($candidatelegacyassignments as $candidate) {
|
||||
if (!array_key_exists($candidate->id, $currentvalidroleassignments)) {
|
||||
role_unassign($candidate->roleid, $candidate->userid, $candidate->contextid, 'tool_cohortroles');
|
||||
$rolesremoved[] = array(
|
||||
'useridassignedto' => $candidate->userid,
|
||||
'useridassignedover' => $candidate->instanceid,
|
||||
'roleid' => $candidate->roleid
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($candidatelegacyassignments as $candidate) {
|
||||
role_unassign($candidate->roleid, $candidate->userid, $candidate->contextid, 'tool_cohortroles');
|
||||
$rolesremoved[] = array(
|
||||
'useridassignedto' => $candidate->userid,
|
||||
'useridassignedover' => $candidate->instanceid,
|
||||
'roleid' => $candidate->roleid
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array('rolesadded' => $rolesadded, 'rolesremoved' => $rolesremoved);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,5 +103,4 @@ class cohort_role_assignment extends persistent {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
|
||||
* Setup function- we will create a course and add an assign instance to it.
|
||||
*/
|
||||
protected function setUp() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create some users.
|
||||
@@ -133,14 +131,91 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
|
||||
|
||||
public function test_delete_cohort_role_assignment() {
|
||||
$this->setAdminUser();
|
||||
$params = (object) array(
|
||||
// Create a cohort role assigment.
|
||||
$params = (object) [
|
||||
'userid' => $this->userassignto->id,
|
||||
'roleid' => $this->roleid,
|
||||
'cohortid' => $this->cohort->id
|
||||
);
|
||||
$result = api::create_cohort_role_assignment($params);
|
||||
$worked = api::delete_cohort_role_assignment($result->get('id'));
|
||||
$this->assertTrue($worked);
|
||||
];
|
||||
$cohortroleassignment = api::create_cohort_role_assignment($params);
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
$rolesadded = [
|
||||
[
|
||||
'useridassignedto' => $this->userassignto->id,
|
||||
'useridassignedover' => $this->userassignover->id,
|
||||
'roleid' => $this->roleid
|
||||
]
|
||||
];
|
||||
$expected = [
|
||||
'rolesadded' => $rolesadded,
|
||||
'rolesremoved' => []
|
||||
];
|
||||
$this->assertEquals($sync, $expected);
|
||||
|
||||
// Delete the cohort role assigment and confirm the roles are removed.
|
||||
$result = api::delete_cohort_role_assignment($cohortroleassignment->get('id'));
|
||||
$this->assertTrue($result);
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
$expected = [
|
||||
'rolesadded' => [],
|
||||
'rolesremoved' => $rolesadded
|
||||
];
|
||||
$this->assertEquals($expected, $sync);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case verifying that syncing won't remove role assignments if they are valid for another cohort role assignment.
|
||||
*/
|
||||
public function test_delete_cohort_role_assignment_cohorts_having_same_members() {
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create 2 cohorts, with a 1 user (user1) present in both,
|
||||
// and user2 and user3 members of 1 cohort each.
|
||||
$cohort1 = $this->getDataGenerator()->create_cohort();
|
||||
$cohort2 = $this->getDataGenerator()->create_cohort();
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
cohort_add_member($cohort1->id, $user1->id);
|
||||
cohort_add_member($cohort1->id, $user2->id);
|
||||
cohort_add_member($cohort2->id, $user1->id);
|
||||
cohort_add_member($cohort2->id, $user3->id);
|
||||
|
||||
// And a role and a user to assign that role to.
|
||||
$user4 = $this->getDataGenerator()->create_user(); // A cohort manager, for example.
|
||||
$roleid = create_role('Role 1', 'myrole', 'test');
|
||||
|
||||
// Assign the role for user4 in both cohorts.
|
||||
$params = (object) [
|
||||
'userid' => $user4->id,
|
||||
'roleid' => $roleid,
|
||||
'cohortid' => $cohort1->id
|
||||
];
|
||||
$cohort1roleassignment = api::create_cohort_role_assignment($params);
|
||||
$params->cohortid = $cohort2->id;
|
||||
$cohort2roleassignment = api::create_cohort_role_assignment($params);
|
||||
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
|
||||
// There is no guarantee about the order of roles assigned.
|
||||
// so confirm we have 3 role assignments, and they are for the users 1, 2 and 3.
|
||||
$this->assertCount(3, $sync['rolesadded']);
|
||||
$addedusers = array_column($sync['rolesadded'], 'useridassignedover');
|
||||
$this->assertContains($user1->id, $addedusers);
|
||||
$this->assertContains($user2->id, $addedusers);
|
||||
$this->assertContains($user3->id, $addedusers);
|
||||
|
||||
// Remove the role assignment for user4/cohort1.
|
||||
// Verify only 1 role is unassigned as the others are still valid for the other cohort role assignment.
|
||||
$result = api::delete_cohort_role_assignment($cohort1roleassignment->get('id'));
|
||||
$this->assertTrue($result);
|
||||
|
||||
$sync = api::sync_all_cohort_roles();
|
||||
|
||||
$this->assertCount(0, $sync['rolesadded']);
|
||||
$this->assertCount(1, $sync['rolesremoved']);
|
||||
$removedusers = array_column($sync['rolesremoved'], 'useridassignedover');
|
||||
$this->assertContains($user2->id, $removedusers);
|
||||
}
|
||||
|
||||
public function test_list_cohort_role_assignments() {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/url","core/str"],function(a,b,c){var d=a('<img alt="" src="'+b.imageUrl("t/expanded")+'"/>'),e=a('<img alt="" src="'+b.imageUrl("t/collapsed")+'"/>');return{expandCollapse:function(a,b){a.hasClass("hide")?(a.removeClass("hide"),a.addClass("visible"),a.attr("aria-expanded",!0),b.find(":header i.fa").removeClass("fa-plus-square"),b.find(":header i.fa").addClass("fa-minus-square"),b.find(":header img.icon").attr("src",d.attr("src"))):(a.removeClass("visible"),a.addClass("hide"),a.attr("aria-expanded",!1),b.find(":header i.fa").removeClass("fa-minus-square"),b.find(":header i.fa").addClass("fa-plus-square"),b.find(":header img.icon").attr("src",e.attr("src")))},expandCollapseAll:function(b){var f="visible"==b?"hide":"visible",g="visible"==b,h="visible"==b?"fa-plus-square":"fa-minus-square",i="visible"==b?"fa-minus-square":"fa-plus-square",j="visible"==b?d.attr("src"):e.attr("src");a("."+f).each(function(){a(this).removeClass(f),a(this).addClass(b),a(this).attr("aria-expanded",g)}),a(".tool_dataprivacy-expand-all").data("visibilityState",f),c.get_string(f,"tool_dataprivacy").then(function(b){a(".tool_dataprivacy-expand-all").html(b)})["catch"](Notification.exception),a(":header i.fa").each(function(){a(this).removeClass(h),a(this).addClass(i)}),a(":header img.icon").each(function(){a(this).attr("src",j)})}}});
|
||||
define(["jquery","core/url","core/str"],function(a,b,c){var d=a('<img alt="" src="'+b.imageUrl("t/expanded")+'"/>'),e=a('<img alt="" src="'+b.imageUrl("t/collapsed")+'"/>'),f={EXPAND:"fa-caret-right",COLLAPSE:"fa-caret-down"};return{expandCollapse:function(a,b){a.hasClass("hide")?(a.removeClass("hide"),a.addClass("visible"),a.attr("aria-expanded",!0),b.find(":header i.fa").removeClass(f.EXPAND),b.find(":header i.fa").addClass(f.COLLAPSE),b.find(":header img.icon").attr("src",d.attr("src"))):(a.removeClass("visible"),a.addClass("hide"),a.attr("aria-expanded",!1),b.find(":header i.fa").removeClass(f.COLLAPSE),b.find(":header i.fa").addClass(f.EXPAND),b.find(":header img.icon").attr("src",e.attr("src")))},expandCollapseAll:function(b){var g="visible"==b?"hide":"visible",h="visible"==b,i="visible"==b?f.EXPAND:f.COLLAPSE,j="visible"==b?f.COLLAPSE:f.EXPAND,k="visible"==b?d.attr("src"):e.attr("src");a("."+g).each(function(){a(this).removeClass(g),a(this).addClass(b),a(this).attr("aria-expanded",h)}),a(".tool_dataprivacy-expand-all").data("visibilityState",g),c.get_string(g,"tool_dataprivacy").then(function(b){a(".tool_dataprivacy-expand-all").html(b)})["catch"](Notification.exception),a(":header i.fa").each(function(){a(this).removeClass(i),a(this).addClass(j)}),a(":header img.icon").each(function(){a(this).attr("src",k)})}}});
|
||||
@@ -28,6 +28,14 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
|
||||
var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
|
||||
|
||||
/*
|
||||
* Class names to apply when expanding/collapsing nodes.
|
||||
*/
|
||||
var CLASSES = {
|
||||
EXPAND: 'fa-caret-right',
|
||||
COLLAPSE: 'fa-caret-down'
|
||||
};
|
||||
|
||||
return /** @alias module:tool_dataprivacy/expand-collapse */ {
|
||||
/**
|
||||
* Expand or collapse a selected node.
|
||||
@@ -40,15 +48,15 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
targetnode.removeClass('hide');
|
||||
targetnode.addClass('visible');
|
||||
targetnode.attr('aria-expanded', true);
|
||||
thisnode.find(':header i.fa').removeClass('fa-plus-square');
|
||||
thisnode.find(':header i.fa').addClass('fa-minus-square');
|
||||
thisnode.find(':header i.fa').removeClass(CLASSES.EXPAND);
|
||||
thisnode.find(':header i.fa').addClass(CLASSES.COLLAPSE);
|
||||
thisnode.find(':header img.icon').attr('src', expandedImage.attr('src'));
|
||||
} else {
|
||||
targetnode.removeClass('visible');
|
||||
targetnode.addClass('hide');
|
||||
targetnode.attr('aria-expanded', false);
|
||||
thisnode.find(':header i.fa').removeClass('fa-minus-square');
|
||||
thisnode.find(':header i.fa').addClass('fa-plus-square');
|
||||
thisnode.find(':header i.fa').removeClass(CLASSES.COLLAPSE);
|
||||
thisnode.find(':header i.fa').addClass(CLASSES.EXPAND);
|
||||
thisnode.find(':header img.icon').attr('src', collapsedImage.attr('src'));
|
||||
}
|
||||
},
|
||||
@@ -61,8 +69,8 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
expandCollapseAll: function(nextstate) {
|
||||
var currentstate = (nextstate == 'visible') ? 'hide' : 'visible';
|
||||
var ariaexpandedstate = (nextstate == 'visible') ? true : false;
|
||||
var iconclassnow = (nextstate == 'visible') ? 'fa-plus-square' : 'fa-minus-square';
|
||||
var iconclassnext = (nextstate == 'visible') ? 'fa-minus-square' : 'fa-plus-square';
|
||||
var iconclassnow = (nextstate == 'visible') ? CLASSES.EXPAND : CLASSES.COLLAPSE;
|
||||
var iconclassnext = (nextstate == 'visible') ? CLASSES.COLLAPSE : CLASSES.EXPAND;
|
||||
var imagenow = (nextstate == 'visible') ? expandedImage.attr('src') : collapsedImage.attr('src');
|
||||
$('.' + currentstate).each(function() {
|
||||
$(this).removeClass(currentstate);
|
||||
|
||||
@@ -774,7 +774,8 @@ class api {
|
||||
public static function can_create_data_deletion_request_for_self(int $userid = null): bool {
|
||||
global $USER;
|
||||
$userid = $userid ?: $USER->id;
|
||||
return has_capability('tool/dataprivacy:requestdelete', \context_user::instance($userid), $userid);
|
||||
return has_capability('tool/dataprivacy:requestdelete', \context_user::instance($userid), $userid)
|
||||
&& !is_primary_admin($userid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -803,7 +804,7 @@ class api {
|
||||
global $USER;
|
||||
$requesterid = $requesterid ?: $USER->id;
|
||||
return has_capability('tool/dataprivacy:makedatadeletionrequestsforchildren', \context_user::instance($userid),
|
||||
$requesterid);
|
||||
$requesterid) && !is_primary_admin($userid);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -713,7 +713,6 @@ class external extends external_api {
|
||||
|
||||
list($sql, $params) = users_search_sql($query, '', false, $extrafields, $excludedusers);
|
||||
$users = $DB->get_records_select('user', $sql, $params, $sort, $fields, 0, 30);
|
||||
|
||||
$useroptions = [];
|
||||
foreach ($users as $user) {
|
||||
$useroption = (object)[
|
||||
@@ -722,9 +721,10 @@ class external extends external_api {
|
||||
];
|
||||
$useroption->extrafields = [];
|
||||
foreach ($extrafields as $extrafield) {
|
||||
// Sanitize the extra fields to prevent potential XSS exploit.
|
||||
$useroption->extrafields[] = (object)[
|
||||
'name' => $extrafield,
|
||||
'value' => $user->$extrafield
|
||||
'value' => s($user->$extrafield)
|
||||
];
|
||||
}
|
||||
$useroptions[$user->id] = $useroption;
|
||||
|
||||
@@ -127,13 +127,17 @@ class process_data_request_task extends adhoc_task {
|
||||
$thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
|
||||
$completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY;
|
||||
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
|
||||
// Delete the data.
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
// Delete the data for users other than the primary admin, which is rejected.
|
||||
if (is_primary_admin($foruser->id)) {
|
||||
$completestatus = api::DATAREQUEST_STATUS_REJECTED;
|
||||
} else {
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$manager->delete_data_for_user($approvedclcollection);
|
||||
$completestatus = api::DATAREQUEST_STATUS_DELETED;
|
||||
$deleteuser = !$foruser->deleted;
|
||||
$manager->delete_data_for_user($approvedclcollection);
|
||||
$completestatus = api::DATAREQUEST_STATUS_DELETED;
|
||||
$deleteuser = !$foruser->deleted;
|
||||
}
|
||||
}
|
||||
|
||||
// When the preparation of the metadata finishes, update the request status to awaiting approval.
|
||||
|
||||
@@ -47,6 +47,6 @@
|
||||
<span>
|
||||
<span>{{fullname}}</span>
|
||||
{{#extrafields}}
|
||||
<span><small>{{value}}</small></span>
|
||||
<span><small>{{{value}}}</small></span>
|
||||
{{/extrafields}}
|
||||
</span>
|
||||
|
||||
@@ -303,6 +303,29 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$requestid = $datarequest->get('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deletion requests for the primary admin are rejected
|
||||
*/
|
||||
public function test_reject_data_deletion_request_primary_admin() {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$datarequest = api::create_data_request(get_admin()->id, api::DATAREQUEST_TYPE_DELETE);
|
||||
|
||||
// Approve the request and execute the ad-hoc process task.
|
||||
ob_start();
|
||||
api::approve_data_request($datarequest->get('id'));
|
||||
$this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
$request = api::get_request($datarequest->get('id'));
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_REJECTED, $request->get('status'));
|
||||
|
||||
// Confirm they weren't deleted.
|
||||
$user = core_user::get_user($request->get('userid'));
|
||||
core_user::require_active_user($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::can_contact_dpo()
|
||||
*/
|
||||
@@ -2126,6 +2149,33 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$this->assertFalse(api::can_create_data_deletion_request_for_self());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test primary admin cannot create data deletion request for themselves
|
||||
*/
|
||||
public function test_can_create_data_deletion_request_for_self_primary_admin() {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$this->assertFalse(api::can_create_data_deletion_request_for_self());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test secondary admin can create data deletion request for themselves
|
||||
*/
|
||||
public function test_can_create_data_deletion_request_for_self_secondary_admin() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$admin1 = $this->getDataGenerator()->create_user();
|
||||
$admin2 = $this->getDataGenerator()->create_user();
|
||||
|
||||
// The primary admin is the one listed first in the 'siteadmins' config.
|
||||
set_config('siteadmins', implode(',', [$admin1->id, $admin2->id]));
|
||||
|
||||
// Set the current user as the second admin (non-primary).
|
||||
$this->setUser($admin2);
|
||||
|
||||
$this->assertTrue(api::can_create_data_deletion_request_for_self());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user can create data deletion request for themselves if they have
|
||||
* "tool/dataprivacy:requestdelete" capability.
|
||||
@@ -2171,7 +2221,8 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check parents can create data deletion request for their children but not others.
|
||||
* Check parents can create data deletion request for their children (unless the child is the primary admin),
|
||||
* but not other users.
|
||||
*
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
@@ -2194,5 +2245,9 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$this->setUser($parent);
|
||||
$this->assertTrue(api::can_create_data_deletion_request_for_children($child->id));
|
||||
$this->assertFalse(api::can_create_data_deletion_request_for_children($otheruser->id));
|
||||
|
||||
// Now make child the primary admin, confirm parent can't make deletion request.
|
||||
set_config('siteadmins', $child->id);
|
||||
$this->assertFalse(api::can_create_data_deletion_request_for_children($child->id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,12 @@ Feature: Data delete from the privacy API
|
||||
And I follow "Profile" in the user menu
|
||||
Then I should not see "Delete my account"
|
||||
|
||||
@javascript
|
||||
Scenario: As a primary admin, the link to create a data deletion request should not be shown.
|
||||
Given I log in as "admin"
|
||||
When I follow "Profile" in the user menu
|
||||
Then I should not see "Delete my account"
|
||||
|
||||
@javascript
|
||||
Scenario: As a Privacy Officer, I cannot Approve to Deny deletion data request without permission.
|
||||
Given the following "permission overrides" exist:
|
||||
|
||||
@@ -75,7 +75,7 @@ class manage_competency_frameworks_page implements renderable, templatable {
|
||||
);
|
||||
$this->navigation[] = $addpage;
|
||||
$competenciesrepository = new single_button(
|
||||
new moodle_url('https://moodle.net/competencies'),
|
||||
new moodle_url('https://archive.moodle.net/competencies'),
|
||||
get_string('competencyframeworksrepository', 'tool_lp'),
|
||||
'get'
|
||||
);
|
||||
|
||||
@@ -206,6 +206,12 @@ class api {
|
||||
$identityprovidersdata = \auth_plugin_base::prepare_identity_providers_for_output($identityproviders, $OUTPUT);
|
||||
if (!empty($identityprovidersdata)) {
|
||||
$settings['identityproviders'] = $identityprovidersdata;
|
||||
// Clean URLs to avoid breaking Web Services.
|
||||
// We can't do it in prepare_identity_providers_for_output() because it may break the web output.
|
||||
foreach ($settings['identityproviders'] as &$ip) {
|
||||
$ip['url'] = (!empty($ip['url'])) ? clean_param($ip['url'], PARAM_URL) : '';
|
||||
$ip['iconurl'] = (!empty($ip['iconurl'])) ? clean_param($ip['iconurl'], PARAM_URL) : '';
|
||||
}
|
||||
}
|
||||
|
||||
// If age is verified, return also the admin contact details.
|
||||
|
||||
@@ -91,7 +91,7 @@ $string['readingthisemailgettheapp'] = 'Reading this in an email? <a href="{$a}"
|
||||
$string['remoteaddons'] = 'Remote add-ons';
|
||||
$string['selfsignedoruntrustedcertificatewarning'] = 'It seems that the HTTPS certificate is self-signed or not trusted. The mobile app will only work with trusted sites.';
|
||||
$string['setuplink'] = 'App download page';
|
||||
$string['setuplink_desc'] = 'URL of page with links to download the mobile app from the App Store and Google Play.';
|
||||
$string['setuplink_desc'] = 'URL of page with options to download the mobile app from the App Store and Google Play. The app download page link is displayed in the page footer and in a user\'s profile. Leave blank to not display a link.';
|
||||
$string['smartappbanners'] = 'App Banners';
|
||||
$string['typeoflogin'] = 'Type of login';
|
||||
$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
|
||||
|
||||
@@ -99,6 +99,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
|
||||
);
|
||||
$this->assertEquals($expected, $result);
|
||||
|
||||
$this->setAdminUser();
|
||||
// Change some values.
|
||||
set_config('registerauth', 'email');
|
||||
$authinstructions = 'Something with <b>html tags</b>';
|
||||
@@ -112,6 +113,18 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
|
||||
set_config('lang', 'a_b'); // Set invalid lang.
|
||||
set_config('disabledfeatures', 'myoverview', 'tool_mobile');
|
||||
|
||||
// Enable couple of issuers.
|
||||
$issuer = \core\oauth2\api::create_standard_issuer('google');
|
||||
$irecord = $issuer->to_record();
|
||||
$irecord->clientid = 'mock';
|
||||
$irecord->clientsecret = 'mock';
|
||||
core\oauth2\api::update_issuer($irecord);
|
||||
|
||||
set_config('hostname', 'localhost', 'auth_cas');
|
||||
set_config('auth_logo', 'http://invalidurl.com//invalid/', 'auth_cas');
|
||||
|
||||
set_config('auth', 'oauth2,cas');
|
||||
|
||||
list($authinstructions, $notusedformat) = external_format_text($authinstructions, FORMAT_MOODLE, $context->id);
|
||||
$expected['registerauth'] = 'email';
|
||||
$expected['authinstructions'] = $authinstructions;
|
||||
@@ -133,7 +146,26 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
|
||||
|
||||
$result = external::get_public_config();
|
||||
$result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
|
||||
// First check providers.
|
||||
$identityproviders = $result['identityproviders'];
|
||||
unset($result['identityproviders']);
|
||||
|
||||
$this->assertEquals('Google', $identityproviders[0]['name']);
|
||||
$this->assertEquals($irecord->image, $identityproviders[0]['iconurl']);
|
||||
$this->assertContains($CFG->wwwroot, $identityproviders[0]['url']);
|
||||
|
||||
$this->assertEquals('CAS', $identityproviders[1]['name']);
|
||||
$this->assertEmpty($identityproviders[1]['iconurl']);
|
||||
$this->assertContains($CFG->wwwroot, $identityproviders[1]['url']);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
|
||||
// Change providers img.
|
||||
$newurl = 'validimage.png';
|
||||
set_config('auth_logo', $newurl, 'auth_cas');
|
||||
$result = external::get_public_config();
|
||||
$result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
|
||||
$this->assertContains($newurl, $result['identityproviders'][1]['iconurl']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
use \tool_monitor\privacy\provider;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\approved_userlist;
|
||||
use \core_privacy\tests\provider_testcase;
|
||||
|
||||
/**
|
||||
* Privacy test for the event monitor
|
||||
@@ -37,7 +38,7 @@ use \core_privacy\local\request\approved_userlist;
|
||||
* @copyright 2018 Adrian Greeve <adriangreeve.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_monitor_privacy_testcase extends advanced_testcase {
|
||||
class tool_monitor_privacy_testcase extends provider_testcase {
|
||||
|
||||
/**
|
||||
* Set up method.
|
||||
|
||||
@@ -46,6 +46,9 @@ class issuer extends persistent {
|
||||
/** @var string $type */
|
||||
protected $type;
|
||||
|
||||
/** @var boolean $showrequireconfirm Whether to show the require confirmation email checkbox or not. */
|
||||
protected $showrequireconfirm;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -71,6 +74,7 @@ class issuer extends persistent {
|
||||
if (array_key_exists('type', $customdata)) {
|
||||
$this->type = $customdata['type'];
|
||||
}
|
||||
$this->showrequireconfirm = !empty($customdata['showrequireconfirm']);
|
||||
parent::__construct($action, $customdata, $method, $target, $attributes, $editable, $ajaxformdata);
|
||||
}
|
||||
|
||||
@@ -157,9 +161,11 @@ class issuer extends persistent {
|
||||
$mform->addElement('checkbox', 'showonloginpage', get_string('issuershowonloginpage', 'tool_oauth2'));
|
||||
$mform->addHelpButton('showonloginpage', 'issuershowonloginpage', 'tool_oauth2');
|
||||
|
||||
// Require confirmation email for new accounts.
|
||||
$mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
|
||||
$mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
|
||||
if ($this->showrequireconfirm) {
|
||||
// Require confirmation email for new accounts.
|
||||
$mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
|
||||
$mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
|
||||
}
|
||||
|
||||
$mform->addElement('hidden', 'sortorder');
|
||||
$mform->setType('sortorder', PARAM_INT);
|
||||
|
||||
@@ -58,7 +58,24 @@ if ($action == 'edit') {
|
||||
$PAGE->navbar->add(get_string('createnewissuer', 'tool_oauth2'));
|
||||
}
|
||||
|
||||
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer]);
|
||||
$showrequireconfirm = false;
|
||||
if (!empty($issuerid)) {
|
||||
// Show the "Require confirmation email" checkbox for trusted issuers like Google, Facebook and Microsoft.
|
||||
$likefacebook = $DB->sql_like('url', ':facebook');
|
||||
$likegoogle = $DB->sql_like('url', ':google');
|
||||
$likemicrosoft = $DB->sql_like('url', ':microsoft');
|
||||
$params = [
|
||||
'issuerid' => $issuerid,
|
||||
'facebook' => '%facebook%',
|
||||
'google' => '%google%',
|
||||
'microsoft' => '%microsoft%',
|
||||
];
|
||||
$select = "issuerid = :issuerid AND ($likefacebook OR $likegoogle OR $likemicrosoft)";
|
||||
// We're querying from the oauth2_endpoint table because the base URLs of FB and Microsoft can be empty in the issuer table.
|
||||
$showrequireconfirm = $DB->record_exists_select('oauth2_endpoint', $select, $params);
|
||||
}
|
||||
|
||||
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'showrequireconfirm' => $showrequireconfirm]);
|
||||
}
|
||||
|
||||
if ($mform && $mform->is_cancelled()) {
|
||||
@@ -108,9 +125,11 @@ if ($mform && $mform->is_cancelled()) {
|
||||
|
||||
$type = required_param('type', PARAM_ALPHA);
|
||||
$docs = required_param('docslink', PARAM_ALPHAEXT);
|
||||
$showrequireconfirm = optional_param('showrequireconfirm', false, PARAM_BOOL);
|
||||
require_sesskey();
|
||||
$issuer = core\oauth2\api::init_standard_issuer($type);
|
||||
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'type' => $type]);
|
||||
$mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'type' => $type,
|
||||
'showrequireconfirm' => $showrequireconfirm]);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
$mform->display();
|
||||
@@ -178,26 +197,37 @@ if ($mform && $mform->is_cancelled()) {
|
||||
$issuers = core\oauth2\api::get_all_issuers();
|
||||
echo $renderer->issuers_table($issuers);
|
||||
|
||||
// Google template.
|
||||
$docs = 'admin/tool/oauth2/issuers/google';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'google', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'google', 'sesskey' => sesskey(), 'docslink' => $docs,
|
||||
'showrequireconfirm' => true];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewgoogleissuer', 'tool_oauth2'));
|
||||
|
||||
// Microsoft template.
|
||||
$docs = 'admin/tool/oauth2/issuers/microsoft';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs,
|
||||
'showrequireconfirm' => true];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewmicrosoftissuer', 'tool_oauth2'));
|
||||
|
||||
// Facebook template.
|
||||
$docs = 'admin/tool/oauth2/issuers/facebook';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'microsoft', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'facebook', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$params = ['action' => 'edittemplate', 'type' => 'facebook', 'sesskey' => sesskey(), 'docslink' => $docs,
|
||||
'showrequireconfirm' => true];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewfacebookissuer', 'tool_oauth2'));
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['action' => 'edit']);
|
||||
|
||||
// Nextcloud template.
|
||||
$docs = 'admin/tool/oauth2/issuers/nextcloud';
|
||||
$params = ['action' => 'edittemplate', 'type' => 'nextcloud', 'sesskey' => sesskey(), 'docslink' => $docs];
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', $params);
|
||||
echo $renderer->single_button($addurl, get_string('createnewnextcloudissuer', 'tool_oauth2'));
|
||||
|
||||
// Generic issuer.
|
||||
$addurl = new moodle_url('/admin/tool/oauth2/issuers.php', ['action' => 'edit']);
|
||||
echo $renderer->single_button($addurl, get_string('createnewissuer', 'tool_oauth2'));
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ class manager {
|
||||
'title' => get_string('importtour', 'tool_usertours'),
|
||||
],
|
||||
(object) [
|
||||
'link' => new \moodle_url('https://moodle.net/tours'),
|
||||
'link' => new \moodle_url('https://archive.moodle.net/tours'),
|
||||
'linkproperties' => [
|
||||
'target' => '_blank',
|
||||
],
|
||||
|
||||
+1
-1
@@ -401,7 +401,7 @@
|
||||
$row = array ();
|
||||
$row[] = "<a href=\"../user/view.php?id=$user->id&course=$site->id\">$fullname</a>";
|
||||
foreach ($extracolumns as $field) {
|
||||
$row[] = $user->{$field};
|
||||
$row[] = s($user->{$field});
|
||||
}
|
||||
$row[] = $user->city;
|
||||
$row[] = $user->country;
|
||||
|
||||
@@ -138,7 +138,7 @@ foreach ($users as $user) {
|
||||
'<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $user->id . '&course=' . SITEID . '">' .
|
||||
$user->fullname .
|
||||
'</a>',
|
||||
$user->email,
|
||||
s($user->email),
|
||||
$user->city,
|
||||
$user->country,
|
||||
$user->lastaccess ? format_time(time() - $user->lastaccess) : $strnever
|
||||
|
||||
@@ -72,7 +72,7 @@ foreach($users as $user) {
|
||||
$table->data[] = array (
|
||||
'<a href="'.$CFG->wwwroot.'/user/view.php?id='.$user->id.'&course='.SITEID.'">'.$user->fullname.'</a>',
|
||||
// $user->username,
|
||||
$user->email,
|
||||
s($user->email),
|
||||
$user->city,
|
||||
$user->country,
|
||||
$user->lastaccess ? format_time(time() - $user->lastaccess) : $strnever
|
||||
|
||||
@@ -138,6 +138,9 @@ class analysis {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force GC to clean up the indicator instances used during the last iteration.
|
||||
$this->analyser->instantiate_indicators();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -474,6 +477,11 @@ class analysis {
|
||||
list($samplesfeatures, $newindicatorcalculations, $indicatornotnulls) = $rangeindicator->calculate($sampleids,
|
||||
$this->analyser->get_samples_origin(), $range['start'], $range['end'], $prevcalculations);
|
||||
|
||||
// Free memory ASAP.
|
||||
unset($rangeindicator);
|
||||
gc_collect_cycles();
|
||||
gc_mem_caches();
|
||||
|
||||
// Copy the features data to the dataset.
|
||||
foreach ($samplesfeatures as $analysersampleid => $features) {
|
||||
|
||||
@@ -503,7 +511,7 @@ class analysis {
|
||||
$indcalc->endtime = $range['end'];
|
||||
$indcalc->sampleid = $sampleid;
|
||||
$indcalc->sampleorigin = $this->analyser->get_samples_origin();
|
||||
$indcalc->indicator = $rangeindicator->get_id();
|
||||
$indcalc->indicator = $indicator->get_id();
|
||||
$indcalc->value = $calculatedvalue;
|
||||
$indcalc->timecreated = $timecreated;
|
||||
$newcalculations[] = $indcalc;
|
||||
|
||||
@@ -135,7 +135,7 @@ class insights_generator {
|
||||
$message->component = 'moodle';
|
||||
$message->name = 'insights';
|
||||
|
||||
$message->userfrom = \core_user::get_noreply_user();
|
||||
$message->userfrom = \core_user::get_support_user();
|
||||
$message->userto = $user;
|
||||
|
||||
$message->subject = $this->target->get_insight_subject($this->modelid, $context);
|
||||
@@ -193,18 +193,23 @@ class insights_generator {
|
||||
$insighturl = null;
|
||||
foreach ($predictionactions as $action) {
|
||||
$actionurl = $action->get_url();
|
||||
$opentoblank = false;
|
||||
if (!$actionurl->get_param('forwardurl')) {
|
||||
|
||||
$actiondoneurl = new \moodle_url('/report/insights/done.php');
|
||||
$params = ['actionvisiblename' => $action->get_text(), 'target' => '_blank'];
|
||||
$actiondoneurl = new \moodle_url('/report/insights/done.php', $params);
|
||||
// Set the forward url to the 'done' script.
|
||||
$actionurl->param('forwardurl', $actiondoneurl->out(false));
|
||||
|
||||
$opentoblank = true;
|
||||
}
|
||||
|
||||
if (empty($insighturl)) {
|
||||
// We use the primary action url as insight url so we log that the user followed the provided link.
|
||||
$insighturl = $action->get_url();
|
||||
}
|
||||
$actiondata = (object)['url' => $action->get_url()->out(false), 'text' => $action->get_text()];
|
||||
$actiondata = (object)['url' => $action->get_url()->out(false), 'text' => $action->get_text(),
|
||||
'opentoblank' => $opentoblank];
|
||||
$fullmessageplaintext .= get_string('insightinfomessageaction', 'analytics', $actiondata) . PHP_EOL;
|
||||
$messageactions[] = $actiondata;
|
||||
}
|
||||
|
||||
@@ -246,6 +246,11 @@ abstract class base {
|
||||
foreach ($this->indicators as $key => $indicator) {
|
||||
$this->indicators[$key] = call_user_func(array($indicator, 'instance'));
|
||||
}
|
||||
|
||||
// Free memory ASAP.
|
||||
gc_collect_cycles();
|
||||
gc_mem_caches();
|
||||
|
||||
return $this->indicators;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,15 @@ abstract class base extends \core_analytics\calculable {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the insights of this model be linked from reports?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function link_insights_report(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on facts (processed by machine learning backends) by default.
|
||||
*
|
||||
@@ -137,7 +146,7 @@ abstract class base extends \core_analytics\calculable {
|
||||
|
||||
$actions = array();
|
||||
|
||||
if ($includedetailsaction) {
|
||||
if ($this->link_insights_report() && $includedetailsaction) {
|
||||
|
||||
$predictionurl = new \moodle_url('/report/insights/prediction.php', array('id' => $predictionid));
|
||||
$detailstext = $this->get_view_details_text();
|
||||
|
||||
@@ -480,6 +480,9 @@ class manager {
|
||||
/**
|
||||
* Returns the models with insights at the provided context.
|
||||
*
|
||||
* Note that this method is used for display purposes. It filters out models whose insights
|
||||
* are not linked from the reports page.
|
||||
*
|
||||
* @param \context $context
|
||||
* @return \core_analytics\model[]
|
||||
*/
|
||||
@@ -490,13 +493,52 @@ class manager {
|
||||
$models = self::get_all_models(true, true, $context);
|
||||
foreach ($models as $key => $model) {
|
||||
// Check that it not only have predictions but also generates insights from them.
|
||||
if (!$model->uses_insights()) {
|
||||
if (!$model->uses_insights() || !$model->get_target()->link_insights_report()) {
|
||||
unset($models[$key]);
|
||||
}
|
||||
}
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the models that generated insights in the provided context. It can also be used to add new models to the context.
|
||||
*
|
||||
* Note that if you use this function with $newmodelid is the caller responsibility to ensure that the
|
||||
* provided model id generated insights for the provided context.
|
||||
*
|
||||
* @throws \coding_exception
|
||||
* @param \context $context
|
||||
* @param int|null $newmodelid A new model to add to the list of models with insights in the provided context.
|
||||
* @return int[]
|
||||
*/
|
||||
public static function cached_models_with_insights(\context $context, int $newmodelid = null) {
|
||||
|
||||
$cache = \cache::make('core', 'contextwithinsights');
|
||||
$modelids = $cache->get($context->id);
|
||||
if ($modelids === false) {
|
||||
// The cache is empty, but we don't know if it is empty because there are no insights
|
||||
// in this context or because cache/s have been purged, we need to be conservative and
|
||||
// "pay" 1 db read to fill up the cache.
|
||||
|
||||
$models = \core_analytics\manager::get_models_with_insights($context);
|
||||
|
||||
if ($newmodelid && empty($models[$newmodelid])) {
|
||||
throw new \coding_exception('The provided modelid ' . $newmodelid . ' did not generate any insights');
|
||||
}
|
||||
|
||||
$modelids = array_keys($models);
|
||||
$cache->set($context->id, $modelids);
|
||||
|
||||
} else if ($newmodelid && !in_array($newmodelid, $modelids)) {
|
||||
// We add the context we got as an argument to the cache.
|
||||
|
||||
array_push($modelids, $newmodelid);
|
||||
$cache->set($context->id, $modelids);
|
||||
}
|
||||
|
||||
return $modelids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a prediction
|
||||
*
|
||||
@@ -549,30 +591,32 @@ class manager {
|
||||
public static function cleanup() {
|
||||
global $DB;
|
||||
|
||||
// Clean up stuff that depends on contexts that do not exist anymore.
|
||||
$sql = "SELECT DISTINCT ap.contextid FROM {analytics_predictions} ap
|
||||
LEFT JOIN {context} ctx ON ap.contextid = ctx.id
|
||||
WHERE ctx.id IS NULL";
|
||||
$apcontexts = $DB->get_records_sql($sql);
|
||||
$DB->execute("DELETE FROM {analytics_prediction_actions} WHERE predictionid IN
|
||||
(SELECT ap.id FROM {analytics_predictions} ap
|
||||
LEFT JOIN {context} ctx ON ap.contextid = ctx.id
|
||||
WHERE ctx.id IS NULL)");
|
||||
|
||||
$sql = "SELECT DISTINCT aic.contextid FROM {analytics_indicator_calc} aic
|
||||
LEFT JOIN {context} ctx ON aic.contextid = ctx.id
|
||||
WHERE ctx.id IS NULL";
|
||||
$indcalccontexts = $DB->get_records_sql($sql);
|
||||
|
||||
$contexts = $apcontexts + $indcalccontexts;
|
||||
if ($contexts) {
|
||||
list($sql, $params) = $DB->get_in_or_equal(array_keys($contexts));
|
||||
$DB->execute("DELETE FROM {analytics_prediction_actions} WHERE predictionid IN
|
||||
(SELECT ap.id FROM {analytics_predictions} ap WHERE ap.contextid $sql)", $params);
|
||||
|
||||
$DB->delete_records_select('analytics_predictions', "contextid $sql", $params);
|
||||
$DB->delete_records_select('analytics_indicator_calc', "contextid $sql", $params);
|
||||
}
|
||||
$contextsql = "SELECT id FROM {context} ctx";
|
||||
$DB->delete_records_select('analytics_predictions', "contextid NOT IN ($contextsql)");
|
||||
$DB->delete_records_select('analytics_indicator_calc', "contextid NOT IN ($contextsql)");
|
||||
|
||||
// Clean up stuff that depends on analysable ids that do not exist anymore.
|
||||
|
||||
$models = self::get_all_models();
|
||||
foreach ($models as $model) {
|
||||
|
||||
// We first dump into memory the list of analysables we have in the database (we could probably do this with 1 single
|
||||
// query for the 3 tables, but it may be safer to do it separately).
|
||||
$predictsamplesanalysableids = $DB->get_fieldset_select('analytics_predict_samples', 'DISTINCT analysableid',
|
||||
'modelid = :modelid', ['modelid' => $model->get_id()]);
|
||||
$predictsamplesanalysableids = array_flip($predictsamplesanalysableids);
|
||||
$trainsamplesanalysableids = $DB->get_fieldset_select('analytics_train_samples', 'DISTINCT analysableid',
|
||||
'modelid = :modelid', ['modelid' => $model->get_id()]);
|
||||
$trainsamplesanalysableids = array_flip($trainsamplesanalysableids);
|
||||
$usedanalysablesanalysableids = $DB->get_fieldset_select('analytics_used_analysables', 'DISTINCT analysableid',
|
||||
'modelid = :modelid', ['modelid' => $model->get_id()]);
|
||||
$usedanalysablesanalysableids = array_flip($usedanalysablesanalysableids);
|
||||
|
||||
$analyser = $model->get_analyser(array('notimesplitting' => true));
|
||||
$analysables = $analyser->get_analysables_iterator();
|
||||
|
||||
@@ -581,17 +625,28 @@ class manager {
|
||||
if (!$analysable) {
|
||||
continue;
|
||||
}
|
||||
$analysableids[] = $analysable->get_id();
|
||||
}
|
||||
if (empty($analysableids)) {
|
||||
continue;
|
||||
unset($predictsamplesanalysableids[$analysable->get_id()]);
|
||||
unset($trainsamplesanalysableids[$analysable->get_id()]);
|
||||
unset($usedanalysablesanalysableids[$analysable->get_id()]);
|
||||
}
|
||||
|
||||
list($notinsql, $params) = $DB->get_in_or_equal($analysableids, SQL_PARAMS_NAMED, 'param', false);
|
||||
$params['modelid'] = $model->get_id();
|
||||
$param = ['modelid' => $model->get_id()];
|
||||
|
||||
$DB->delete_records_select('analytics_predict_samples', "modelid = :modelid AND analysableid $notinsql", $params);
|
||||
$DB->delete_records_select('analytics_train_samples', "modelid = :modelid AND analysableid $notinsql", $params);
|
||||
if ($predictsamplesanalysableids) {
|
||||
list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($predictsamplesanalysableids), SQL_PARAMS_NAMED);
|
||||
$DB->delete_records_select('analytics_predict_samples', "modelid = :modelid AND analysableid $idssql",
|
||||
$param + $idsparams);
|
||||
}
|
||||
if ($trainsamplesanalysableids) {
|
||||
list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($trainsamplesanalysableids), SQL_PARAMS_NAMED);
|
||||
$DB->delete_records_select('analytics_train_samples', "modelid = :modelid AND analysableid $idssql",
|
||||
$param + $idsparams);
|
||||
}
|
||||
if ($usedanalysablesanalysableids) {
|
||||
list($idssql, $idsparams) = $DB->get_in_or_equal(array_flip($usedanalysablesanalysableids), SQL_PARAMS_NAMED);
|
||||
$DB->delete_records_select('analytics_used_analysables', "modelid = :modelid AND analysableid $idssql",
|
||||
$param + $idsparams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+91
-39
@@ -933,19 +933,11 @@ class model {
|
||||
|
||||
$this->get_target()->generate_insight_notifications($this->model->id, $samplecontexts, $predictions);
|
||||
|
||||
// Update cache.
|
||||
$cache = \cache::make('core', 'contextwithinsights');
|
||||
foreach ($samplecontexts as $context) {
|
||||
$modelids = $cache->get($context->id);
|
||||
if (!$modelids) {
|
||||
// The cache is empty, but we don't know if it is empty because there are no insights
|
||||
// in this context or because cache/s have been purged, we need to be conservative and
|
||||
// "pay" 1 db read to fill up the cache.
|
||||
$models = \core_analytics\manager::get_models_with_insights($context);
|
||||
$cache->set($context->id, array_keys($models));
|
||||
} else if (!in_array($this->get_id(), $modelids)) {
|
||||
array_push($modelids, $this->get_id());
|
||||
$cache->set($context->id, $modelids);
|
||||
if ($this->get_target()->link_insights_report()) {
|
||||
|
||||
// Update cache.
|
||||
foreach ($samplecontexts as $context) {
|
||||
\core_analytics\manager::cached_models_with_insights($context, $this->get_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1000,7 +992,7 @@ class model {
|
||||
}
|
||||
|
||||
// Get all samples data.
|
||||
list($sampleids, $samplesdata) = $this->get_analyser()->get_samples($sampleids);
|
||||
list($sampleids, $samplesdata) = $this->get_samples($sampleids);
|
||||
|
||||
// Calculate the targets.
|
||||
$predictions = array();
|
||||
@@ -1314,7 +1306,7 @@ class model {
|
||||
return $prediction->sampleid;
|
||||
}, $predictions);
|
||||
|
||||
list($unused, $samplesdata) = $this->get_analyser()->get_samples($sampleids);
|
||||
list($unused, $samplesdata) = $this->get_samples($sampleids);
|
||||
|
||||
$current = 0;
|
||||
|
||||
@@ -1356,7 +1348,7 @@ class model {
|
||||
*/
|
||||
public function prediction_sample_data($predictionobj) {
|
||||
|
||||
list($unused, $samplesdata) = $this->get_analyser()->get_samples(array($predictionobj->sampleid));
|
||||
list($unused, $samplesdata) = $this->get_samples(array($predictionobj->sampleid));
|
||||
|
||||
if (empty($samplesdata[$predictionobj->sampleid])) {
|
||||
throw new \moodle_exception('errorsamplenotavailable', 'analytics');
|
||||
@@ -1668,12 +1660,8 @@ class model {
|
||||
$predictor->clear_model($this->get_unique_id(), $this->get_output_dir());
|
||||
}
|
||||
|
||||
$predictionids = $DB->get_fieldset_select('analytics_predictions', 'id', 'modelid = :modelid',
|
||||
array('modelid' => $this->get_id()));
|
||||
if ($predictionids) {
|
||||
list($sql, $params) = $DB->get_in_or_equal($predictionids);
|
||||
$DB->delete_records_select('analytics_prediction_actions', "predictionid $sql", $params);
|
||||
}
|
||||
$DB->delete_records_select('analytics_prediction_actions', "predictionid IN
|
||||
(SELECT id FROM {analytics_predictions} WHERE modelid = :modelid)", ['modelid' => $this->get_id()]);
|
||||
|
||||
$DB->delete_records('analytics_predictions', array('modelid' => $this->model->id));
|
||||
$DB->delete_records('analytics_predict_samples', array('modelid' => $this->model->id));
|
||||
@@ -1759,30 +1747,94 @@ class model {
|
||||
$contextids = array_map(function($predictionobj) {
|
||||
return $predictionobj->contextid;
|
||||
}, $predictionrecords);
|
||||
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
|
||||
|
||||
// We select the fields that will allow us to map ids to $predictionrecords. Given that we already filter by modelid
|
||||
// we have enough with sampleid and rangeindex. The reason is that the sampleid relation to a site is N - 1.
|
||||
$fields = 'id, sampleid, rangeindex';
|
||||
// Limited to 30000 records as a middle point between the ~65000 params limit in pgsql and the size limit for mysql which
|
||||
// can be increased if required up to a reasonable point.
|
||||
$chunks = array_chunk($contextids, 30000);
|
||||
foreach ($chunks as $contextidschunk) {
|
||||
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextidschunk, SQL_PARAMS_NAMED);
|
||||
|
||||
// We include the contextid and the timecreated filter to reduce the number of records in $dbpredictions. We can not
|
||||
// add as many OR conditions as records in $predictionrecords.
|
||||
$sql = "SELECT $fields
|
||||
FROM {analytics_predictions}
|
||||
WHERE modelid = :modelid
|
||||
AND contextid $contextsql
|
||||
AND timecreated >= :firsttimecreated";
|
||||
$params = $contextparams + ['modelid' => $this->model->id, 'firsttimecreated' => $firstprediction->timecreated];
|
||||
$dbpredictions = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($dbpredictions as $id => $dbprediction) {
|
||||
// The append_rangeindex implementation is the same regardless of the time splitting method in use.
|
||||
$uniqueid = $this->get_time_splitting()->append_rangeindex($dbprediction->sampleid, $dbprediction->rangeindex);
|
||||
$predictionrecords[$uniqueid]->id = $dbprediction->id;
|
||||
// We select the fields that will allow us to map ids to $predictionrecords. Given that we already filter by modelid
|
||||
// we have enough with sampleid and rangeindex. The reason is that the sampleid relation to a site is N - 1.
|
||||
$fields = 'id, sampleid, rangeindex';
|
||||
|
||||
// We include the contextid and the timecreated filter to reduce the number of records in $dbpredictions. We can not
|
||||
// add as many OR conditions as records in $predictionrecords.
|
||||
$sql = "SELECT $fields
|
||||
FROM {analytics_predictions}
|
||||
WHERE modelid = :modelid
|
||||
AND contextid $contextsql
|
||||
AND timecreated >= :firsttimecreated";
|
||||
$params = $contextparams + ['modelid' => $this->model->id, 'firsttimecreated' => $firstprediction->timecreated];
|
||||
$dbpredictions = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($dbpredictions as $id => $dbprediction) {
|
||||
// The append_rangeindex implementation is the same regardless of the time splitting method in use.
|
||||
$uniqueid = $this->get_time_splitting()->append_rangeindex($dbprediction->sampleid, $dbprediction->rangeindex);
|
||||
$predictionrecords[$uniqueid]->id = $dbprediction->id;
|
||||
}
|
||||
}
|
||||
|
||||
return $predictionrecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around analyser's get_samples to skip DB's max-number-of-params exception.
|
||||
*
|
||||
* @param array $sampleids
|
||||
* @return array
|
||||
*/
|
||||
public function get_samples(array $sampleids): array {
|
||||
|
||||
if (empty($sampleids)) {
|
||||
throw new \coding_exception('No sample ids provided');
|
||||
}
|
||||
|
||||
$chunksize = count($sampleids);
|
||||
|
||||
// We start with just 1 chunk, if it is too large for the db we split the list of sampleids in 2 and we
|
||||
// try again. We repeat this process until the chunk is small enough for the db engine to process. The
|
||||
// >= has been added in case there are other \dml_read_exceptions unrelated to the max number of params.
|
||||
while (empty($done) && $chunksize >= 1) {
|
||||
|
||||
$chunks = array_chunk($sampleids, $chunksize);
|
||||
$allsampleids = [];
|
||||
$allsamplesdata = [];
|
||||
|
||||
foreach ($chunks as $index => $chunk) {
|
||||
|
||||
try {
|
||||
list($chunksampleids, $chunksamplesdata) = $this->get_analyser()->get_samples($chunk);
|
||||
} catch (\dml_read_exception $e) {
|
||||
|
||||
// Reduce the chunksize, we use floor() so the $chunksize is always less than the previous $chunksize value.
|
||||
$chunksize = floor($chunksize / 2);
|
||||
break;
|
||||
}
|
||||
|
||||
// We can sum as these two arrays are indexed by sampleid and there are no collisions.
|
||||
$allsampleids = $allsampleids + $chunksampleids;
|
||||
$allsamplesdata = $allsamplesdata + $chunksamplesdata;
|
||||
|
||||
if ($index === count($chunks) - 1) {
|
||||
// We successfully processed all the samples in all chunks, we are done.
|
||||
$done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($done)) {
|
||||
if (!empty($e)) {
|
||||
// Throw the last exception we caught, the \dml_read_exception we have been catching is unrelated to the max number
|
||||
// of param's exception.
|
||||
throw new \dml_read_exception($e);
|
||||
} else {
|
||||
throw new \coding_exception('We should never reach this point, there is a bug in ' .
|
||||
'core_analytics\\model::get_samples\'s code');
|
||||
}
|
||||
}
|
||||
return [$allsampleids, $allsamplesdata];
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges the insights cache.
|
||||
*/
|
||||
|
||||
@@ -306,13 +306,9 @@ class provider implements
|
||||
$idssql = "SELECT ap.id FROM {analytics_predictions} ap
|
||||
WHERE ap.contextid = :contextid AND ap.modelid = :modelid";
|
||||
$idsparams = ['contextid' => $context->id, 'modelid' => $modelid];
|
||||
$predictionids = $DB->get_fieldset_sql($idssql, $idsparams);
|
||||
if ($predictionids) {
|
||||
list($predictionidssql, $params) = $DB->get_in_or_equal($predictionids, SQL_PARAMS_NAMED);
|
||||
|
||||
$DB->delete_records_select('analytics_prediction_actions', "predictionid IN ($idssql)", $idsparams);
|
||||
$DB->delete_records_select('analytics_predictions', "id $predictionidssql", $params);
|
||||
}
|
||||
$DB->delete_records_select('analytics_prediction_actions', "predictionid IN ($idssql)", $idsparams);
|
||||
$DB->delete_records_select('analytics_predictions', "contextid = :contextid AND modelid = :modelid", $idsparams);
|
||||
}
|
||||
|
||||
// We delete them all this table is just a cache and we don't know which model filled it.
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
"text": "Moodle"
|
||||
}, {
|
||||
"url": "https://en.wikipedia.org/wiki/Noodle",
|
||||
"text": "Noodle"
|
||||
"text": "Noodle",
|
||||
"opentoblank": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -65,5 +66,5 @@ body:not(.dir-ltr):not(.dir-rtl) .btn-insight {
|
||||
|
||||
<br/>
|
||||
{{#actions}}
|
||||
<a class="btn btn-default m-r-1 m-b-1 btn-insight" href="{{url}}">{{text}}</a>
|
||||
<a class="btn btn-default m-r-1 m-b-1 btn-insight" {{#opentoblank}}target="_blank" {{/opentoblank}}href="{{url}}">{{text}}</a>
|
||||
{{/actions}}
|
||||
|
||||
@@ -134,8 +134,9 @@ class analytics_manager_testcase extends advanced_testcase {
|
||||
$model->train();
|
||||
$model->predict();
|
||||
|
||||
$npredictsamples = $DB->count_records('analytics_predict_samples');
|
||||
$ntrainsamples = $DB->count_records('analytics_train_samples');
|
||||
$this->assertNotEmpty($DB->count_records('analytics_predict_samples'));
|
||||
$this->assertNotEmpty($DB->count_records('analytics_train_samples'));
|
||||
$this->assertNotEmpty($DB->count_records('analytics_used_analysables'));
|
||||
|
||||
// Now we delete an analysable, stored predict and training samples should be deleted.
|
||||
$deletedcontext = \context_course::instance($coursepredict1->id);
|
||||
@@ -145,6 +146,7 @@ class analytics_manager_testcase extends advanced_testcase {
|
||||
|
||||
$this->assertEmpty($DB->count_records('analytics_predict_samples', array('analysableid' => $coursepredict1->id)));
|
||||
$this->assertEmpty($DB->count_records('analytics_train_samples', array('analysableid' => $coursepredict1->id)));
|
||||
$this->assertEmpty($DB->count_records('analytics_used_analysables', array('analysableid' => $coursepredict1->id)));
|
||||
|
||||
set_config('enabled_stores', '', 'tool_log');
|
||||
get_log_manager(true);
|
||||
|
||||
@@ -494,6 +494,61 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
$this->assertEquals($data->name['value'], '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests model::get_samples()
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function test_get_samples() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
if (!PHPUNIT_LONGTEST) {
|
||||
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
|
||||
}
|
||||
|
||||
// 10000 should be enough to make oracle and mssql fail, if we want pgsql to fail we need around 70000
|
||||
// users, that is a few minutes just to create the users.
|
||||
$nusers = 10000;
|
||||
|
||||
$userids = [];
|
||||
for ($i = 0; $i < $nusers; $i++) {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$userids[] = $user->id;
|
||||
}
|
||||
|
||||
$upcomingactivities = null;
|
||||
foreach (\core_analytics\manager::get_all_models() as $model) {
|
||||
if (get_class($model->get_target()) === 'core_user\\analytics\\target\\upcoming_activities_due') {
|
||||
$upcomingactivities = $model;
|
||||
}
|
||||
}
|
||||
|
||||
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($userids);
|
||||
$this->assertCount($nusers, $sampleids);
|
||||
$this->assertCount($nusers, $samplesdata);
|
||||
|
||||
$subset = array_slice($userids, 0, 100);
|
||||
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
|
||||
$this->assertCount(100, $sampleids);
|
||||
$this->assertCount(100, $samplesdata);
|
||||
|
||||
$subset = array_slice($userids, 0, 2);
|
||||
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
|
||||
$this->assertCount(2, $sampleids);
|
||||
$this->assertCount(2, $samplesdata);
|
||||
|
||||
$subset = array_slice($userids, 0, 1);
|
||||
list($sampleids, $samplesdata) = $upcomingactivities->get_samples($subset);
|
||||
$this->assertCount(1, $sampleids);
|
||||
$this->assertCount(1, $samplesdata);
|
||||
|
||||
// Unexisting, so nothing returned, but still 2 arrays.
|
||||
list($sampleids, $samplesdata) = $upcomingactivities->get_samples([1231231231231231]);
|
||||
$this->assertEmpty($sampleids);
|
||||
$this->assertEmpty($samplesdata);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a model log record.
|
||||
*/
|
||||
|
||||
+11
-7
@@ -365,13 +365,17 @@ class auth_plugin_cas extends auth_plugin_ldap {
|
||||
return [];
|
||||
}
|
||||
|
||||
$iconurl = moodle_url::make_pluginfile_url(
|
||||
context_system::instance()->id,
|
||||
'auth_cas',
|
||||
'logo',
|
||||
null,
|
||||
'/',
|
||||
$this->config->auth_logo);
|
||||
if ($this->config->auth_logo) {
|
||||
$iconurl = moodle_url::make_pluginfile_url(
|
||||
context_system::instance()->id,
|
||||
'auth_cas',
|
||||
'logo',
|
||||
null,
|
||||
null,
|
||||
$this->config->auth_logo);
|
||||
} else {
|
||||
$iconurl = null;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
|
||||
@@ -402,7 +402,6 @@ class api {
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_enabled() {
|
||||
$plugininfo = \core_plugin_manager::instance()->get_plugin_info('auth_oauth2');
|
||||
return $plugininfo->is_enabled();
|
||||
return is_enabled_auth('oauth2');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,39 @@ function xmldb_auth_oauth2_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.7.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2019052001) {
|
||||
// Fetch Facebook, Google, and Microsoft issuers. We use the URL field to determine the issuer type as it's the only
|
||||
// field that contains the keyword that can somewhat let us reliably determine the issuer type.
|
||||
$likefacebook = $DB->sql_like('oe.url', ':facebook');
|
||||
$likegoogle = $DB->sql_like('oe.url', ':google');
|
||||
$likemicrosoft = $DB->sql_like('oe.url', ':microsoft');
|
||||
|
||||
$params = [
|
||||
'facebook' => '%facebook%',
|
||||
'google' => '%google%',
|
||||
'microsoft' => '%microsoft%',
|
||||
];
|
||||
|
||||
// We're querying from the oauth2_endpoint table because the base URLs of FB and Microsoft can be empty in the issuer table.
|
||||
$subsql = "
|
||||
SELECT DISTINCT oe.issuerid
|
||||
FROM {oauth2_endpoint} oe
|
||||
WHERE $likefacebook
|
||||
OR $likegoogle
|
||||
OR $likemicrosoft";
|
||||
|
||||
// Update non-Facebook/Google/Microsoft issuers and set requireconfirmation to 1.
|
||||
$updatesql = "
|
||||
UPDATE {oauth2_issuer}
|
||||
SET requireconfirmation = 1
|
||||
WHERE id NOT IN ({$subsql})";
|
||||
$DB->execute($updatesql, $params);
|
||||
|
||||
// Delete linked logins for non-Facebook/Google/Microsoft issuers. They can easily re-link their logins anyway.
|
||||
$DB->delete_records_select('auth_oauth2_linked_login', "issuerid NOT IN ($subsql)", $params);
|
||||
|
||||
upgrade_plugin_savepoint(true, 2019052001, 'auth', 'oauth2');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
{$a->admin}';
|
||||
{$a->admin}
|
||||
|
||||
If you did not do this, someone else could be trying to compromise your account.
|
||||
Please contact the site administrator immediately.';
|
||||
$string['confirmaccountemailsubject'] = '{$a}: account confirmation';
|
||||
$string['confirmationinvalid'] = 'The confirmation link is either invalid, or has expired. Please start the login process again to generate a new confirmation email.';
|
||||
$string['confirmationpending'] = 'This account is pending email confirmation.';
|
||||
@@ -60,7 +63,10 @@ then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
{$a->admin}';
|
||||
{$a->admin}
|
||||
|
||||
If you did not do this, someone else could be trying to compromise your account.
|
||||
Please contact the site administrator immediately.';
|
||||
$string['confirmlinkedloginemailsubject'] = '{$a}: linked login confirmation';
|
||||
$string['createaccountswarning'] = 'This authentication plugin allows users to create accounts on your site. You may want to enable the setting "authpreventaccountcreation" if you use this plugin.';
|
||||
$string['createnewlinkedlogin'] = 'Link a new account ({$a})';
|
||||
|
||||
@@ -140,4 +140,23 @@ class auth_oauth2_external_testcase extends advanced_testcase {
|
||||
$this->assertEquals($newuser->id, $match->get('userid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that is_enabled correctly identifies when the plugin is enabled.
|
||||
*/
|
||||
public function test_is_enabled() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
set_config('auth', 'manual,oauth2');
|
||||
$this->assertTrue(\auth_oauth2\api::is_enabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that is_enabled correctly identifies when the plugin is disabled.
|
||||
*/
|
||||
public function test_is_enabled_disabled() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
set_config('auth', 'manual');
|
||||
$this->assertFalse(\auth_oauth2\api::is_enabled());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2019052000; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2019052001; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2019051100; // Requires this Moodle version.
|
||||
$plugin->component = 'auth_oauth2'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -294,12 +294,19 @@ class auth_plugin_shibboleth extends auth_plugin_base {
|
||||
}
|
||||
|
||||
$url = new moodle_url('/auth/shibboleth/index.php');
|
||||
$iconurl = moodle_url::make_pluginfile_url(context_system::instance()->id,
|
||||
'auth_shibboleth',
|
||||
'logo',
|
||||
null,
|
||||
'/',
|
||||
$config->auth_logo);
|
||||
|
||||
if ($config->auth_logo) {
|
||||
$iconurl = moodle_url::make_pluginfile_url(
|
||||
context_system::instance()->id,
|
||||
'auth_shibboleth',
|
||||
'logo',
|
||||
null,
|
||||
null,
|
||||
$config->auth_logo);
|
||||
} else {
|
||||
$iconurl = null;
|
||||
}
|
||||
|
||||
$result[] = ['url' => $url, 'iconurl' => $iconurl, 'name' => $config->login_name];
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -42,16 +42,23 @@ class behat_auth extends behat_base {
|
||||
* Logs in the user. There should exist a user with the same value as username and password.
|
||||
*
|
||||
* @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
|
||||
* @param string $username the user to log in as.
|
||||
* @param moodle_url|null $wantsurl optional, URL to go to after logging in.
|
||||
*/
|
||||
public function i_log_in_as($username) {
|
||||
// In the mobile app the required tasks are different.
|
||||
public function i_log_in_as(string $username, moodle_url $wantsurl = null) {
|
||||
// In the mobile app the required tasks are different (does not support $wantsurl).
|
||||
if ($this->is_in_app()) {
|
||||
$this->execute('behat_app::login', [$username]);
|
||||
return;
|
||||
}
|
||||
|
||||
$loginurl = new moodle_url('/login/index.php');
|
||||
if ($wantsurl !== null) {
|
||||
$loginurl->param('wantsurl', $wantsurl->out_as_local_url());
|
||||
}
|
||||
|
||||
// Visit login page.
|
||||
$this->getSession()->visit($this->locate_path('login/index.php'));
|
||||
$this->getSession()->visit($this->locate_path($loginurl->out_as_local_url()));
|
||||
|
||||
// Enter username and password.
|
||||
$this->execute('behat_forms::i_set_the_field_to', array('Username', $this->escape($username)));
|
||||
|
||||
@@ -123,6 +123,9 @@ if (!async_helper::is_async_pending($id, 'course', 'backup')) {
|
||||
if (!($bc = backup_ui::load_controller($backupid))) {
|
||||
$bc = new backup_controller($type, $id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, $backupmode, $USER->id);
|
||||
// The backup id did not relate to a valid controller so we made a new controller.
|
||||
// Now we need to reset the backup id to match the new controller.
|
||||
$backupid = $bc->get_backupid();
|
||||
}
|
||||
|
||||
// Prepare a progress bar which can display optionally during long-running
|
||||
|
||||
@@ -127,7 +127,12 @@ class core_badges_assertion {
|
||||
$hash = $this->_data->uniquehash;
|
||||
$email = empty($this->_data->backpackemail) ? $this->_data->email : $this->_data->backpackemail;
|
||||
$assertionurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
|
||||
$classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1));
|
||||
|
||||
if ($this->_obversion == OPEN_BADGES_V2) {
|
||||
$classurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id()));
|
||||
} else {
|
||||
$classurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'action' => 1));
|
||||
}
|
||||
|
||||
// Required.
|
||||
$assertion['uid'] = $hash;
|
||||
@@ -193,7 +198,11 @@ class core_badges_assertion {
|
||||
$class['image'] = 'data:image/png;base64,' . $imagedata;
|
||||
$class['criteria'] = $this->_url->out(false); // Currently issued badge URL.
|
||||
if ($issued) {
|
||||
$issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0));
|
||||
if ($this->_obversion == OPEN_BADGES_V2) {
|
||||
$issuerurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id(), 'action' => 0));
|
||||
} else {
|
||||
$issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0));
|
||||
}
|
||||
$class['issuer'] = $issuerurl->out(false);
|
||||
}
|
||||
$this->embed_data_badge_version2($class, OPEN_BADGES_V2_TYPE_BADGE);
|
||||
@@ -329,10 +338,10 @@ class core_badges_assertion {
|
||||
$hash = $this->_data->uniquehash;
|
||||
$assertionsurl = new moodle_url('/badges/assertion.php', array('b' => $hash, 'obversion' => $this->_obversion));
|
||||
$classurl = new moodle_url(
|
||||
'/badges/assertion.php',
|
||||
array('b' => $hash, 'action' => 1, 'obversion' => $this->_obversion)
|
||||
'/badges/badge_json.php',
|
||||
array('id' => $this->get_badge_id())
|
||||
);
|
||||
$issuerurl = new moodle_url('/badges/assertion.php', array('b' => $this->_data->uniquehash, 'action' => 0,
|
||||
$issuerurl = new moodle_url('/badges/badge_json.php', array('id' => $this->get_badge_id(), 'action' => 0,
|
||||
'obversion' => $this->_obversion));
|
||||
// For assertion.
|
||||
if ($type == OPEN_BADGES_V2_TYPE_ASSERTION) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains upgrade and install functions for badges.
|
||||
*
|
||||
* @package core_badges
|
||||
* @copyright 2019 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Called on install or upgrade to create default list of backpacks a user can connect to.
|
||||
* Don't use the global defines from badgeslib because this is for install/upgrade.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function badges_install_default_backpacks() {
|
||||
global $DB;
|
||||
|
||||
$record = new stdClass();
|
||||
$record->backpackweburl = 'https://backpack.openbadges.org';
|
||||
$record->backpackapiurl = 'https://backpack.openbadges.org';
|
||||
$record->apiversion = 1;
|
||||
$record->sortorder = 0;
|
||||
$record->password = '';
|
||||
|
||||
if (!($bp = $DB->get_record('badge_external_backpack', array('backpackapiurl' => $record->backpackapiurl)))) {
|
||||
$bpid = $DB->insert_record('badge_external_backpack', $record);
|
||||
} else {
|
||||
$bpid = $bp->id;
|
||||
}
|
||||
set_config('badges_site_backpack', $bpid);
|
||||
|
||||
// All existing backpacks default to V1.
|
||||
$DB->set_field('badge_backpack', 'externalbackpackid', $bpid);
|
||||
|
||||
$record = new stdClass();
|
||||
$record->backpackapiurl = 'https://api.badgr.io/v2';
|
||||
$record->backpackweburl = 'https://badgr.io';
|
||||
$record->apiversion = 2;
|
||||
$record->sortorder = 1;
|
||||
$record->password = '';
|
||||
|
||||
if (!$DB->record_exists('badge_external_backpack', array('backpackapiurl' => $record->backpackapiurl))) {
|
||||
$DB->insert_record('badge_external_backpack', $record);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file keeps track of upgrades to the myoverview block
|
||||
*
|
||||
* @since 3.7.3
|
||||
* @package block_myoverview
|
||||
* @copyright 2019 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Upgrade code for the MyOverview block.
|
||||
*
|
||||
* @param int $oldversion
|
||||
*/
|
||||
function xmldb_block_myoverview_upgrade($oldversion) {
|
||||
global $DB;
|
||||
|
||||
if ($oldversion < 2019052001) {
|
||||
// Remove orphaned course favourites, which weren't being deleted when the course was deleted.
|
||||
$sql = 'SELECT f.id
|
||||
FROM {favourite} f
|
||||
LEFT JOIN {course} c
|
||||
ON (c.id = f.itemid)
|
||||
WHERE f.component = :component
|
||||
AND f.itemtype = :itemtype
|
||||
AND c.id IS NULL';
|
||||
$params = ['component' => 'core_course', 'itemtype' => 'courses'];
|
||||
|
||||
if ($records = $DB->get_fieldset_sql($sql, $params)) {
|
||||
$chunks = array_chunk($records, 1000);
|
||||
foreach ($chunks as $chunk) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($chunk);
|
||||
$DB->delete_records_select('favourite', "id $insql", $inparams);
|
||||
}
|
||||
}
|
||||
|
||||
upgrade_block_savepoint(true, 2019052001, 'myoverview', false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ $string['aria:displaydropdown'] = 'Display drop-down menu';
|
||||
$string['aria:favourites'] = 'Show starred courses';
|
||||
$string['aria:future'] = 'Show future courses';
|
||||
$string['aria:groupingdropdown'] = 'Grouping drop-down menu';
|
||||
$string['aria:inprogress'] = 'Show in courses in progress';
|
||||
$string['aria:inprogress'] = 'Show courses in progress';
|
||||
$string['aria:lastaccessed'] = 'Sort courses by last accessed date';
|
||||
$string['aria:list'] = 'Switch to list view';
|
||||
$string['aria:title'] = 'Sort courses by course name';
|
||||
|
||||
@@ -121,3 +121,14 @@ function block_myoverview_user_preferences() {
|
||||
|
||||
return $preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-delete course hook to cleanup any records with references to the deleted course.
|
||||
*
|
||||
* @param stdClass $course The deleted course
|
||||
*/
|
||||
function block_myoverview_pre_course_delete(\stdClass $course) {
|
||||
// Removing any starred courses which have been created for users, for this course.
|
||||
$service = \core_favourites\service_factory::get_service_for_component('core_course');
|
||||
$service->delete_favourites_by_type_and_item('courses', $course->id);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"fullname": "course 3",
|
||||
"hasprogress": true,
|
||||
"progress": 10,
|
||||
"coursecategory": "Miscellaneous"
|
||||
"coursecategory": "Miscellaneous",
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"fullname": "course 3",
|
||||
"hasprogress": true,
|
||||
"progress": 10,
|
||||
"coursecategory": "Miscellaneous"
|
||||
"coursecategory": "Miscellaneous",
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -65,6 +66,11 @@
|
||||
</span>
|
||||
{{{fullname}}}
|
||||
</a>
|
||||
{{^visible}}
|
||||
<div class="d-flex flex-wrap">
|
||||
<span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
|
||||
</div>
|
||||
{{/visible}}
|
||||
</div>
|
||||
</div>
|
||||
{{#hasprogress}}
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"summary": "This course is about assignments",
|
||||
"hasprogress": true,
|
||||
"progress": 10,
|
||||
"coursecategory": "Miscellaneous"
|
||||
"coursecategory": "Miscellaneous",
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -75,6 +76,11 @@
|
||||
</a>
|
||||
{{> block_myoverview/course-action-menu }}
|
||||
</div>
|
||||
{{^visible}}
|
||||
<div class="d-flex flex-wrap">
|
||||
<span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
|
||||
</div>
|
||||
{{/visible}}
|
||||
<div class="summary">
|
||||
<span class="sr-only">{{#str}}aria:coursesummary, block_myoverview{{/str}}</span>
|
||||
{{{summary}}}
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2019052000; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2019052001; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2019051100; // Requires this Moodle version.
|
||||
$plugin->component = 'block_myoverview'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -114,12 +114,8 @@ class behat_block_site_main_menu extends behat_base {
|
||||
protected function get_site_menu_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_site_menu_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
|
||||
$selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
|
||||
return $this->find($selectortype, $element, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,7 +56,11 @@ class behat_block_social_activities extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu contains an item.
|
||||
* Checks that the specified activity in the social activities block should have the specified editing icon.
|
||||
*
|
||||
* This includes items in the action menu for the item (does not require it to be open)
|
||||
*
|
||||
* You should be in the course page with editing mode turned on.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
|
||||
* @param string $activityname
|
||||
@@ -71,7 +75,11 @@ class behat_block_social_activities extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu contains an item.
|
||||
* Checks that the specified activity in the social activities block should not have the specified editing icon.
|
||||
*
|
||||
* This includes items in the action menu for the item (does not require it to be open)
|
||||
*
|
||||
* You should be in the course page with editing mode turned on.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should not have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
|
||||
* @param string $activityname
|
||||
@@ -103,7 +111,7 @@ class behat_block_social_activities extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the specified element inside the activity container.
|
||||
* Finds the element containing a specific activity in the social activity block.
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
* @param string $element
|
||||
@@ -114,16 +122,12 @@ class behat_block_social_activities extends behat_base {
|
||||
protected function get_social_block_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_social_block_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
|
||||
$selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
|
||||
return $this->find($selectortype, $element, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is hidden.
|
||||
* Checks that the specified activity is hidden in the social activities block.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should be hidden$/
|
||||
* @param string $activityname
|
||||
@@ -133,7 +137,7 @@ class behat_block_social_activities extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is hidden.
|
||||
* Checks that the specified activity is hidden in the social activities block.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should be available but hidden from course page$/
|
||||
* @param string $activityname
|
||||
@@ -143,7 +147,7 @@ class behat_block_social_activities extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an activity actions menu if it is not already opened.
|
||||
* Opens an activity actions menu in the social activities block if it is not already opened.
|
||||
*
|
||||
* @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu in social activities block$/
|
||||
* @throws DriverException The step is not available when Javascript is disabled
|
||||
|
||||
@@ -88,13 +88,18 @@ class block_starredcourses_external extends core_course_external {
|
||||
return ($a->timemodified > $b->timemodified) ? -1 : 1;
|
||||
});
|
||||
|
||||
$formattedcourses = array_map(function($favourite) use ($renderer) {
|
||||
$formattedcourses = array();
|
||||
foreach ($favourites as $favourite) {
|
||||
$course = get_course($favourite->itemid);
|
||||
$context = \context_course::instance($favourite->itemid);
|
||||
$canviewhiddencourses = has_capability('moodle/course:viewhiddencourses', $context);
|
||||
|
||||
$exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => true]);
|
||||
return $exporter->export($renderer);
|
||||
}, $favourites);
|
||||
if ($course->visible || $canviewhiddencourses) {
|
||||
$exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => true]);
|
||||
$formattedcourse = $exporter->export($renderer);
|
||||
$formattedcourses[] = $formattedcourse;
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedcourses;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/templates","core/paged_content_factory","core/str","core/user_date","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g){var h=86400,i={EMPTY_MESSAGE:'[data-region="empty-message"]',ROOT:'[data-region="event-list-container"]',EVENT_LIST_CONTENT:'[data-region="event-list-content"]',EVENT_LIST_LOADING_PLACEHOLDER:'[data-region="event-list-loading-placeholder"]'},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content"},k={ignoreControlWhileLoading:!0,controlPlacementBottom:!0,ariaLabels:{itemsperpagecomponents:"ariaeventlistpagelimit, block_timeline"}},l=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden"),a.find(i.EMPTY_MESSAGE).removeClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden"),a.find(i.EMPTY_MESSAGE).addClass("hidden")},n=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},o=function(a,b){var c={},d={eventsbyday:[]};return a.forEach(function(a){var d=f.getUserMidnightForTimestamp(a.timesort,b);c[d]?c[d].push(a):c[d]=[a]}),Object.keys(c).forEach(function(a){var e=c[a];d.eventsbyday.push({past:a<b,dayTimestamp:a,events:e})}),d},p=function(a,b){var d=o(a,b),e=j.EVENT_LIST_CONTENT;return c.render(e,d)},q=function(a,b,c,d,e,f){var i=a+c*h,j=void 0!=d&&a+d*h,k={starttime:i,limit:b};return e&&(k.aftereventid=e),j&&(k.endtime=j),f?(k.courseid=f,g.queryByCourse(k)):g.queryByTime(k)},r=function(a,b,c,d,e,f,g,h){for(var i=a.pageNumber,j=a.limit,k=i;!d.hasOwnProperty(k);)k--;var l=d[k],m=null;return m=e&&e.hasOwnProperty(i)?e[i]:q(c,j+1,g,h,l,f),m.then(function(a){if(!a.events.length)return b.allItemsLoaded(i),[];var c=a.events,d=c.length<=j;return d?b.allItemsLoaded(i):c.pop(),c})},s=function(c,f,g,h,i,j,l,m,n){var o={1:0},q=!1,s=a.extend({},k,n);return e.get_string("ariaeventlistpagelimit","block_timeline",a.isArray(c)?c[0].value:c).then(function(a){return s.ariaLabels.itemsperpage=a,s.ariaLabels.paginationnav=m,a}).then(function(){return d.createWithLimit(c,function(c,d){var e=[];return c.forEach(function(a){var c=a.pageNumber,h=r(a,d,g,o,f,i,j,l).then(function(a){if(a.length){q=!0;var b=a[a.length-1].id;return o[c+1]=b,p(a,g)}return a})["catch"](b.exception);e.push(h)}),a.when.apply(a,e).then(function(){h.resolve(q)})["catch"](function(){h.resolve(q)}),e},s)})},t=function(d,e,f,g,h){d=a(d);var j=a.Deferred(),k=d.find(i.EVENT_LIST_CONTENT),o=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),p=d.attr("data-course-id"),q=parseInt(d.attr("data-days-offset"),10),r=d.attr("data-days-limit"),t=parseInt(d.attr("data-midnight"),10);return n(d),m(d),o.removeClass("hidden"),void 0!=r&&(r=parseInt(r,10)),s(e,f,t,j,p,q,r,g,h).then(function(b,e){return b=a(b),b.addClass("hidden"),c.replaceNodeContents(k,b,e),j.then(function(a){return b.removeClass("hidden"),o.addClass("hidden"),a||l(d),a})["catch"](function(){return!1}),b})["catch"](b.exception)};return{init:t,rootSelector:i.ROOT}});
|
||||
define(["jquery","core/notification","core/templates","core/paged_content_factory","core/str","core/user_date","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g){var h=86400,i={EMPTY_MESSAGE:'[data-region="empty-message"]',ROOT:'[data-region="event-list-container"]',EVENT_LIST_CONTENT:'[data-region="event-list-content"]',EVENT_LIST_LOADING_PLACEHOLDER:'[data-region="event-list-loading-placeholder"]'},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content"},k={ignoreControlWhileLoading:!0,controlPlacementBottom:!0,ariaLabels:{itemsperpagecomponents:"ariaeventlistpagelimit, block_timeline"}},l=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden"),a.find(i.EMPTY_MESSAGE).removeClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden"),a.find(i.EMPTY_MESSAGE).addClass("hidden")},n=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},o=function(a,b){var c={},d={eventsbyday:[]};return a.forEach(function(a){var d=f.getUserMidnightForTimestamp(a.timesort,b);c[d]?c[d].push(a):c[d]=[a]}),Object.keys(c).forEach(function(a){var e=c[a];d.eventsbyday.push({past:a<b,dayTimestamp:a,events:e})}),d},p=function(a,b){var d=o(a,b),e=j.EVENT_LIST_CONTENT;return c.render(e,d)},q=function(a,b,c,d,e,f){var i=a+c*h,j=void 0!=d&&a+d*h,k={starttime:i,limit:b};return e&&(k.aftereventid=e),j&&(k.endtime=j),f?(k.courseid=f,g.queryByCourse(k)):g.queryByTime(k)},r=function(a,b,c,d,e,f,g,h){for(var i=a.pageNumber,j=a.limit,k=i;!d.hasOwnProperty(k);)k--;var l=d[k],m=null;return m=e&&e.hasOwnProperty(i)?e[i]:q(c,j+1,g,h,l,f),m.then(function(a){if(!a.events.length)return b.allItemsLoaded(i),[];var c=a.events.filter(function(a){return"open"!=a.eventtype&&"opensubmission"!=a.eventtype}),d=c.length<=j;return d?b.allItemsLoaded(i):c.pop(),c})},s=function(c,f,g,h,i,j,l,m,n){var o={1:0},q=!1,s=a.extend({},k,n);return e.get_string("ariaeventlistpagelimit","block_timeline",a.isArray(c)?c[0].value:c).then(function(a){return s.ariaLabels.itemsperpage=a,s.ariaLabels.paginationnav=m,a}).then(function(){return d.createWithLimit(c,function(c,d){var e=[];return c.forEach(function(a){var c=a.pageNumber,h=r(a,d,g,o,f,i,j,l).then(function(a){if(a.length){q=!0;var b=a[a.length-1].id;return o[c+1]=b,p(a,g)}return a})["catch"](b.exception);e.push(h)}),a.when.apply(a,e).then(function(){h.resolve(q)})["catch"](function(){h.resolve(q)}),e},s)})},t=function(d,e,f,g,h){d=a(d);var j=a.Deferred(),k=d.find(i.EVENT_LIST_CONTENT),o=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),p=d.attr("data-course-id"),q=parseInt(d.attr("data-days-offset"),10),r=d.attr("data-days-limit"),t=parseInt(d.attr("data-midnight"),10);return n(d),m(d),o.removeClass("hidden"),void 0!=r&&(r=parseInt(r,10)),s(e,f,t,j,p,q,r,g,h).then(function(b,e){return b=a(b),b.addClass("hidden"),c.replaceNodeContents(k,b,e),j.then(function(a){return b.removeClass("hidden"),o.addClass("hidden"),a||l(d),a})["catch"](function(){return!1}),b})["catch"](b.exception)};return{init:t,rootSelector:i.ROOT}});
|
||||
@@ -265,7 +265,10 @@ function(
|
||||
return [];
|
||||
}
|
||||
|
||||
var calendarEvents = result.events;
|
||||
var calendarEvents = result.events.filter(function(event) {
|
||||
// Do not include events that does not have a due date.
|
||||
return event.eventtype != "open" && event.eventtype != "opensubmission";
|
||||
});
|
||||
// We expect to receive limit + 1 events back from the server.
|
||||
// Any less means there are no more events to load.
|
||||
var loadedAll = calendarEvents.length <= limit;
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
}}
|
||||
<div class="border-bottom pb-2">
|
||||
{{#eventsbyday}}
|
||||
<h5 class="h6 mt-3 mb-0 {{#past}}text-danger{{/past}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedayshort, core_langconfig {{/str}} {{/userdate}}</h5>
|
||||
<h5 class="h6 mt-3 mb-0 {{#past}}text-danger{{/past}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}</h5>
|
||||
{{> block_timeline/event-list-items }}
|
||||
{{/eventsbyday}}
|
||||
</div>
|
||||
@@ -460,8 +460,7 @@ class provider implements
|
||||
$params = array_merge($inparams, ['userid' => $userid]);
|
||||
$associds = $DB->get_fieldset_sql($sql, $params);
|
||||
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($associds, SQL_PARAMS_NAMED, 'param', true);
|
||||
$DB->delete_records_select('blog_association', "id $insql", $inparams);
|
||||
$DB->delete_records_list('blog_association', 'id', $associds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -370,6 +370,37 @@ class core_blog_privacy_testcase extends provider_testcase {
|
||||
$this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test provider delete_data_for_user with a context that contains no entries
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_delete_data_for_user_empty_context() {
|
||||
global $DB;
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
|
||||
// Create a blog entry for user, associated with course.
|
||||
$entry = new blog_entry($this->create_post(['userid' => $user->id, 'courseid' => $course->id])->id);
|
||||
$entry->add_association($context->id);
|
||||
|
||||
// Generate list of contexts for user.
|
||||
$contexts = provider::get_contexts_for_userid($user->id);
|
||||
$this->assertContains($context->id, $contexts->get_contextids());
|
||||
|
||||
// Now delete the blog entry.
|
||||
$entry->delete();
|
||||
|
||||
// Try to delete user data using contexts obtained prior to entry deletion.
|
||||
$contextlist = new approved_contextlist($user, 'core_blog', $contexts->get_contextids());
|
||||
provider::delete_data_for_user($contextlist);
|
||||
|
||||
// Sanity check to ensure blog_associations is really empty.
|
||||
$this->assertEmpty($DB->get_records('blog_association', ['contextid' => $context->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core_calendar/selectors","core_calendar/events","core/str","core/templates"],function(a,b,c,d,e){var f=function(d){d.on("click",b.eventFilterItem,function(b){var c=a(b.currentTarget);g(c),b.preventDefault()}),a("body").on(c.viewUpdated,function(){var c=d.find(b.eventFilterItem);c.each(function(b,c){if(c=a(c),c.data("eventtype-hidden")){var d=i(c);h(d)}})})},g=function(a){var b=i(a);return b.hidden=!b.hidden,d.get_string("eventtype"+b.eventtype,"calendar").then(function(a){return b.name=a,b}).then(function(a){return e.render("core_calendar/event_filter_key",a)}).then(function(b,c){return e.replaceNode(a,b,c)}).then(function(){h(b)})},h=function(b){M.util.js_pending("month-mini-filterChanged"),a("body").trigger(c.filterChanged,{type:b.eventtype,hidden:b.hidden}),M.util.js_complete("month-mini-filterChanged")},i=function(a){return{eventtype:a.data("eventtype"),hidden:a.data("eventtype-hidden")}};return{init:function(b){b=a(b),f(b)}}});
|
||||
define(["jquery","core_calendar/selectors","core_calendar/events","core/str","core/templates"],function(a,b,c,d,e){var f=function(d){d.on("click",b.eventFilterItem,function(b){var c=a(b.currentTarget);g(c),b.preventDefault()}),a("body").on(c.viewUpdated,function(){var c=d.find(b.eventFilterItem);c.each(function(b,c){if(c=a(c),c.data("eventtype-hidden")){var d=i(c);h(d)}})})},g=function(a){var b=i(a);return b.hidden=!b.hidden,d.get_string("eventtype"+b.eventtype,"calendar").then(function(a){return b.name=a,b.icon=!0,b.key="i/"+b.eventtype+"event",b.component="core",b}).then(function(a){return e.render("core_calendar/event_filter_key",a)}).then(function(b,c){return e.replaceNode(a,b,c)}).then(function(){h(b)})},h=function(b){M.util.js_pending("month-mini-filterChanged"),a("body").trigger(c.filterChanged,{type:b.eventtype,hidden:b.hidden}),M.util.js_complete("month-mini-filterChanged")},i=function(a){return{eventtype:a.data("eventtype"),hidden:a.data("eventtype-hidden")}};return{init:function(b){b=a(b),f(b)}}});
|
||||
@@ -67,6 +67,9 @@ function(
|
||||
return Str.get_string('eventtype' + data.eventtype, 'calendar')
|
||||
.then(function(nameStr) {
|
||||
data.name = nameStr;
|
||||
data.icon = true;
|
||||
data.key = 'i/' + data.eventtype + 'event';
|
||||
data.component = 'core';
|
||||
|
||||
return data;
|
||||
})
|
||||
|
||||
@@ -70,11 +70,13 @@ class create_update_form_mapper implements create_update_form_mapper_interface {
|
||||
'format' => $data->format
|
||||
];
|
||||
|
||||
// We don't want to return the context because it's not a
|
||||
// form value and breaks the validation.
|
||||
// Don't return context or subscription because they're not form values and break validation.
|
||||
if (isset($data->context)) {
|
||||
unset($data->context);
|
||||
}
|
||||
if (isset($data->subscription)) {
|
||||
unset($data->subscription);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
+5
-2
@@ -2203,8 +2203,8 @@ function calendar_view_event_allowed(calendar_event $event) {
|
||||
if (has_capability('moodle/calendar:manageentries', $event->context)) {
|
||||
return true;
|
||||
}
|
||||
$mycourses = enrol_get_my_courses('id');
|
||||
return isset($mycourses[$event->courseid]);
|
||||
|
||||
return can_access_course(get_course($event->courseid));
|
||||
} else if ($event->userid) {
|
||||
if ($event->userid != $USER->id) {
|
||||
// No-one can ever see another users events.
|
||||
@@ -3668,6 +3668,9 @@ function calendar_get_filter_types() {
|
||||
return [
|
||||
'eventtype' => $type,
|
||||
'name' => get_string("eventtype{$type}", "calendar"),
|
||||
'icon' => true,
|
||||
'key' => 'i/'.$type.'event',
|
||||
'component' => 'core'
|
||||
];
|
||||
}, $types);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ $searches = [];
|
||||
$params = [];
|
||||
|
||||
$usedefaultfilters = true;
|
||||
if (!empty($courseid) && $courseid == SITEID && !empty($types['site'])) {
|
||||
|
||||
if (!empty($types['site'])) {
|
||||
$searches[] = "(eventtype = 'site')";
|
||||
$usedefaultfilters = false;
|
||||
}
|
||||
@@ -144,9 +145,14 @@ if (!empty($courseid) && !empty($types['course'])) {
|
||||
$usedefaultfilters = false;
|
||||
}
|
||||
|
||||
if (!empty($categoryid) && !empty($types['category'])) {
|
||||
$searches[] = "(eventtype = 'category' AND categoryid = :categoryid)";
|
||||
$params += ['categoryid' => $categoryid];
|
||||
if (!empty($types['category'])) {
|
||||
if (!empty($categoryid)) {
|
||||
$searches[] = "(eventtype = 'category' AND categoryid = :categoryid)";
|
||||
$params += ['categoryid' => $categoryid];
|
||||
} else {
|
||||
$searches[] = "(eventtype = 'category')";
|
||||
}
|
||||
|
||||
$usedefaultfilters = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
{{#pix}} icon, {{modulename}} {{/pix}}
|
||||
{{/modulename}}
|
||||
{{^modulename}}
|
||||
{{#pix}} i/{{eventtype}}event, core {{/pix}}
|
||||
{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}
|
||||
{{/modulename}}
|
||||
|
||||
@@ -136,12 +136,7 @@
|
||||
{{$content}}
|
||||
{{#events}}
|
||||
<div data-popover-eventtype-{{normalisedeventtype}}="1">
|
||||
{{#modulename}}
|
||||
{{#pix}} icon, {{modulename}} {{/pix}}
|
||||
{{/modulename}}
|
||||
{{^modulename}}
|
||||
{{#pix}} i/{{eventtype}}event, core {{/pix}}
|
||||
{{/modulename}}
|
||||
{{> core_calendar/event_icon}}
|
||||
{{{popupname}}}
|
||||
</div>
|
||||
{{/events}}
|
||||
|
||||
@@ -47,3 +47,30 @@ Feature: Import and edit calendar events
|
||||
And I view the calendar for "2" "2017"
|
||||
And I should not see "Event on 2-25-2017"
|
||||
And I should not see "Event on 2-20-2017"
|
||||
|
||||
Scenario: Import events using different event types.
|
||||
Given I log in as "admin"
|
||||
And I view the calendar for "1" "2016"
|
||||
And I press "Manage subscriptions"
|
||||
And I set the following fields to these values:
|
||||
| Calendar name | Test Import |
|
||||
| Import from | Calendar file (.ics) |
|
||||
| Type of event | User |
|
||||
And I upload "calendar/tests/fixtures/import.ics" file to "Calendar file (.ics)" filemanager
|
||||
And I press "Add"
|
||||
And I should see "User events"
|
||||
And I set the following fields to these values:
|
||||
| Calendar name | Test Import |
|
||||
| Import from | Calendar file (.ics) |
|
||||
| Type of event | Category |
|
||||
| Category | Miscellaneous |
|
||||
And I upload "calendar/tests/fixtures/import.ics" file to "Calendar file (.ics)" filemanager
|
||||
And I press "Add"
|
||||
And I should see "Category events"
|
||||
And I set the following fields to these values:
|
||||
| Calendar name | Test Import |
|
||||
| Import from | Calendar file (.ics) |
|
||||
| Type of event | Site |
|
||||
And I upload "calendar/tests/fixtures/import.ics" file to "Calendar file (.ics)" filemanager
|
||||
And I press "Add"
|
||||
And I should see "Site events"
|
||||
|
||||
@@ -772,4 +772,72 @@ class core_calendar_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals(array($coursegroups[$courses[0]->id][1]->id), $groupids);
|
||||
$this->assertEquals($users[1]->id, $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for calendar_view_event_allowed for course event types.
|
||||
*/
|
||||
public function test_calendar_view_event_allowed_course_event() {
|
||||
global $USER;
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
|
||||
// A student in a course.
|
||||
$student = $generator->create_user();
|
||||
// Some user not enrolled in any course.
|
||||
$someuser = $generator->create_user();
|
||||
|
||||
// A course with manual enrolments.
|
||||
$manualcourse = $generator->create_course();
|
||||
|
||||
// Enrol the student to the manual enrolment course.
|
||||
$generator->enrol_user($student->id, $manualcourse->id);
|
||||
|
||||
// A course that allows guest access.
|
||||
$guestcourse = $generator->create_course(
|
||||
(object)[
|
||||
'shortname' => 'guestcourse',
|
||||
'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
|
||||
'enrol_guest_password_0' => ''
|
||||
]);
|
||||
|
||||
$manualevent = (object)[
|
||||
'name' => 'Manual course event',
|
||||
'description' => '',
|
||||
'format' => 1,
|
||||
'categoryid' => 0,
|
||||
'courseid' => $manualcourse->id,
|
||||
'groupid' => 0,
|
||||
'userid' => $USER->id,
|
||||
'modulename' => 0,
|
||||
'instance' => 0,
|
||||
'eventtype' => 'course',
|
||||
'timestart' => time(),
|
||||
'timeduration' => 86400,
|
||||
'visible' => 1
|
||||
];
|
||||
$caleventmanual = calendar_event::create($manualevent, false);
|
||||
|
||||
// Create a course event for the course with guest access.
|
||||
$guestevent = clone $manualevent;
|
||||
$guestevent->name = 'Guest course event';
|
||||
$guestevent->courseid = $guestcourse->id;
|
||||
$caleventguest = calendar_event::create($guestevent, false);
|
||||
|
||||
// Viewing as admin.
|
||||
$this->assertTrue(calendar_view_event_allowed($caleventmanual));
|
||||
$this->assertTrue(calendar_view_event_allowed($caleventguest));
|
||||
|
||||
// Viewing as someone enrolled in a course.
|
||||
$this->setUser($student);
|
||||
$this->assertTrue(calendar_view_event_allowed($caleventmanual));
|
||||
|
||||
// Viewing as someone not enrolled in any course.
|
||||
$this->setUser($someuser);
|
||||
// Viewing as someone not enrolled in a course without guest access on.
|
||||
$this->assertFalse(calendar_view_event_allowed($caleventmanual));
|
||||
// Viewing as someone not enrolled in a course with guest access on.
|
||||
$this->assertTrue(calendar_view_event_allowed($caleventguest));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -564,7 +564,7 @@ class comment {
|
||||
c.commentarea = :commentarea AND
|
||||
c.itemid = :itemid AND
|
||||
$componentwhere
|
||||
ORDER BY c.timecreated DESC";
|
||||
ORDER BY c.timecreated DESC, c.id DESC";
|
||||
$params['contextid'] = $this->contextid;
|
||||
$params['commentarea'] = $this->commentarea;
|
||||
$params['itemid'] = $this->itemid;
|
||||
|
||||
@@ -96,17 +96,18 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase {
|
||||
|
||||
// We need to add the comments manually, the comment API uses the global OUTPUT and this is going to make the WS to fail.
|
||||
$newcmt = new stdClass;
|
||||
$timecreated = time();
|
||||
$newcmt->contextid = $context->id;
|
||||
$newcmt->commentarea = 'database_entry';
|
||||
$newcmt->itemid = $recordid;
|
||||
$newcmt->content = 'New comment';
|
||||
$newcmt->format = 0;
|
||||
$newcmt->userid = $user->id;
|
||||
$newcmt->timecreated = time();
|
||||
$newcmt->timecreated = $timecreated;
|
||||
$cmtid1 = $DB->insert_record('comments', $newcmt);
|
||||
|
||||
$newcmt->content = 'New comment 2';
|
||||
$newcmt->timecreated = time() + 1;
|
||||
$newcmt->timecreated = $timecreated;
|
||||
$cmtid2 = $DB->insert_record('comments', $newcmt);
|
||||
|
||||
$contextlevel = 'module';
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "7.5.*",
|
||||
"phpunit/dbunit": "4.0.*",
|
||||
"moodlehq/behat-extension": "3.37.2",
|
||||
"moodlehq/behat-extension": "3.37.3",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"instaclick/php-webdriver": "dev-local as 1.x-dev"
|
||||
}
|
||||
|
||||
Generated
+133
-134
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "563c5366d69fea69cea6809ac2f410cf",
|
||||
"content-hash": "ee640e103837d18775219772bd0bfb92",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
@@ -621,27 +621,28 @@
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "6.3.3",
|
||||
"version": "6.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.0",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"guzzlehttp/psr7": "^1.6.1",
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
|
||||
"psr/log": "^1.0"
|
||||
"psr/log": "^1.1"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
@@ -653,12 +654,12 @@
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
@@ -682,7 +683,7 @@
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"time": "2018-04-22T15:46:56+00:00"
|
||||
"time": "2019-10-23T15:58:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
@@ -905,8 +906,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Frank Kleine",
|
||||
"role": "Developer",
|
||||
"homepage": "http://frankkleine.de/"
|
||||
"homepage": "http://frankkleine.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Virtual file system to mock the real file system in unit tests.",
|
||||
@@ -915,16 +916,16 @@
|
||||
},
|
||||
{
|
||||
"name": "moodlehq/behat-extension",
|
||||
"version": "v3.37.2",
|
||||
"version": "v3.37.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
|
||||
"reference": "ee293554b4e75b7444576bfe0c9f6779fb1c04cb"
|
||||
"reference": "c4b69596142fa297ba049e13f04f3b158dcc94e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/ee293554b4e75b7444576bfe0c9f6779fb1c04cb",
|
||||
"reference": "ee293554b4e75b7444576bfe0c9f6779fb1c04cb",
|
||||
"url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/c4b69596142fa297ba049e13f04f3b158dcc94e6",
|
||||
"reference": "c4b69596142fa297ba049e13f04f3b158dcc94e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -949,9 +950,9 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Monllaó",
|
||||
"role": "Developer",
|
||||
"email": "david.monllao@gmail.com",
|
||||
"homepage": "http://moodle.com"
|
||||
"homepage": "http://moodle.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Moodle behat extension",
|
||||
@@ -960,7 +961,7 @@
|
||||
"Behat",
|
||||
"moodle"
|
||||
],
|
||||
"time": "2019-08-26T22:08:54+00:00"
|
||||
"time": "2019-10-28T11:41:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
@@ -1048,18 +1049,18 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Arne Blankerts",
|
||||
"role": "Developer",
|
||||
"email": "arne@blankerts.de"
|
||||
"email": "arne@blankerts.de",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Sebastian Heuer",
|
||||
"role": "Developer",
|
||||
"email": "sebastian@phpeople.de"
|
||||
"email": "sebastian@phpeople.de",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "Developer",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
|
||||
@@ -1095,18 +1096,18 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Arne Blankerts",
|
||||
"role": "Developer",
|
||||
"email": "arne@blankerts.de"
|
||||
"email": "arne@blankerts.de",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Sebastian Heuer",
|
||||
"role": "Developer",
|
||||
"email": "sebastian@phpeople.de"
|
||||
"email": "sebastian@phpeople.de",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "Developer",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Library for handling version information and constraints",
|
||||
@@ -1114,35 +1115,33 @@
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
"version": "1.0.1",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
|
||||
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
|
||||
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
|
||||
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
|
||||
"reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.6"
|
||||
"phpunit/phpunit": "~6"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpDocumentor\\Reflection\\": [
|
||||
"src"
|
||||
]
|
||||
"phpDocumentor\\Reflection\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@@ -1164,30 +1163,30 @@
|
||||
"reflection",
|
||||
"static analysis"
|
||||
],
|
||||
"time": "2017-09-11T18:02:19+00:00"
|
||||
"time": "2018-08-07T13:53:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-docblock",
|
||||
"version": "4.3.1",
|
||||
"version": "4.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
||||
"reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c"
|
||||
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
|
||||
"reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
|
||||
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0",
|
||||
"phpdocumentor/reflection-common": "^1.0.0",
|
||||
"phpdocumentor/type-resolver": "^0.4.0",
|
||||
"phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
|
||||
"phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
|
||||
"webmozart/assert": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/instantiator": "~1.0.5",
|
||||
"doctrine/instantiator": "^1.0.5",
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpunit/phpunit": "^6.4"
|
||||
},
|
||||
@@ -1215,41 +1214,40 @@
|
||||
}
|
||||
],
|
||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
||||
"time": "2019-04-30T17:48:53+00:00"
|
||||
"time": "2019-09-12T14:27:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/type-resolver",
|
||||
"version": "0.4.0",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
||||
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
|
||||
"reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
|
||||
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
|
||||
"reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5 || ^7.0",
|
||||
"phpdocumentor/reflection-common": "^1.0"
|
||||
"php": "^7.1",
|
||||
"phpdocumentor/reflection-common": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^0.9.4",
|
||||
"phpunit/phpunit": "^5.2||^4.8.24"
|
||||
"ext-tokenizer": "^7.1",
|
||||
"mockery/mockery": "~1",
|
||||
"phpunit/phpunit": "^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"phpDocumentor\\Reflection\\": [
|
||||
"src/"
|
||||
]
|
||||
"phpDocumentor\\Reflection\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@@ -1262,26 +1260,27 @@
|
||||
"email": "me@mikevanriel.com"
|
||||
}
|
||||
],
|
||||
"time": "2017-07-14T14:27:02+00:00"
|
||||
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
||||
"time": "2019-08-22T18:11:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
"version": "1.8.1",
|
||||
"version": "1.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpspec/prophecy.git",
|
||||
"reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
|
||||
"reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
|
||||
"reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203",
|
||||
"reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/instantiator": "^1.0.2",
|
||||
"php": "^5.3|^7.0",
|
||||
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
|
||||
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
|
||||
"sebastian/comparator": "^1.1|^2.0|^3.0",
|
||||
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
|
||||
},
|
||||
@@ -1325,7 +1324,7 @@
|
||||
"spy",
|
||||
"stub"
|
||||
],
|
||||
"time": "2019-06-13T12:50:23+00:00"
|
||||
"time": "2019-10-03T11:07:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/dbunit",
|
||||
@@ -1430,8 +1429,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "lead",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
|
||||
@@ -1481,8 +1480,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "lead",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
|
||||
@@ -1523,8 +1522,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "lead",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "Simple template engine.",
|
||||
@@ -1572,8 +1571,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "lead",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "Utility class for timing",
|
||||
@@ -1585,16 +1584,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-token-stream",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
|
||||
"reference": "e899757bb3df5ff6e95089132f32cd59aac2220a"
|
||||
"reference": "995192df77f63a59e47f025390d2d1fdf8f425ff"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a",
|
||||
"reference": "e899757bb3df5ff6e95089132f32cd59aac2220a",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff",
|
||||
"reference": "995192df77f63a59e47f025390d2d1fdf8f425ff",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1630,20 +1629,20 @@
|
||||
"keywords": [
|
||||
"tokenizer"
|
||||
],
|
||||
"time": "2019-07-25T05:29:42+00:00"
|
||||
"time": "2019-09-17T06:23:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "7.5.15",
|
||||
"version": "7.5.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "d79c053d972856b8b941bb233e39dc521a5093f0"
|
||||
"reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d79c053d972856b8b941bb233e39dc521a5093f0",
|
||||
"reference": "d79c053d972856b8b941bb233e39dc521a5093f0",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c92a15296e58191a4cd74cff3b34fc8e374174a",
|
||||
"reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1703,8 +1702,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "lead",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "The PHP Unit Testing framework.",
|
||||
@@ -1714,7 +1713,7 @@
|
||||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2019-08-21T07:05:16+00:00"
|
||||
"time": "2019-10-28T10:37:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1817,16 +1816,16 @@
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
|
||||
"reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
|
||||
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
|
||||
"reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1835,7 +1834,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -1860,7 +1859,7 @@
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"time": "2018-11-20T15:27:04+00:00"
|
||||
"time": "2019-10-25T08:06:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
@@ -2122,16 +2121,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/exporter",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/exporter.git",
|
||||
"reference": "06a9a5947f47b3029d76118eb5c22802e5869687"
|
||||
"reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687",
|
||||
"reference": "06a9a5947f47b3029d76118eb5c22802e5869687",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
|
||||
"reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2185,7 +2184,7 @@
|
||||
"export",
|
||||
"exporter"
|
||||
],
|
||||
"time": "2019-08-11T12:43:14+00:00"
|
||||
"time": "2019-09-14T09:02:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/global-state",
|
||||
@@ -2460,8 +2459,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sebastian Bergmann",
|
||||
"role": "lead",
|
||||
"email": "sebastian@phpunit.de"
|
||||
"email": "sebastian@phpunit.de",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
|
||||
@@ -2470,16 +2469,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/browser-kit",
|
||||
"version": "v4.3.4",
|
||||
"version": "v4.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/browser-kit.git",
|
||||
"reference": "9e5dddb637b13db82e35695a8603fe6e118cc119"
|
||||
"reference": "78b7611c45039e8ce81698be319851529bf040b1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/9e5dddb637b13db82e35695a8603fe6e118cc119",
|
||||
"reference": "9e5dddb637b13db82e35695a8603fe6e118cc119",
|
||||
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/78b7611c45039e8ce81698be319851529bf040b1",
|
||||
"reference": "78b7611c45039e8ce81698be319851529bf040b1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2525,11 +2524,11 @@
|
||||
],
|
||||
"description": "Symfony BrowserKit Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-08-26T08:26:39+00:00"
|
||||
"time": "2019-09-10T11:25:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/class-loader",
|
||||
"version": "v3.4.31",
|
||||
"version": "v3.4.32",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/class-loader.git",
|
||||
@@ -2585,16 +2584,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
"version": "v4.3.4",
|
||||
"version": "v4.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/config.git",
|
||||
"reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece"
|
||||
"reference": "0acb26407a9e1a64a275142f0ae5e36436342720"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/07d49c0f823e0bc367c6d84e35b61419188a5ece",
|
||||
"reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/0acb26407a9e1a64a275142f0ae5e36436342720",
|
||||
"reference": "0acb26407a9e1a64a275142f0ae5e36436342720",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2645,7 +2644,7 @@
|
||||
],
|
||||
"description": "Symfony Config Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-08-26T08:26:39+00:00"
|
||||
"time": "2019-09-19T15:51:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
@@ -2717,16 +2716,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.4.31",
|
||||
"version": "v3.4.32",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "e18c5c4b35e7f17513448a25d02f7af34a4bdb41"
|
||||
"reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/e18c5c4b35e7f17513448a25d02f7af34a4bdb41",
|
||||
"reference": "e18c5c4b35e7f17513448a25d02f7af34a4bdb41",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
|
||||
"reference": "f819f71ae3ba6f396b4c015bd5895de7d2f1f85f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2766,20 +2765,20 @@
|
||||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-08-20T13:31:17+00:00"
|
||||
"time": "2019-10-01T11:57:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.4.31",
|
||||
"version": "v3.4.32",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "0b600300918780001e2821db77bc28b677794486"
|
||||
"reference": "b3e7ce815d82196435d16dc458023f8fb6b36ceb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/0b600300918780001e2821db77bc28b677794486",
|
||||
"reference": "0b600300918780001e2821db77bc28b677794486",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/b3e7ce815d82196435d16dc458023f8fb6b36ceb",
|
||||
"reference": "b3e7ce815d82196435d16dc458023f8fb6b36ceb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2822,7 +2821,7 @@
|
||||
],
|
||||
"description": "Symfony Debug Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-08-20T13:31:17+00:00"
|
||||
"time": "2019-09-19T15:32:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dependency-injection",
|
||||
@@ -2896,16 +2895,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/dom-crawler",
|
||||
"version": "v4.3.4",
|
||||
"version": "v4.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dom-crawler.git",
|
||||
"reference": "cc686552948d627528c0e2e759186dff67c2610e"
|
||||
"reference": "e9f7b4d19d69b133bd638eeddcdc757723b4211f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cc686552948d627528c0e2e759186dff67c2610e",
|
||||
"reference": "cc686552948d627528c0e2e759186dff67c2610e",
|
||||
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/e9f7b4d19d69b133bd638eeddcdc757723b4211f",
|
||||
"reference": "e9f7b4d19d69b133bd638eeddcdc757723b4211f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2953,11 +2952,11 @@
|
||||
],
|
||||
"description": "Symfony DomCrawler Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2019-08-26T08:26:39+00:00"
|
||||
"time": "2019-09-28T21:25:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v3.4.31",
|
||||
"version": "v3.4.32",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
@@ -3020,7 +3019,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v4.3.4",
|
||||
"version": "v4.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
@@ -3387,8 +3386,8 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Arne Blankerts",
|
||||
"role": "Developer",
|
||||
"email": "arne@blankerts.de"
|
||||
"email": "arne@blankerts.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
||||
|
||||
@@ -834,6 +834,10 @@ $CFG->admin = 'admin';
|
||||
// Example:
|
||||
// $CFG->behat_faildump_path = '/my/path/to/save/failure/dumps';
|
||||
//
|
||||
// You can make behat pause upon failure to help you diagnose and debug problems with your tests.
|
||||
//
|
||||
// $CFG->behat_pause_on_fail = true;
|
||||
//
|
||||
// You can specify db, selenium wd_host etc. for behat parallel run by setting following variable.
|
||||
// Example:
|
||||
// $CFG->behat_parallel_run = array (
|
||||
|
||||
@@ -74,6 +74,10 @@ abstract class course_enrolments extends \core_analytics\local\target\binary {
|
||||
return get_string('coursenotyetstarted', 'course');
|
||||
}
|
||||
|
||||
if (!$fortraining && !$course->get_course_data()->visible) {
|
||||
return get_string('hiddenfromstudents');
|
||||
}
|
||||
|
||||
if (!$this->students = $course->get_students()) {
|
||||
return get_string('nocoursestudents', 'course');
|
||||
}
|
||||
|
||||
@@ -105,6 +105,9 @@ class course_summary_exporter extends \core\external\exporter {
|
||||
),
|
||||
'enddate' => array(
|
||||
'type' => PARAM_INT,
|
||||
),
|
||||
'visible' => array(
|
||||
'type' => PARAM_BOOL,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
+3
-5
@@ -166,11 +166,9 @@ if ($editform->is_cancelled()) {
|
||||
|
||||
if (!empty($CFG->creatornewroleid) and !is_viewing($context, NULL, 'moodle/role:assign') and !is_enrolled($context, NULL, 'moodle/role:assign')) {
|
||||
// Deal with course creators - enrol them internally with default role.
|
||||
if (user_can_assign($context, $CFG->creatornewroleid)) {
|
||||
enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
|
||||
} else {
|
||||
enrol_try_internal_enrol($course->id, $USER->id);
|
||||
}
|
||||
// Note: This does not respect capabilities, the creator will be assigned the default role.
|
||||
// This is an expected behaviour. See MDL-66683 for further details.
|
||||
enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
|
||||
}
|
||||
|
||||
// The URL to take them to if they chose save and display.
|
||||
|
||||
@@ -84,7 +84,14 @@ if ($deletesection) {
|
||||
}
|
||||
}
|
||||
|
||||
$editoroptions = array('context'=>$context ,'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true);
|
||||
$editoroptions = array(
|
||||
'context' => $context,
|
||||
'maxfiles' => EDITOR_UNLIMITED_FILES,
|
||||
'maxbytes' => $CFG->maxbytes,
|
||||
'trusttext' => false,
|
||||
'noclean' => true,
|
||||
'subdirs' => true
|
||||
);
|
||||
|
||||
$courseformat = course_get_format($course);
|
||||
$defaultsectionname = $courseformat->get_default_section_name($section);
|
||||
|
||||
@@ -3716,7 +3716,7 @@ class core_course_external extends external_api {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return structure for edit_module()
|
||||
* Return structure for get_module()
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @return external_description
|
||||
|
||||
@@ -50,7 +50,7 @@ if (trim($forum->intro) != '') {
|
||||
$streditsummary = get_string('editsummary');
|
||||
$introcontent .= html_writer::start_div('editinglink');
|
||||
$introcontent .= html_writer::link(
|
||||
new moodle_url('/modedit.php', [
|
||||
new moodle_url('/course/modedit.php', [
|
||||
'update' => $coursemodule->id,
|
||||
'sesskey' => sesskey(),
|
||||
]),
|
||||
|
||||
+1
-1
@@ -4590,7 +4590,7 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
|
||||
}
|
||||
|
||||
$basefields = array('id', 'idnumber', 'summary', 'summaryformat', 'startdate', 'enddate', 'category',
|
||||
'shortname', 'fullname', 'timeaccess', 'component');
|
||||
'shortname', 'fullname', 'timeaccess', 'component', 'visible');
|
||||
|
||||
$sort = trim($sort);
|
||||
if (empty($sort)) {
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
"courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
|
||||
"fullname": "course 3",
|
||||
"hasprogress": true,
|
||||
"progress": 10
|
||||
"progress": 10,
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -63,6 +64,11 @@
|
||||
</span>
|
||||
{{$coursename}}{{/coursename}}
|
||||
</a>
|
||||
{{^visible}}
|
||||
<div class="d-flex flex-wrap">
|
||||
<span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
|
||||
</div>
|
||||
{{/visible}}
|
||||
</div>
|
||||
{{$menu}}{{/menu}}
|
||||
</div>
|
||||
|
||||
@@ -1144,11 +1144,8 @@ class behat_course extends behat_base {
|
||||
protected function get_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' . $selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
$exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'");
|
||||
return $this->find($selectortype, $element, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,7 +85,6 @@ Feature: Managers can create courses
|
||||
| moodle/course:create | Allow |
|
||||
| moodle/course:manageactivities | Allow |
|
||||
| moodle/course:viewparticipants | Allow |
|
||||
| moodle/role:assign | Allow |
|
||||
And I log out
|
||||
And I log in as "kevin"
|
||||
And I am on site homepage
|
||||
@@ -96,20 +95,4 @@ Feature: Managers can create courses
|
||||
And I press "Save and display"
|
||||
And I follow "Participants"
|
||||
Then I should see "Kevin the"
|
||||
And I should not see "Teacher"
|
||||
And I log out
|
||||
Given I log in as "admin"
|
||||
And I define the allowed role assignments for the "Creator" role as:
|
||||
| Teacher | Assignable |
|
||||
And I log out
|
||||
And I log in as "kevin"
|
||||
And I am on site homepage
|
||||
And I turn editing mode on
|
||||
When I press "Add a new course"
|
||||
And I set the following fields to these values:
|
||||
| Course full name | My second course |
|
||||
| Course short name | mysecondcourse |
|
||||
And I press "Save and display"
|
||||
And I follow "Participants"
|
||||
Then I should see "Kevin the"
|
||||
And I should see "Teacher"
|
||||
|
||||
@@ -37,6 +37,14 @@ use \core_customfield\category_controller;
|
||||
*/
|
||||
class core_customfield_api_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run.
|
||||
*/
|
||||
public static function tearDownAfterClass() {
|
||||
$handler = core_course\customfield\course_handler::create();
|
||||
$handler->delete_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests set up.
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,14 @@ use \core_customfield\field_controller;
|
||||
*/
|
||||
class core_customfield_category_controller_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run.
|
||||
*/
|
||||
public static function tearDownAfterClass() {
|
||||
$handler = core_course\customfield\course_handler::create();
|
||||
$handler->delete_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests set up.
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,14 @@ use core_customfield\data_controller;
|
||||
*/
|
||||
class core_customfield_data_controller_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run.
|
||||
*/
|
||||
public static function tearDownAfterClass() {
|
||||
$handler = core_course\customfield\course_handler::create();
|
||||
$handler->delete_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests set up.
|
||||
*/
|
||||
|
||||
@@ -38,6 +38,14 @@ use \core_customfield\field_controller;
|
||||
*/
|
||||
class core_customfield_field_controller_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* This method is called after the last test of this test class is run.
|
||||
*/
|
||||
public static function tearDownAfterClass() {
|
||||
$handler = core_course\customfield\course_handler::create();
|
||||
$handler->delete_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests set up.
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user