Compare commits
570 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 73d401b462 | |||
| 7ea8581868 | |||
| 3f923b4570 | |||
| 8ef6c25a98 | |||
| 007b946948 | |||
| d1e419d86f | |||
| 2e190e4739 | |||
| e81ac514ca | |||
| 563f08ff54 | |||
| 8e70179f4d | |||
| 30ac3bcfeb | |||
| b287771272 | |||
| 4b00d8392c | |||
| abe4ff5534 | |||
| 5b641c4187 | |||
| 02dc7147c7 | |||
| 8ab8dee8c9 | |||
| b9569a887e | |||
| efee2e4c8b | |||
| 33f22e1e83 | |||
| 500fc1b8f0 | |||
| e36f1c9896 | |||
| cd6fb4322b | |||
| 1398145998 | |||
| ec6f1419dc | |||
| 473145c2c7 | |||
| 469ac6dd08 | |||
| 5ddac4b5b9 | |||
| 0c8113bf8c | |||
| f4110673d4 | |||
| fdecf60bab | |||
| bef3d096fc | |||
| bb1cfe25cf | |||
| 1150862f9d | |||
| b94ea41bbf | |||
| 6946a25e9f | |||
| 2a75ea0ec9 | |||
| dafd6f136c | |||
| b6147f7e80 | |||
| 531036848c | |||
| c0e1742985 | |||
| bedb1ffc49 | |||
| 4a6b23937a | |||
| 83d8624eec | |||
| 6e311854b7 | |||
| 12531500d3 | |||
| 73c4aa313b | |||
| b22947d3b7 | |||
| e208a8d307 | |||
| a4c878c734 | |||
| 54d672958d | |||
| 13e1ef0300 | |||
| b1899fcc4a | |||
| 31c96d5e7f | |||
| 3bb9a613c6 | |||
| 29fcc59f88 | |||
| 3cf521a427 | |||
| a27c089b40 | |||
| 4d1fbba086 | |||
| c8c229e08b | |||
| 7d78aeece1 | |||
| 4d0fa8abdc | |||
| 4c775e4618 | |||
| 9462d6f1c8 | |||
| 2aeff9cfd7 | |||
| 5b01f0099f | |||
| 6e3f19324a | |||
| 34a46486ef | |||
| 2a373db105 | |||
| 08135b0689 | |||
| 1bd6794d80 | |||
| 3e854512c5 | |||
| 8326233128 | |||
| eb29ebefb4 | |||
| 638ba35597 | |||
| 94cbb103ef | |||
| efaad61031 | |||
| 05e4efbcee | |||
| 3451667bc9 | |||
| 9efdbf36d7 | |||
| 23bb771193 | |||
| f47cc5ce3f | |||
| 9f504db2f3 | |||
| 9e5cda1f6c | |||
| 3751ec0e68 | |||
| 5a93c6c9ab | |||
| 596aa22c4e | |||
| cc72ae7f29 | |||
| 7aa7b392c0 | |||
| 11a4683cfd | |||
| b6af039890 | |||
| 69e9f234e1 | |||
| b7f0a3fd9c | |||
| 4f701a0e67 | |||
| 45b9bae80f | |||
| 5d3bf61c27 | |||
| c344124599 | |||
| 22baf5c0aa | |||
| f1125775fe | |||
| ca0f244e09 | |||
| 216ea27814 | |||
| e0ea44ae29 | |||
| 8ffac7d8e3 | |||
| 8d0c6ac9dd | |||
| 5b7563dbb2 | |||
| 8bf1ffc9ed | |||
| b3d49c46eb | |||
| 98af4c736d | |||
| 2653ea76be | |||
| 367a45a60b | |||
| 1da8aa30e0 | |||
| 2e9bc58475 | |||
| dd74898f63 | |||
| 12476a35a6 | |||
| bf35831359 | |||
| cc145e54fd | |||
| 48f96614ff | |||
| 4066dda725 | |||
| 0a91d215c6 | |||
| 6d0e1fe82d | |||
| b7218d8a10 | |||
| de2f0d85e7 | |||
| 077825f8c9 | |||
| 06d2ee0fde | |||
| 96607cf5b3 | |||
| b290b3eded | |||
| f059d1176c | |||
| 0f389d44b6 | |||
| 29dba99cdf | |||
| 7e7b71fbeb | |||
| 4f2ee1dec1 | |||
| f7a4ef3866 | |||
| 6181040347 | |||
| 8082ff4805 | |||
| 5e2338c868 | |||
| 6909b8aada | |||
| 80c1b6bcd0 | |||
| d6455734d8 | |||
| c91c235fb6 | |||
| f9ea236c4f | |||
| 61adf3b25b | |||
| e9c0b71aee | |||
| 3304b0728c | |||
| d831bfbf04 | |||
| 6bfcfd9a1f | |||
| 9cbdd41129 | |||
| c56206cebf | |||
| 71c0f9c055 | |||
| 5e3ce4ef7d | |||
| 1e21aeb97c | |||
| 403f2d450c | |||
| c8ce874199 | |||
| 13cdeaf6a4 | |||
| 0503fc7a35 | |||
| 6abbe519d6 | |||
| bdca601d03 | |||
| 4477b21322 | |||
| 1625acf3eb | |||
| 8326e3d1a9 | |||
| c4658fd9e2 | |||
| d28abb632d | |||
| 819ff2f3d7 | |||
| 2787bb5593 | |||
| 2ca6891e1f | |||
| 5381aad94a | |||
| 618b27a8ae | |||
| 1ed39a4991 | |||
| 2427689f4c | |||
| 89b9e4f805 | |||
| 4418ec9411 | |||
| 5fbbcddd0c | |||
| 25570f80dc | |||
| 0f038af8de | |||
| 5e97ea1423 | |||
| 2e790a9741 | |||
| 68b61978e8 | |||
| 0eff5b7765 | |||
| fd0b312303 | |||
| cfa3409712 | |||
| 15d1157600 | |||
| a1026db0fe | |||
| 959c70e87b | |||
| 8e2360f9c8 | |||
| 55d5362ab6 | |||
| e1c280f951 | |||
| 238585c911 | |||
| d7903d34f0 | |||
| 710b4e0a4b | |||
| 0c74fb3f82 | |||
| ab3236199d | |||
| 67d62946ff | |||
| 9415d26ea4 | |||
| c2a956a63d | |||
| d9af5335c2 | |||
| 4c0ea3f45e | |||
| d707892d0c | |||
| d70651f787 | |||
| c55d75120a | |||
| 59f0c4b48a | |||
| f4ae633fa5 | |||
| 89eded8477 | |||
| 57b5bf77b7 | |||
| dc5d047953 | |||
| 8f9f22aeb3 | |||
| 500ea66aba | |||
| 5c12024435 | |||
| 51feaeafa2 | |||
| bccd34fa0a | |||
| 77e067dc2e | |||
| 0ec20a18a4 | |||
| d402e44ab9 | |||
| a8ad45b9e8 | |||
| b60590a2fa | |||
| 25160a2a05 | |||
| 849f7f40c8 | |||
| 36ec2d7503 | |||
| e3a7647ec3 | |||
| 70c84f07d7 | |||
| 485ac4aeba | |||
| ca39432c7a | |||
| e1de53be9f | |||
| 7c05382b75 | |||
| 8d371c2174 | |||
| 457f84654e | |||
| 87648029c4 | |||
| 5fd2f276f1 | |||
| ba8f84021f | |||
| 7f84843045 | |||
| 41bb7608e8 | |||
| b9ceefadf0 | |||
| 2ff2274ffc | |||
| 82a46e6f07 | |||
| e34384ed18 | |||
| 43121fbe8b | |||
| fe033417a6 | |||
| 73c24fa02c | |||
| b1376641d4 | |||
| 546ea12179 | |||
| 55c0301d33 | |||
| cca1025271 | |||
| dda86847b1 | |||
| 12abff6943 | |||
| e247dabc3a | |||
| 22f5807e22 | |||
| 3be7a5ce18 | |||
| 34667451cf | |||
| b02583be81 | |||
| 4d53d44f2e | |||
| deb6d89f94 | |||
| 9820d27a23 | |||
| 78615db17f | |||
| 8d34e84bb9 | |||
| 5e878219fb | |||
| e8c0748998 | |||
| 8360ef9293 | |||
| d750647969 | |||
| f20c9009fa | |||
| 3be433b5d2 | |||
| 3086259abb | |||
| 2a3758ace1 | |||
| a868b63085 | |||
| ffe4e082b0 | |||
| e4a8e40912 | |||
| b51a2ccba5 | |||
| 0cdffdca4c | |||
| bdf22ec3b7 | |||
| 5d4597cda7 | |||
| 90a5693234 | |||
| aec6391bc2 | |||
| 21f27a2bd3 | |||
| 9f071b48c5 | |||
| 3717e40450 | |||
| f7e4dacd1e | |||
| c80db2680f | |||
| 438cf695bf | |||
| cc52293498 | |||
| 3eab97cc34 | |||
| e225d71259 | |||
| 2504aa33e0 | |||
| 4480118b5d | |||
| 816e6abb1b | |||
| d3f2f990dd | |||
| 427463a525 | |||
| f46413cb03 | |||
| 723d1a7475 | |||
| 070f24d006 | |||
| 911f748806 | |||
| 9ed8cafff3 | |||
| d85fed227b | |||
| 0b9e9bf89d | |||
| ed5a080034 | |||
| 419275478b | |||
| 6a06c7ca03 | |||
| 6b9cce6b98 | |||
| f8e7257327 | |||
| 37a1983740 | |||
| ebbb7f9a5f | |||
| 95209cb64a | |||
| 973c2551aa | |||
| cdfdef65b5 | |||
| 7f5479a142 | |||
| 5320dc6722 | |||
| c67c536dfe | |||
| 4c73fd62b0 | |||
| b38eb7882b | |||
| c4af3e8ec1 | |||
| d5cdc97b2f | |||
| bc2a000664 | |||
| 651f5c494b | |||
| 536faec42a | |||
| c7ff973845 | |||
| 3235bb616d | |||
| 86c240ddfa | |||
| a94b5db132 | |||
| 47ff684a89 | |||
| 50733f26f7 | |||
| d14925e818 | |||
| a5dfb4cb3b | |||
| 591a7952c2 | |||
| f4713ca7ec | |||
| ce0a325481 | |||
| e12a258678 | |||
| 2f9592c57a | |||
| 79cbc5ca4c | |||
| e6e776dc77 | |||
| f5dde8d05e | |||
| e8833c4a18 | |||
| a71f1f4336 | |||
| 6e3e7d3a01 | |||
| a37d6ab8ff | |||
| 96803a9db7 | |||
| dfff9d51bb | |||
| c9bb208ba8 | |||
| 7b5b613820 | |||
| 93551b5f58 | |||
| 6fb045026e | |||
| a6ad453ad7 | |||
| 6456fa0617 | |||
| 903be41022 | |||
| 72163b6f59 | |||
| 461981dca0 | |||
| d9b034dadb | |||
| 0937c18c6d | |||
| 2e80ca02f3 | |||
| 331aec2f27 | |||
| 6d31a91aff | |||
| df51b94410 | |||
| c96968b7e9 | |||
| e995a79f7b | |||
| f148fa37cb | |||
| 1e1ab33625 | |||
| b049fa2c8d | |||
| d6f6c78c73 | |||
| 4c0a87629f | |||
| fb5836b42e | |||
| 8fd354bc92 | |||
| 8374e2db3f | |||
| c4f137749d | |||
| 18735c9f51 | |||
| 77474f4733 | |||
| 41a15e6d73 | |||
| bcd2debd4e | |||
| 4440ca8121 | |||
| 4d67bcdb5e | |||
| af3214adae | |||
| a3f16eb4ea | |||
| 579700dcf4 | |||
| ea2ed45ec7 | |||
| c35399cc17 | |||
| f0f468ba1f | |||
| 8bb8d213db | |||
| a75f2c2f1b | |||
| 85da5ed5e2 | |||
| 82b6519647 | |||
| 799e4e2fbf | |||
| ecbcbb2a7f | |||
| 6699b20eb9 | |||
| 5af18c34e3 | |||
| fb911d3422 | |||
| 895ec79efd | |||
| 7ccf3ebdf2 | |||
| e3c4a1fc9f | |||
| 1bcf9c913f | |||
| ecf8e7eda9 | |||
| 9b6f09eeeb | |||
| 83e40aa825 | |||
| 008121e57f | |||
| 67bc6c6ebb | |||
| 649ffed15d | |||
| 57ac26edae | |||
| f05e71a443 | |||
| 0cccbe18f2 | |||
| 861b48d332 | |||
| 5719590cb1 | |||
| 571b22a693 | |||
| 603aaf66c4 | |||
| 99f29e2580 | |||
| 3ae9b14420 | |||
| 302a14d287 | |||
| 1e41439336 | |||
| b8ddf80ca6 | |||
| d2abeb3b3c | |||
| fd0bbd3201 | |||
| ed73a073a3 | |||
| 1084a0afe4 | |||
| 43f894f2ba | |||
| 29086bc3b3 | |||
| 395b1f8fe7 | |||
| 1a20233e7f | |||
| 2a26069c13 | |||
| 4e9c2677e5 | |||
| 763c1bc6fb | |||
| e5f929a854 | |||
| 78bb834249 | |||
| a4c9320fae | |||
| c59ad632a2 | |||
| ab2a6da0da | |||
| fb14b2aab7 | |||
| cf775cb602 | |||
| 0603a6db6d | |||
| 9fb8d55550 | |||
| 9f19bb9dfb | |||
| 06e372a3c8 | |||
| 2980c823f8 | |||
| c025ce8af1 | |||
| 3144a87a57 | |||
| 07579a6bba | |||
| 1210555318 | |||
| 68bc7c4658 | |||
| f0f73daeb9 | |||
| 46f6c0da7b | |||
| b69c36612d | |||
| c2587d48ba | |||
| 9400329867 | |||
| 2486b50a7b | |||
| ac3a49cd4a | |||
| 9cf38a5d24 | |||
| 1b7a0a4d1a | |||
| d403ff368e | |||
| 541122f622 | |||
| 960fef4854 | |||
| 88e90e3a6b | |||
| 16e46ae78b | |||
| db3008e2fa | |||
| 06c98804f8 | |||
| 8e85d900db | |||
| e25999cfeb | |||
| a2740b43c5 | |||
| b14eb813a0 | |||
| 7ed8b29579 | |||
| 64d7b87888 | |||
| 5944f26805 | |||
| 420daee391 | |||
| 2afeb308b6 | |||
| eacb5e71d5 | |||
| ad2ee11aae | |||
| be5dd33e4d | |||
| a16553fc55 | |||
| a8159737cf | |||
| 88c0ab547b | |||
| 9b71e8a8b0 | |||
| 6c461125c5 | |||
| e3138b870e | |||
| eb18ff3cf3 | |||
| 8c435f5f42 | |||
| 75153c10e6 | |||
| 328aa06432 | |||
| 64a588ccca | |||
| 2844f968b0 | |||
| 82e028d7bd | |||
| c14b0c9bcd | |||
| 7c815adea4 | |||
| a5fb32a623 | |||
| c0d4b3ba08 | |||
| 4a6403f068 | |||
| 1e0cf52994 | |||
| 6ad3397d69 | |||
| 4fc6d79fa3 | |||
| cbf5a395a3 | |||
| be521ca625 | |||
| 16e61e37ac | |||
| e39f009680 | |||
| be1f9cfba2 | |||
| b895b8b74f | |||
| 4777f33bbe | |||
| 2f4185c8ba | |||
| 8eaffb287a | |||
| 1f666f276e | |||
| d7072f15e9 | |||
| cf1f48b322 | |||
| 787991f1d4 | |||
| b5a94c3e85 | |||
| 2e77251c8b | |||
| 124c24eb0e | |||
| f958fafc60 | |||
| 70a11e3daa | |||
| 58a6e4bfe4 | |||
| 6e81e436bf | |||
| 85fec2b605 | |||
| b8360b3cfb | |||
| 2baaed1860 | |||
| b56666b99c | |||
| 983ae52810 | |||
| 2c6d5bf004 | |||
| 11cf3ecc56 | |||
| 3ebff5cc76 | |||
| 609203ada6 | |||
| b50e1b2450 | |||
| 6649b4f560 | |||
| 365435182e | |||
| 279ec1f986 | |||
| b52735e2b4 | |||
| ee95b9c319 | |||
| 81e8a74a7b | |||
| 5a76a51e3a | |||
| 152e59f56d | |||
| d41a75ffad | |||
| f9d389f45b | |||
| 284b7c820e | |||
| 6a09040e4d | |||
| 46bd72dc4f | |||
| 1f3fd18541 | |||
| 247b70b0de | |||
| 61319327b1 | |||
| 1237fe9140 | |||
| e489ebbb0b | |||
| 15c3d7e208 | |||
| c1aa2c28c2 | |||
| 901c95587f | |||
| b39f7194a8 | |||
| 9ebaea25f6 | |||
| a67284cb47 | |||
| f14ed54861 | |||
| e618ee9fb5 | |||
| b4ce080632 | |||
| a660a74f7a | |||
| 017578d64f | |||
| e37c1f7ed0 | |||
| 57768467dc | |||
| b562767744 | |||
| 95f2f79218 | |||
| 021143d5cc | |||
| fcd19e1faf | |||
| c9d4bb2d53 | |||
| 8b1bf65b22 | |||
| 6f60c7a6f2 | |||
| 8c6a45020e | |||
| 38f65afecf | |||
| cd05eba738 | |||
| 4b1a578e2e | |||
| 22497d9d74 | |||
| 1e90a547e9 | |||
| e2e9df59d7 | |||
| 88d5dbb63f | |||
| e293c3deb3 | |||
| f8be708257 | |||
| 40bf3feacd | |||
| 3691ff2f40 | |||
| 29d25b33a5 | |||
| 9d046294fb | |||
| 968c6115dc | |||
| 16668320a6 | |||
| dfce9c02d2 | |||
| 512044f0c9 | |||
| 1aaa130376 | |||
| ee38a8d1e4 | |||
| 55a1e8b719 | |||
| 7afbae7e1a | |||
| d6b498f6ae | |||
| 866cabf59d |
+10
-2
@@ -9,10 +9,18 @@
|
||||
#
|
||||
# See gitignore(5) man page for more details
|
||||
#
|
||||
|
||||
# Swap files (vim)
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
# Temporary files including undo
|
||||
*~
|
||||
#
|
||||
/config.php
|
||||
/lib/editor/tinymce/extra/tools/temp/
|
||||
*~
|
||||
*.swp
|
||||
/tags
|
||||
/TAGS
|
||||
/cscope.*
|
||||
|
||||
@@ -710,7 +710,7 @@ if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminemail', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminemail']);
|
||||
$options['adminemail'] = cli_input($prompt);
|
||||
$options['adminemail'] = cli_input($prompt, $options['adminemail']);
|
||||
}
|
||||
|
||||
// Validate that the address provided was an e-mail address.
|
||||
|
||||
@@ -82,11 +82,6 @@ require_once($CFG->libdir.'/installlib.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir.'/componentlib.class.php');
|
||||
|
||||
// make sure no tables are installed yet
|
||||
if ($DB->get_tables() ) {
|
||||
cli_error(get_string('clitablesexist', 'install'));
|
||||
}
|
||||
|
||||
$CFG->early_install_lang = true;
|
||||
get_string_manager(true);
|
||||
|
||||
@@ -109,12 +104,17 @@ list($options, $unrecognized) = cli_get_params(
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// We show help text even if tables are installed.
|
||||
if ($options['help']) {
|
||||
echo $help;
|
||||
die;
|
||||
}
|
||||
|
||||
// Make sure no tables are installed yet.
|
||||
if ($DB->get_tables() ) {
|
||||
cli_error(get_string('clitablesexist', 'install'));
|
||||
}
|
||||
|
||||
if (!$options['agree-license']) {
|
||||
cli_error('You have to agree to the license. --help prints out the help'); // TODO: localize
|
||||
}
|
||||
|
||||
+1
-2
@@ -823,8 +823,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
*/
|
||||
protected function registration_warning($registered) {
|
||||
|
||||
if (!$registered) {
|
||||
|
||||
if (!$registered && site_is_public()) {
|
||||
if (has_capability('moodle/site:config', context_system::instance())) {
|
||||
$registerbutton = $this->single_button(new moodle_url('/admin/registration/index.php'),
|
||||
get_string('register', 'admin'));
|
||||
|
||||
@@ -69,8 +69,11 @@ class core_role_check_users_selector extends user_selector_base {
|
||||
|
||||
if ($coursecontext and $coursecontext != SITEID) {
|
||||
$sql1 = " FROM {user} u
|
||||
JOIN {user_enrolments} ue ON (ue.userid = u.id)
|
||||
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
|
||||
JOIN (SELECT DISTINCT subu.id
|
||||
FROM {user} subu
|
||||
JOIN {user_enrolments} ue ON (ue.userid = subu.id)
|
||||
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
|
||||
) subq ON subq.id = u.id
|
||||
WHERE $wherecondition";
|
||||
$params['courseid1'] = $coursecontext->instanceid;
|
||||
|
||||
|
||||
@@ -325,7 +325,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_skip_modif_prev', new lang_string('skipmodifprev', 'backup'), new lang_string('skipmodifprevhelp', 'backup'), 0));
|
||||
|
||||
// Automated defaults section.
|
||||
$temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), ''));
|
||||
$temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), new lang_string('recyclebin_desc', 'backup')));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', new lang_string('generalusers', 'backup'), new lang_string('configgeneralusers', 'backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_role_assignments', new lang_string('generalroleassignments','backup'), new lang_string('configgeneralroleassignments','backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), 1));
|
||||
|
||||
@@ -7,7 +7,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
|
||||
// "systempaths" settingpage
|
||||
$temp = new admin_settingpage('systempaths', new lang_string('systempaths','admin'));
|
||||
|
||||
$temp->add(new admin_setting_configexecutable('pathtophp', new lang_string('pathtophp', 'admin'),
|
||||
new lang_string('configpathtophp', 'admin'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('pathtodu', 'admin'), new lang_string('configpathtodu', 'admin'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
|
||||
|
||||
@@ -58,7 +58,7 @@ class behat_admin extends behat_base {
|
||||
// We expect admin block to be visible, otherwise go to homepage.
|
||||
if (!$this->getSession()->getPage()->find('css', '.block_settings')) {
|
||||
$this->getSession()->visit($this->locate_path('/'));
|
||||
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
|
||||
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
}
|
||||
|
||||
// Search by label.
|
||||
@@ -67,7 +67,7 @@ class behat_admin extends behat_base {
|
||||
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
|
||||
$submitsearch->press();
|
||||
|
||||
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
|
||||
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
|
||||
// Admin settings does not use the same DOM structure than other moodle forms
|
||||
// but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
|
||||
|
||||
@@ -1,57 +1,74 @@
|
||||
@core @core_admin
|
||||
Feature: Enable multiple accounts to have the same email address
|
||||
In order to have multiple accounts registerd on the system with the same email address
|
||||
@core @core_admin @core_user
|
||||
Feature: Allowing multiple accounts to have the same email address
|
||||
In order to manage user accounts
|
||||
As an admin
|
||||
I need to enable multiple accounts to be registered with the same email address and verify it is applied
|
||||
I need to be able to set whether to allow multiple accounts with the same email or not
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
|
||||
Scenario: Enable registration of multiple accounts with the same email address
|
||||
Scenario Outline: Create a user with the same email as an existing user
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | 1 |
|
||||
When I navigate to "Add a new user" node in "Site administration>Users>Accounts"
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Add a new user" in site administration
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser1 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User1 |
|
||||
| First name | Test |
|
||||
| Surname | Multi1 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
| Username | s2 |
|
||||
| First name | Jane |
|
||||
| Surname | Doe |
|
||||
| Email address | <email> |
|
||||
| New password | test |
|
||||
And I press "Create user"
|
||||
And I should see "Test Multi1"
|
||||
And I press "Add a new user"
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser2 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User2 |
|
||||
| First name | Test |
|
||||
| Surname | Multi2 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
And I press "Create user"
|
||||
Then I should see "Test Multi2"
|
||||
And I should not see "This email address is already registered"
|
||||
Then I should <expect> "This email address is already registered."
|
||||
|
||||
Scenario: Disable registration of multiple accounts with the same email address
|
||||
Examples:
|
||||
| allowsameemail | email | expect |
|
||||
| 0 | s1@example.com | see |
|
||||
| 0 | S1@EXAMPLE.COM | see |
|
||||
| 1 | s1@example.com | not see |
|
||||
| 1 | S1@EXAMPLE.COM | not see |
|
||||
|
||||
Scenario Outline: Update a user with the same email as an existing user
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | 0 |
|
||||
When I navigate to "Add a new user" node in "Site administration>Users>Accounts"
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser1 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User1 |
|
||||
| First name | Test |
|
||||
| Surname | Multi1 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
And I press "Create user"
|
||||
And I should see "Test Multi1"
|
||||
And I press "Add a new user"
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser2 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User2 |
|
||||
| First name | Test |
|
||||
| Surname | Multi2 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
And I press "Create user"
|
||||
Then I should see "This email address is already registered"
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
| s2 | Jane | Doe | s2@example.com |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
And I click on "Edit" "link" in the "Jane Doe" "table_row"
|
||||
And I set the field "Email address" to "<email>"
|
||||
And I press "Update profile"
|
||||
Then I should <expect> "This email address is already registered."
|
||||
|
||||
Examples:
|
||||
| allowsameemail | email | expect |
|
||||
| 0 | s1@example.com | see |
|
||||
| 0 | S1@EXAMPLE.COM | see |
|
||||
| 1 | s1@example.com | not see |
|
||||
| 1 | S1@EXAMPLE.COM | not see |
|
||||
| 0 | S2@EXAMPLE.COM | not see |
|
||||
| 1 | S2@EXAMPLE.COM | not see |
|
||||
|
||||
Scenario Outline: Update own user profile with the same email as an existing user
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
| s2 | Jane | Doe | s2@example.com |
|
||||
When I log in as "s2"
|
||||
And I open my profile in edit mode
|
||||
And I set the field "Email address" to "<email>"
|
||||
And I press "Update profile"
|
||||
Then I should <expect> "This email address is already registered."
|
||||
|
||||
Examples:
|
||||
| allowsameemail | email | expect |
|
||||
| 0 | s1@example.com | see |
|
||||
| 0 | S1@EXAMPLE.COM | see |
|
||||
| 1 | s1@example.com | not see |
|
||||
| 1 | S1@EXAMPLE.COM | not see |
|
||||
| 0 | S2@EXAMPLE.COM | not see |
|
||||
| 1 | S2@EXAMPLE.COM | not see |
|
||||
|
||||
@@ -153,6 +153,12 @@ class invalid_analysables implements \renderable, \templatable {
|
||||
$data->analysables[] = $obj;
|
||||
}
|
||||
|
||||
if (empty($data->analysables)) {
|
||||
$data->noanalysables = [
|
||||
'message' => get_string('noinvalidanalysables', 'tool_analytics'),
|
||||
'announce' => true,
|
||||
];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ class models_list implements \renderable, \templatable {
|
||||
}
|
||||
|
||||
// Clear model.
|
||||
if (!empty($predictioncontexts)) {
|
||||
if (!empty($predictioncontexts) || $model->is_trained()) {
|
||||
$actionid = 'clear-' . $model->get_id();
|
||||
$PAGE->requires->js_call_amd('tool_analytics/model', 'confirmAction', [$actionid, 'clear']);
|
||||
$urlparams['action'] = 'clear';
|
||||
|
||||
@@ -62,7 +62,7 @@ $string['indicators'] = 'Indicators';
|
||||
$string['info'] = 'Info';
|
||||
$string['insights'] = 'Insights';
|
||||
$string['invalidanalysables'] = 'Invalid site elements';
|
||||
$string['invalidanalysablesinfo'] = 'This pages lists this site analysable elements that can not be used by this prediction model. The listed elements can not be used neither to train the prediction model nor the prediction model can get predictions for them.';
|
||||
$string['invalidanalysablesinfo'] = 'This page lists analysable elements that can\'t be used by this prediction model. The listed elements can\'t be used either to train the prediction model nor can the prediction model obtain predictions for them.';
|
||||
$string['invalidanalysablestable'] = 'Invalid site analysable elements table';
|
||||
$string['invalidprediction'] = 'Invalid to get predictions';
|
||||
$string['invalidtraining'] = 'Invalid to train the model';
|
||||
@@ -75,6 +75,7 @@ $string['nextpage'] = 'Next page';
|
||||
$string['nodatatoevaluate'] = 'There is no data to evaluate the model';
|
||||
$string['nodatatopredict'] = 'No new elements to get predictions for';
|
||||
$string['nodatatotrain'] = 'There is no new data that can be used for training';
|
||||
$string['noinvalidanalysables'] = 'This site does not contain any invalid analysable element.';
|
||||
$string['notdefined'] = 'Not yet defined';
|
||||
$string['pluginname'] = 'Analytic models';
|
||||
$string['predictionresults'] = 'Prediction results';
|
||||
|
||||
@@ -48,31 +48,38 @@
|
||||
<div class="box">
|
||||
<h3>{{#str}}modelinvalidanalysables, tool_analytics, {{modelname}}{{/str}}</h3>
|
||||
<div>{{#str}}invalidanalysablesinfo, tool_analytics{{/str}}</div>
|
||||
<div class="m-t-2 m-b-1">
|
||||
<span>{{#prev}}{{> core/single_button}}{{/prev}}</span>
|
||||
<span>{{#next}}{{> core/single_button}}{{/next}}</span>
|
||||
</div>
|
||||
<table class="generaltable fullwidth">
|
||||
<caption class="accesshide">{{#str}}invalidanalysablestable, tool_analytics{{/str}}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{#str}}name{{/str}}</th>
|
||||
<th scope="col">{{#str}}invalidtraining, tool_analytics{{/str}}</th>
|
||||
<th scope="col">{{#str}}invalidprediction, tool_analytics{{/str}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#analysables}}
|
||||
<tr>
|
||||
<td>{{{url}}}</td>
|
||||
<td>{{validtraining}}</td>
|
||||
<td>{{validprediction}}</td>
|
||||
</tr>
|
||||
{{/analysables}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="m-t-1 m-b-2">
|
||||
<span>{{#prev}}{{> core/single_button}}{{/prev}}</span>
|
||||
<span>{{#next}}{{> core/single_button}}{{/next}}</span>
|
||||
</div>
|
||||
{{#noanalysables}}
|
||||
<div class="m-t-2 m-b-1">
|
||||
{{> core/notification_info}}
|
||||
</div>
|
||||
{{/noanalysables}}
|
||||
{{^noanalysables}}
|
||||
<div class="m-t-2 m-b-1">
|
||||
<span>{{#prev}}{{> core/single_button}}{{/prev}}</span>
|
||||
<span>{{#next}}{{> core/single_button}}{{/next}}</span>
|
||||
</div>
|
||||
<table class="generaltable fullwidth">
|
||||
<caption class="accesshide">{{#str}}invalidanalysablestable, tool_analytics{{/str}}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{#str}}name{{/str}}</th>
|
||||
<th scope="col">{{#str}}invalidtraining, tool_analytics{{/str}}</th>
|
||||
<th scope="col">{{#str}}invalidprediction, tool_analytics{{/str}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#analysables}}
|
||||
<tr>
|
||||
<td>{{{url}}}</td>
|
||||
<td>{{validtraining}}</td>
|
||||
<td>{{validprediction}}</td>
|
||||
</tr>
|
||||
{{/analysables}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="m-t-1 m-b-2">
|
||||
<span>{{#prev}}{{> core/single_button}}{{/prev}}</span>
|
||||
<span>{{#next}}{{> core/single_button}}{{/next}}</span>
|
||||
</div>
|
||||
{{/noanalysables}}
|
||||
</div>
|
||||
|
||||
@@ -109,6 +109,17 @@ require_once(__DIR__ . '/../../../../lib/behat/lib.php');
|
||||
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_command.php');
|
||||
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_config_manager.php');
|
||||
|
||||
// Remove error handling overrides done in config.php. This is consistent with admin/tool/behat/cli/util_single_run.php.
|
||||
$CFG->debug = (E_ALL | E_STRICT);
|
||||
$CFG->debugdisplay = 1;
|
||||
error_reporting($CFG->debug);
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('log_errors', '1');
|
||||
|
||||
// Import the necessary libraries.
|
||||
require_once($CFG->libdir . '/setuplib.php');
|
||||
require_once($CFG->libdir . '/behat/classes/util.php');
|
||||
|
||||
// For drop option check if parallel site.
|
||||
if ((empty($options['parallel'])) && ($options['drop']) || $options['updatesteps']) {
|
||||
$options['parallel'] = behat_config_manager::get_behat_run_config_value('parallel');
|
||||
|
||||
@@ -218,7 +218,7 @@ if ($options['install']) {
|
||||
// Run behat command to get steps in feature files.
|
||||
$featurestepscmd = behat_command::get_behat_command(true);
|
||||
$featurestepscmd .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
|
||||
$featurestepscmd .= ' --dry-run --format=moodle_step_count';
|
||||
$featurestepscmd .= ' --dry-run --format=moodle_stepcount';
|
||||
$processes = cli_execute_parallel(array($featurestepscmd), __DIR__ . "/../../../../");
|
||||
$status = print_update_step_output(array_pop($processes), $behatstepfile);
|
||||
|
||||
|
||||
@@ -162,11 +162,11 @@ class tool_behat_renderer extends plugin_renderer_base {
|
||||
$html .= $this->output->heading($title);
|
||||
|
||||
// Info.
|
||||
$installurl = behat_command::DOCS_URL . '#Installation';
|
||||
$installurl = behat_command::DOCS_URL;
|
||||
$installlink = html_writer::tag('a', $installurl, array('href' => $installurl, 'target' => '_blank'));
|
||||
$writetestsurl = behat_command::DOCS_URL . '#Writing_features';
|
||||
$writetestsurl = 'https://docs.moodle.org/dev/Writing acceptance tests';
|
||||
$writetestslink = html_writer::tag('a', $writetestsurl, array('href' => $writetestsurl, 'target' => '_blank'));
|
||||
$writestepsurl = behat_command::DOCS_URL . '#Adding_steps_definitions';
|
||||
$writestepsurl = 'https://docs.moodle.org/dev/Writing_new_acceptance_test_step_definitions';
|
||||
$writestepslink = html_writer::tag('a', $writestepsurl, array('href' => $writestepsurl, 'target' => '_blank'));
|
||||
$infos = array(
|
||||
get_string('installinfo', 'tool_behat', $installlink),
|
||||
|
||||
@@ -920,9 +920,17 @@ class expired_contexts_manager {
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_course_context_expired_or_unprotected_for_user(\context $context, \stdClass $user) {
|
||||
$expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
|
||||
|
||||
$info = $expiryrecords[$context->path]->info;
|
||||
if ($context->get_course_context()->instanceid == SITEID) {
|
||||
// The is an activity in the site course (front page).
|
||||
$purpose = data_registry::get_effective_contextlevel_value(CONTEXT_SYSTEM, 'purpose');
|
||||
$info = static::get_expiry_info($purpose);
|
||||
|
||||
} else {
|
||||
$expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
|
||||
$info = $expiryrecords[$context->path]->info;
|
||||
}
|
||||
|
||||
if ($info->is_fully_expired()) {
|
||||
// This context is fully expired.
|
||||
return true;
|
||||
|
||||
@@ -277,7 +277,7 @@ $string['resubmitrequestasnew'] = 'Resubmit as new request';
|
||||
$string['resubmitrequest'] = 'Resubmit {$a->type} request for {$a->username}';
|
||||
$string['resubmittedrequest'] = 'The existing {$a->type} request for {$a->username} was cancelled and resubmitted';
|
||||
$string['resultdeleted'] = 'You recently requested to have your account and personal data in {$a} to be deleted. This process has been completed and you will no longer be able to log in.';
|
||||
$string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
|
||||
$string['resultdownloadready'] = 'Your copy of your personal data from {$a} that you recently requested is now available for download from the following link.';
|
||||
$string['reviewdata'] = 'Review data';
|
||||
$string['retentionperiod'] = 'Retention period';
|
||||
$string['retentionperiod_help'] = 'The retention period specifies the length of time that data should be kept for. When the retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
|
||||
|
||||
@@ -15,16 +15,6 @@
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
dd a.contactdpo {
|
||||
/* Reverting dd's left margin */
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.card dd a.contactdpo {
|
||||
/* Reverting dd's left margin */
|
||||
margin-left: inherit;
|
||||
}
|
||||
|
||||
[data-region="data-requests-table"] .moodle-actionmenu {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
}} data-status="{{status}}"{{!
|
||||
}}>
|
||||
<td>{{typename}}</td>
|
||||
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}</td>
|
||||
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}</td>
|
||||
<td><a href="{{requestedbyuser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{requestedbyuser.fullname}}</a></td>
|
||||
<td>
|
||||
<span class="label {{statuslabelclass}}" title="{{statuslabeltitle}}">{{statuslabel}}</span>
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
<a href="mailto:{{foruser.email}}">{{foruser.email}}</a>
|
||||
<div class="clearfix m-t-1 m-b-1">
|
||||
<span class="pull-left m-r-1">
|
||||
<strong>{{#str}}daterequesteddetail, tool_dataprivacy{{/str}}</strong> {{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}
|
||||
<strong>{{#str}}daterequesteddetail, tool_dataprivacy{{/str}}</strong>
|
||||
{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}
|
||||
</span>
|
||||
<span class="pull-left m-r-1">
|
||||
<strong>{{#str}}statusdetail, tool_dataprivacy{{/str}}</strong>
|
||||
|
||||
@@ -2222,6 +2222,40 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
|
||||
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the is_context_expired functions when supplied with the front page course.
|
||||
*/
|
||||
public function test_is_context_expired_frontpage() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
|
||||
|
||||
$frontcourse = get_site();
|
||||
$frontcoursecontext = \context_course::instance($frontcourse->id);
|
||||
|
||||
$sitenews = $this->getDataGenerator()->create_module('forum', ['course' => $frontcourse->id]);
|
||||
$cm = get_coursemodule_from_instance('forum', $sitenews->id);
|
||||
$sitenewscontext = \context_module::instance($cm->id);
|
||||
|
||||
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
|
||||
|
||||
$this->assertFalse(expired_contexts_manager::is_context_expired($frontcoursecontext));
|
||||
$this->assertFalse(expired_contexts_manager::is_context_expired($sitenewscontext));
|
||||
|
||||
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
|
||||
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
|
||||
|
||||
// Protecting the course contextlevel does not impact the front page.
|
||||
$purposes->course->set('protected', 1)->save();
|
||||
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
|
||||
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
|
||||
|
||||
// Protecting the system contextlevel affects the front page, too.
|
||||
$purposes->system->set('protected', 1)->save();
|
||||
$this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
|
||||
$this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the is_context_expired functions when supplied with an expired course.
|
||||
*/
|
||||
|
||||
@@ -137,14 +137,14 @@ abstract class restore_tool_log_logstore_subplugin extends restore_subplugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now we want to serialize it so we can store it in the DB.
|
||||
$data->other = serialize($data->other);
|
||||
} else {
|
||||
$message = "Event class not found: \"$eventclass\". Skipping log record.";
|
||||
$this->log($message, backup::LOG_DEBUG);
|
||||
return; // No such class, can not restore.
|
||||
}
|
||||
}
|
||||
// Now we want to serialize it so we can store it in the DB.
|
||||
$data->other = serialize($data->other);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -422,6 +422,7 @@ class external extends external_api {
|
||||
'comppath' => competency_path_exporter::get_read_structure(),
|
||||
))),
|
||||
'manageurl' => new external_value(PARAM_LOCALURL, 'Url to the manage competencies page.'),
|
||||
'pluginbaseurl' => new external_value(PARAM_LOCALURL, 'Url to the course competencies page.'),
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ class course_competencies_page implements renderable, templatable {
|
||||
$data->courseid = $this->courseid;
|
||||
$data->pagecontextid = $this->context->id;
|
||||
$data->competencies = array();
|
||||
$data->pluginbaseurl = (new moodle_url('/admin/tool/lp'))->out(true);
|
||||
|
||||
$gradable = is_enrolled($this->context, $USER, 'moodle/competency:coursecompetencygradable');
|
||||
if ($gradable) {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="clearfix"></div>
|
||||
{{/canmanagecoursecompetencies}}
|
||||
{{#competency}}
|
||||
<a href="{{pluginbaseurl}}user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
|
||||
<a href="{{pluginbaseurl}}/user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
|
||||
id="competency-info-link-{{competency.id}}"
|
||||
title="{{#str}}viewdetails, tool_lp{{/str}}">
|
||||
<p><strong>{{{competency.shortname}}} <em>{{competency.idnumber}}</em></strong></p>
|
||||
|
||||
@@ -116,7 +116,7 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
|
||||
$this->userrole = create_role('User role', 'lpuserrole', 'learning plan user role description');
|
||||
|
||||
assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
|
||||
assign_capability('moodle/competency:competencycompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
|
||||
assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
|
||||
assign_capability('moodle/competency:planmanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
|
||||
assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $this->creatorrole, $syscontext->id);
|
||||
assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->creatorrole, $syscontext->id);
|
||||
|
||||
@@ -274,7 +274,6 @@ class framework_importer {
|
||||
// We are calling from browser, display progress bar.
|
||||
if ($this->useprogressbar === true) {
|
||||
$this->progress = new \core\progress\display_if_slow(get_string('processingfile', 'tool_lpimportcsv'));
|
||||
$this->progress->start_html();
|
||||
} else {
|
||||
// Avoid html output on CLI scripts.
|
||||
$this->progress = new \core\progress\none();
|
||||
@@ -464,7 +463,6 @@ class framework_importer {
|
||||
$framework = api::create_framework($record);
|
||||
if ($this->useprogressbar === true) {
|
||||
$this->progress = new \core\progress\display_if_slow(get_string('importingfile', 'tool_lpimportcsv'));
|
||||
$this->progress->start_html();
|
||||
} else {
|
||||
$this->progress = new \core\progress\none();
|
||||
}
|
||||
|
||||
@@ -672,7 +672,7 @@ class manager {
|
||||
$attachment->charset = $partdata->getCharset();
|
||||
$attachment->description = $partdata->getDescription();
|
||||
$attachment->contentid = $partdata->getContentId();
|
||||
$attachment->filesize = $messagedata->getBodyPartSize($part);
|
||||
$attachment->filesize = $partdata->getBytes();
|
||||
|
||||
if (!empty($CFG->antiviruses)) {
|
||||
mtrace("--> Attempting virus scan of '{$attachment->filename}'");
|
||||
|
||||
@@ -109,7 +109,7 @@ $string['sslv3'] = 'SSLv2 (Force SSL Version 3)';
|
||||
$string['taskcleanup'] = 'Cleanup of unverified incoming email';
|
||||
$string['taskpickup'] = 'Incoming email pickup';
|
||||
$string['tls'] = 'TLS (TLS; started via protocol-level negotiation over unencrypted channel; RECOMMENDED way of initiating secure connection)';
|
||||
$string['tlsv1'] = 'TLSv1 (TLS direct version 1.x connection to server)';
|
||||
$string['tlsv1'] = 'TLSv1 (direct connection to TLS server version 1.x)';
|
||||
$string['validateaddress'] = 'Validate sender email address';
|
||||
$string['validateaddress_help'] = 'When a message is received from a user, Moodle attempts to validate the message by comparing the email address of the sender with the email address in their user profile.
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class api {
|
||||
$lang = array();
|
||||
if (!empty($addoninfo['lang'])) {
|
||||
$stringmanager = get_string_manager();
|
||||
$langs = $stringmanager->get_list_of_translations();
|
||||
$langs = $stringmanager->get_list_of_translations(true);
|
||||
foreach ($langs as $langid => $langname) {
|
||||
foreach ($addoninfo['lang'] as $stringinfo) {
|
||||
$lang[$langid][$stringinfo[0]] =
|
||||
|
||||
@@ -66,6 +66,12 @@ function tool_mobile_create_app_download_url() {
|
||||
}
|
||||
|
||||
$downloadurl = new moodle_url($mobilesettings->setuplink);
|
||||
|
||||
// Do not update the URL if it is a custom one (we may break it completely).
|
||||
if ($mobilesettings->setuplink != 'https://download.moodle.org/mobile') {
|
||||
return $downloadurl;
|
||||
}
|
||||
|
||||
$downloadurl->param('version', $CFG->version);
|
||||
$downloadurl->param('lang', current_language());
|
||||
|
||||
|
||||
@@ -64,11 +64,11 @@ $string['managerules'] = 'Event monitoring rules';
|
||||
$string['messageprovider:notification'] = 'Notifications of rule subscriptions';
|
||||
$string['messagetemplate'] = 'Notification message';
|
||||
$string['messagetemplate_help'] = 'A notification message is sent to subscribers once the notification threshold has been reached. It can include any or all of the following placeholders:
|
||||
<br /><br />
|
||||
* Link to the location of the event {link}<br />
|
||||
* Link to the area monitored {modulelink}<br />
|
||||
* Rule name {rulename}<br />
|
||||
* Description {description}<br />
|
||||
|
||||
* Link to the location of the event {link}
|
||||
* Link to the area monitored {modulelink}
|
||||
* Rule name {rulename}
|
||||
* Description {description}
|
||||
* Event {eventname}';
|
||||
$string['messagetemplate_link'] = 'admin/tool/monitor/managerules';
|
||||
$string['moduleinstance'] = 'Instance';
|
||||
|
||||
@@ -232,6 +232,10 @@ class page_agreedocs implements renderable, templatable {
|
||||
];
|
||||
redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
|
||||
}
|
||||
} else {
|
||||
// Update the policyagreed for the user to avoid infinite loop because there are no policies to-be-accepted.
|
||||
api::update_policyagreed($userid);
|
||||
$this->redirect_to_previous_url();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
|
||||
And I press "Next"
|
||||
And I set the field "I agree to the This site policy" to "1"
|
||||
And I press "Next"
|
||||
And I should not see "Next"
|
||||
And I should see "Course overview"
|
||||
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
|
||||
And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
|
||||
And I click on "Consent not given" "link" in the "User One" "table_row"
|
||||
@@ -292,3 +292,23 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
|
||||
And I should see "2 of 2" in the "User One" "table_row"
|
||||
And "Consent given on behalf of user" "icon" should exist in the "User Two" "table_row"
|
||||
And I should see "2 of 2" in the "User Two" "table_row"
|
||||
|
||||
Scenario: View acceptances made by users on their own after inactivating a policy
|
||||
Given I log in as "user1"
|
||||
And I should see "This site policy"
|
||||
And I should not see "Course overview"
|
||||
And I press "Next"
|
||||
And I set the field "I agree to the This site policy" to "1"
|
||||
And I press "Next"
|
||||
And I should see "Course overview"
|
||||
And I log out
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
|
||||
And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
|
||||
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
|
||||
And I press "Continue"
|
||||
And I click on "Set status to \"Inactive\"" "link" in the "This privacy policy" "table_row"
|
||||
And I press "Continue"
|
||||
And I log out
|
||||
When I log in as "user1"
|
||||
Then I should see "Course overview"
|
||||
|
||||
@@ -108,6 +108,16 @@ class category_bin extends base_bin {
|
||||
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
|
||||
// As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
|
||||
// setting (storing backups @ real location. For recycle bin we want to ensure that
|
||||
// backup files are always stored in Moodle file area. In order to achieve that, we
|
||||
// hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
|
||||
// operations. See MDL-65218 for more information.
|
||||
// This hack will be removed once recycle bin switches to use its own backup mode, with
|
||||
// own preferences and 100% appart from MODLE_AUTOMATED.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
$CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
|
||||
|
||||
// Backup the course.
|
||||
$user = get_admin();
|
||||
$controller = new \backup_controller(
|
||||
@@ -115,11 +125,15 @@ class category_bin extends base_bin {
|
||||
$course->id,
|
||||
\backup::FORMAT_MOODLE,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id
|
||||
);
|
||||
$controller->execute_plan();
|
||||
|
||||
// We don't need the forced setting anymore, hence unsetting it.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
unset($CFG->forced_plugin_settings['backup']);
|
||||
|
||||
// Grab the result.
|
||||
$result = $controller->get_results();
|
||||
if (!isset($result['backup_destination'])) {
|
||||
@@ -233,7 +247,7 @@ class category_bin extends base_bin {
|
||||
$tempdir,
|
||||
$course->id,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id,
|
||||
\backup::TARGET_NEW_COURSE
|
||||
);
|
||||
|
||||
@@ -112,6 +112,16 @@ class course_bin extends base_bin {
|
||||
return;
|
||||
}
|
||||
|
||||
// As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
|
||||
// setting (storing backups @ real location. For recycle bin we want to ensure that
|
||||
// backup files are always stored in Moodle file area. In order to achieve that, we
|
||||
// hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
|
||||
// operations. See MDL-65218 for more information.
|
||||
// This hack will be removed once recycle bin switches to use its own backup mode, with
|
||||
// own preferences and 100% appart from MODLE_AUTOMATED.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
$CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
|
||||
|
||||
// Backup the activity.
|
||||
$user = get_admin();
|
||||
$controller = new \backup_controller(
|
||||
@@ -119,11 +129,15 @@ class course_bin extends base_bin {
|
||||
$cm->id,
|
||||
\backup::FORMAT_MOODLE,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id
|
||||
);
|
||||
$controller->execute_plan();
|
||||
|
||||
// We don't need the forced setting anymore, hence unsetting it.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
unset($CFG->forced_plugin_settings['backup']);
|
||||
|
||||
// Grab the result.
|
||||
$result = $controller->get_results();
|
||||
if (!isset($result['backup_destination'])) {
|
||||
@@ -224,7 +238,7 @@ class course_bin extends base_bin {
|
||||
$tempdir,
|
||||
$this->_courseid,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id,
|
||||
\backup::TARGET_EXISTING_ADDING
|
||||
);
|
||||
|
||||
@@ -176,4 +176,116 @@ class tool_recyclebin_category_bin_tests extends advanced_testcase {
|
||||
$course = reset($courses);
|
||||
$this->assertEquals('Test course 2', $course->fullname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for test_course_restore_with_userdata() and test_course_restore_without_userdata()
|
||||
*
|
||||
* Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for
|
||||
* direct usage with set_config()
|
||||
*/
|
||||
public function recycle_bin_settings_provider() {
|
||||
return [
|
||||
'backup/backup_auto_storage moodle' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage external' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage mixed' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is restored when course is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_course_restore_with_userdata($settings) {
|
||||
global $DB;
|
||||
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
// We want user data to be included for this test.
|
||||
set_config('backup_auto_users', true, 'backup');
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
|
||||
// Delete course.
|
||||
delete_course($this->course, false);
|
||||
$this->assertFalse($DB->record_exists('course', ['id' => $this->course->id]));
|
||||
|
||||
// Verify there is now a backup @ cat recycle bin file area.
|
||||
$recyclebin = new \tool_recyclebin\category_bin($this->course->category);
|
||||
$this->assertEquals(1, count($recyclebin->get_items()));
|
||||
|
||||
// Restore the recycle bin item.
|
||||
$recyclebin->restore_item(current($recyclebin->get_items()));
|
||||
|
||||
// Get the new course.
|
||||
$newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST);
|
||||
|
||||
// Check that it was removed from the recycle bin.
|
||||
$this->assertEquals(0, count($recyclebin->get_items()));
|
||||
|
||||
// Verify that student DOES continue enrolled.
|
||||
$this->assertTrue(is_enrolled(context_course::instance($newcourse->id), $student->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is not restored when course is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_course_restore_without_userdata($settings) {
|
||||
global $DB;
|
||||
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
// We want user data to be included for this test.
|
||||
set_config('backup_auto_users', false, 'backup');
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
|
||||
// Delete course.
|
||||
delete_course($this->course, false);
|
||||
$this->assertFalse($DB->record_exists('course', ['id' => $this->course->id]));
|
||||
|
||||
// Verify there is now a backup @ cat recycle bin file area.
|
||||
$recyclebin = new \tool_recyclebin\category_bin($this->course->category);
|
||||
$this->assertEquals(1, count($recyclebin->get_items()));
|
||||
|
||||
// Restore the recycle bin item.
|
||||
$recyclebin->restore_item(current($recyclebin->get_items()));
|
||||
|
||||
// Get the new course.
|
||||
$newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST);
|
||||
|
||||
// Check that it was removed from the recycle bin.
|
||||
$this->assertEquals(0, count($recyclebin->get_items()));
|
||||
|
||||
// Verify that student DOES NOT continue enrolled.
|
||||
$this->assertFalse(is_enrolled(context_course::instance($newcourse->id), $student->id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
|
||||
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
$this->quiz = $this->getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(array(
|
||||
'course' => $this->course->id
|
||||
'course' => $this->course->id, 'grade' => 100.0, 'sumgrades' => 1
|
||||
));
|
||||
}
|
||||
|
||||
@@ -173,4 +173,147 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
|
||||
$deletedbook = reset($items);
|
||||
$this->assertEquals($book->name, $deletedbook->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for test_coursemodule_restore_with_userdata() and test_coursemodule_restore_without_userdata()
|
||||
*
|
||||
* Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for
|
||||
* direct usage with set_config()
|
||||
*/
|
||||
public function recycle_bin_settings_provider() {
|
||||
return [
|
||||
'backup/backup_auto_storage moodle' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage external' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage mixed' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is restored when module is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_coursemodule_restore_with_userdata($settings) {
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
$this->setUser($student);
|
||||
|
||||
set_config('backup_auto_users', true, 'backup');
|
||||
$this->create_quiz_attempt($this->quiz, $student);
|
||||
|
||||
// Delete quiz.
|
||||
$cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
|
||||
course_delete_module($cm->id);
|
||||
phpunit_util::run_all_adhoc_tasks();
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(0, count($quizzes));
|
||||
|
||||
// Restore quiz.
|
||||
$recyclebin = new \tool_recyclebin\course_bin($this->course->id);
|
||||
foreach ($recyclebin->get_items() as $item) {
|
||||
$recyclebin->restore_item($item);
|
||||
}
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(1, count($quizzes));
|
||||
$cm = array_pop($quizzes);
|
||||
|
||||
// Check if user quiz attempt data is restored.
|
||||
$attempts = quiz_get_user_attempts($cm->instance, $student->id);
|
||||
$this->assertEquals(1, count($attempts));
|
||||
$attempt = array_pop($attempts);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$this->assertEquals($student->id, $attemptobj->get_userid());
|
||||
$this->assertEquals(true, $attemptobj->is_finished());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is not restored when module is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_coursemodule_restore_without_userdata($settings) {
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
$this->setUser($student);
|
||||
|
||||
set_config('backup_auto_users', false, 'backup');
|
||||
$this->create_quiz_attempt($this->quiz, $student);
|
||||
|
||||
// Delete quiz.
|
||||
$cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
|
||||
course_delete_module($cm->id);
|
||||
phpunit_util::run_all_adhoc_tasks();
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(0, count($quizzes));
|
||||
|
||||
// Restore quiz.
|
||||
$recyclebin = new \tool_recyclebin\course_bin($this->course->id);
|
||||
foreach ($recyclebin->get_items() as $item) {
|
||||
$recyclebin->restore_item($item);
|
||||
}
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(1, count($quizzes));
|
||||
$cm = array_pop($quizzes);
|
||||
|
||||
// Check if user quiz attempt data is restored.
|
||||
$attempts = quiz_get_user_attempts($cm->instance, $student->id);
|
||||
$this->assertEquals(0, count($attempts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a question to quiz and create a quiz attempt.
|
||||
* @param \stdClass $quiz Quiz
|
||||
* @param \stdClass $student User
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
private function create_quiz_attempt($quiz, $student) {
|
||||
// Add Question.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
$numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
|
||||
quiz_add_quiz_question($numq->id, $quiz);
|
||||
|
||||
// Create quiz attempt.
|
||||
$quizobj = quiz::create($quiz->id, $student->id);
|
||||
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
|
||||
$timenow = time();
|
||||
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id);
|
||||
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
|
||||
quiz_attempt_save_started($quizobj, $quba, $attempt);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$tosubmit = array(1 => array('answer' => '0'));
|
||||
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$attemptobj->process_finish($timenow, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['cannotfit'] = 'The replacement is longer than original and shortening is not allow, cannot continue.';
|
||||
$string['cannotfit'] = 'The replacement is longer than the original and shortening is not allowed; cannot continue.';
|
||||
$string['disclaimer'] = 'I understand the risks of this operation';
|
||||
$string['doit'] = 'Yes, do it!';
|
||||
$string['excludedtables'] = 'Several tables are not updated as part of the text replacement. These include configuration, log, events, and session tables.';
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Form for scheduled tasks admin pages.
|
||||
*
|
||||
* @package tool_task
|
||||
* @copyright 2018 Toni Barbera <toni@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Running tasks from CLI.
|
||||
*
|
||||
* @copyright 2018 Toni Barbera <toni@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class run_from_cli {
|
||||
|
||||
/**
|
||||
* Find the path of PHP CLI binary.
|
||||
*
|
||||
* @return string|false The PHP CLI executable PATH
|
||||
*/
|
||||
protected static function find_php_cli_path() {
|
||||
global $CFG;
|
||||
|
||||
if (!empty($CFG->pathtophp) && is_executable(trim($CFG->pathtophp))) {
|
||||
return $CFG->pathtophp;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if Moodle have access to PHP CLI binary or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_runnable():bool {
|
||||
return self::find_php_cli_path() !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a cron from web invocation using PHP CLI.
|
||||
*
|
||||
* @param \core\task\task_base $task Task that be executed via CLI.
|
||||
* @return bool
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public static function execute(\core\task\task_base $task):bool {
|
||||
global $CFG;
|
||||
|
||||
if (!self::is_runnable()) {
|
||||
$redirecturl = new \moodle_url('/admin/settings.php', ['section' => 'systempaths']);
|
||||
throw new \moodle_exception('cannotfindthepathtothecli', 'tool_task', $redirecturl->out());
|
||||
} else {
|
||||
// Shell-escaped path to the PHP binary.
|
||||
$phpbinary = escapeshellarg(self::find_php_cli_path());
|
||||
|
||||
// Shell-escaped path CLI script.
|
||||
$pathcomponents = [$CFG->dirroot, $CFG->admin, 'tool', 'task', 'cli', 'schedule_task.php'];
|
||||
$scriptpath = escapeshellarg(implode(DIRECTORY_SEPARATOR, $pathcomponents));
|
||||
|
||||
// Shell-escaped task name.
|
||||
$classname = get_class($task);
|
||||
$taskarg = escapeshellarg("--execute={$classname}");
|
||||
|
||||
// Build the CLI command.
|
||||
$command = "{$phpbinary} {$scriptpath} {$taskarg}";
|
||||
|
||||
// Execute it.
|
||||
passthru($command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
$string['asap'] = 'ASAP';
|
||||
$string['backtoscheduledtasks'] = 'Back to scheduled tasks';
|
||||
$string['blocking'] = 'Blocking';
|
||||
$string['cannotfindthepathtothecli'] = 'Cannot find the path to the PHP CLI executable so task execution aborted. Set the "Path to PHP CLI" setting in "Site administration / Server / System paths"';
|
||||
$string['clearfaildelay_confirm'] = 'Are you sure you want to clear the fail delay for task \'{$a}\'? After clearing the delay, the task will run according to its normal schedule.';
|
||||
$string['component'] = 'Component';
|
||||
$string['corecomponent'] = 'Core';
|
||||
@@ -48,13 +49,38 @@ $string['scheduledtasks'] = 'Scheduled tasks';
|
||||
$string['scheduledtaskchangesdisabled'] = 'Modifications to the list of scheduled tasks have been prevented in Moodle configuration';
|
||||
$string['taskdisabled'] = 'Task disabled';
|
||||
$string['taskscheduleday'] = 'Day';
|
||||
$string['taskscheduleday_help'] = 'Day of month field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every day</li><li><strong>*/2</strong> Every 2nd day</li><li><strong>1</strong> The first of every month</li><li><strong>1,15</strong> The first and fifteenth of every month</li></ul>';
|
||||
$string['taskscheduleday_help'] = 'Day of month field for task schedule. The field uses the same format as unix cron. Some examples are:
|
||||
|
||||
* <strong>*</strong> Every day
|
||||
* <strong>*/2</strong> Every 2nd day
|
||||
* <strong>1</strong> The first of every month
|
||||
* <strong>1,15</strong> The first and fifteenth of every month';
|
||||
$string['taskscheduledayofweek'] = 'Day of week';
|
||||
$string['taskscheduledayofweek_help'] = 'Day of week field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every day</li><li><strong>0</strong> Every Sunday</li><li><strong>6</strong> Every Saturday</li><li><strong>1,5</strong> Every Monday and Friday</li></ul>';
|
||||
$string['taskscheduledayofweek_help'] = 'Day of week field for task schedule. The field uses the same format as unix cron. Some examples are:
|
||||
|
||||
* <strong>*</strong> Every day
|
||||
* <strong>0</strong> Every Sunday
|
||||
* <strong>6</strong> Every Saturday
|
||||
* <strong>1,5</strong> Every Monday and Friday';
|
||||
$string['taskschedulehour'] = 'Hour';
|
||||
$string['taskschedulehour_help'] = 'Hour field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every hour</li><li><strong>*/2</strong> Every 2 hours</li><li><strong>2-10</strong> Every hour from 2am until 10am (inclusive)</li><li><strong>2,6,9</strong> 2am, 6am and 9am</li></ul>';
|
||||
$string['taskschedulehour_help'] = 'Hour field for task schedule. The field uses the same format as unix cron. Some examples are:
|
||||
|
||||
* <strong>*</strong> Every hour
|
||||
* <strong>*/2</strong> Every 2 hours
|
||||
* <strong>2-10</strong> Every hour from 2am until 10am (inclusive)
|
||||
* <strong>2,6,9</strong> 2am, 6am and 9am';
|
||||
$string['taskscheduleminute'] = 'Minute';
|
||||
$string['taskscheduleminute_help'] = 'Minute field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every minute</li><li><strong>*/5</strong> Every 5 minutes</li><li><strong>2-10</strong> Every minute between 2 and 10 past the hour (inclusive)</li><li><strong>2,6,9</strong> 2 6 and 9 minutes past the hour</li></ul>';
|
||||
$string['taskscheduleminute_help'] = 'Minute field for task schedule. The field uses the same format as unix cron. Some examples are:
|
||||
|
||||
* <strong>*</strong> Every minute
|
||||
* <strong>*/5</strong> Every 5 minutes
|
||||
* <strong>2-10</strong> Every minute between 2 and 10 past the hour (inclusive)
|
||||
* <strong>2,6,9</strong> 2, 6 and 9 minutes past the hour';
|
||||
$string['taskschedulemonth'] = 'Month';
|
||||
$string['taskschedulemonth_help'] = 'Month field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every month</li><li><strong>*/2</strong> Every second month</li><li><strong>1</strong> Every January</li><li><strong>1,5</strong> Every January and May</li></ul>';
|
||||
$string['taskschedulemonth_help'] = 'Month field for task schedule. The field uses the same format as unix cron. Some examples are:
|
||||
|
||||
* <strong>*</strong> Every month
|
||||
* <strong>*/2</strong> Every second month
|
||||
* <strong>1</strong> Every January
|
||||
* <strong>1,5</strong> Every January and May';
|
||||
$string['privacy:metadata'] = 'The Scheduled task configuration plugin does not store any personal data.';
|
||||
|
||||
@@ -62,6 +62,7 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||
$asap = get_string('asap', 'tool_task');
|
||||
$disabledstr = get_string('taskdisabled', 'tool_task');
|
||||
$plugindisabledstr = get_string('plugindisabled', 'tool_task');
|
||||
$runnabletasks = tool_task\run_from_cli::is_runnable();
|
||||
foreach ($tasks as $task) {
|
||||
$customised = $task->is_customised() ? $no : $yes;
|
||||
if (empty($CFG->preventscheduledtaskchanges)) {
|
||||
@@ -105,7 +106,7 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$runnow = '';
|
||||
if (!$disabled && get_config('tool_task', 'enablerunnow')) {
|
||||
if ( ! $disabled && get_config('tool_task', 'enablerunnow') && $runnabletasks ) {
|
||||
$runnow = html_writer::div(html_writer::link(
|
||||
new moodle_url('/admin/tool/task/schedule_task.php',
|
||||
array('task' => get_class($task))),
|
||||
|
||||
@@ -88,7 +88,8 @@ echo html_writer::start_tag('pre');
|
||||
$CFG->mtrace_wrapper = 'tool_task_mtrace_wrapper';
|
||||
|
||||
// Run the specified task (this will output an error if it doesn't exist).
|
||||
cron_run_single_task($task);
|
||||
\tool_task\run_from_cli::execute($task);
|
||||
|
||||
echo html_writer::end_tag('pre');
|
||||
|
||||
$output = $PAGE->get_renderer('tool_task');
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
@tool @tool_task
|
||||
Feature: Run tasks from web interface
|
||||
In order to run scheduled tasks immediately
|
||||
As an admin
|
||||
I need to be able to run a task from the web interface
|
||||
|
||||
Scenario: Run a task
|
||||
Given I log in as "admin"
|
||||
When I navigate to "Scheduled tasks" node in "Site administration > Server"
|
||||
Then I should see "Never" in the "Log table cleanup" "table_row"
|
||||
|
||||
And I click on "Run now" "text" in the "Log table cleanup" "table_row"
|
||||
And I should see "Are you sure you want to run this task"
|
||||
And I press "Run now"
|
||||
|
||||
And I should see "Log table cleanup" in the "h2" "css_element"
|
||||
And I should see "Scheduled task complete: Log table cleanup"
|
||||
|
||||
And I follow "Back to scheduled tasks"
|
||||
And I should not see "Never" in the "Log table cleanup" "table_row"
|
||||
|
||||
Scenario: Cancel running a task
|
||||
Given I log in as "admin"
|
||||
When I navigate to "Scheduled tasks" node in "Site administration > Server"
|
||||
And I click on "Run now" "text" in the "Log table cleanup" "table_row"
|
||||
And I press "Cancel"
|
||||
# Confirm we're back on the scheduled tasks page by looking for the table.
|
||||
Then "Log table cleanup" "table_row" should exist
|
||||
|
||||
Scenario: Cannot run a task when the option is disabled
|
||||
Given the following config values are set as admin:
|
||||
| enablerunnow | 0 | tool_task |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Scheduled tasks" node in "Site administration > Server"
|
||||
Then I should not see "Run now"
|
||||
@@ -104,8 +104,7 @@ $string['reset_help'] = 'Whether to reset the course after creating/updating it.
|
||||
$string['result'] = 'Result';
|
||||
$string['restoreafterimport'] = 'Restore after import';
|
||||
$string['rowpreviewnum'] = 'Preview rows';
|
||||
$string['rowpreviewnum_help'] = 'Number of rows from the CSV file that will be previewed in the next page. This option exists in
|
||||
order to limit the next page size.';
|
||||
$string['rowpreviewnum_help'] = 'Number of rows from the CSV file that will be previewed on the following page. This option is for limiting the size of the following page.';
|
||||
$string['shortnametemplate'] = 'Template to generate a shortname';
|
||||
$string['shortnametemplate_help'] = 'The short name of the course is displayed in the navigation. You may use template syntax here (%f = fullname, %i = idnumber), or enter an initial value that is incremented.';
|
||||
$string['templatefile'] = 'Restore from this file after upload';
|
||||
|
||||
@@ -183,6 +183,7 @@ function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $pr
|
||||
$processed = array();
|
||||
foreach ($columns as $key=>$unused) {
|
||||
$field = $columns[$key];
|
||||
$field = trim($field);
|
||||
$lcfield = core_text::strtolower($field);
|
||||
if (in_array($field, $stdfields) or in_array($lcfield, $stdfields)) {
|
||||
// standard fields are only lowercase
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/str","core/notification"],function(a,b,c){var d={removeStep:function(d){d.preventDefault(),b.get_strings([{key:"confirmstepremovaltitle",component:"tool_usertours"},{key:"confirmstepremovalquestion",component:"tool_usertours"},{key:"yes",component:"moodle"},{key:"no",component:"moodle"}]).done(function(b){c.confirm(b[0],b[1],b[2],b[3],a.proxy(function(){window.location=a(this).attr("href")},d.currentTarget))})},setup:function(){a("body").delegate('[data-action="delete"]',"click",d.removeStep)}};return{setup:d.setup}});
|
||||
define(["jquery","core/str","core/notification"],function(a,b,c){var d={removeStep:function(d){d.preventDefault();var e=a(d.currentTarget).attr("href");b.get_strings([{key:"confirmstepremovaltitle",component:"tool_usertours"},{key:"confirmstepremovalquestion",component:"tool_usertours"},{key:"yes",component:"moodle"},{key:"no",component:"moodle"}]).then(function(a){c.confirm(a[0],a[1],a[2],a[3],function(){window.location=e})})["catch"]()},setup:function(){a("body").delegate('[data-action="delete"]',"click",d.removeStep)}};return{setup:d.setup}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/str","core/notification"],function(a,b,c,d){var e={removeTour:function(b){b.preventDefault(),c.get_strings([{key:"confirmtourremovaltitle",component:"tool_usertours"},{key:"confirmtourremovalquestion",component:"tool_usertours"},{key:"yes",component:"moodle"},{key:"no",component:"moodle"}]).done(function(c){d.confirm(c[0],c[1],c[2],c[3],a.proxy(function(){window.location=a(this).attr("href")},b.currentTarget))})},setup:function(){a("body").delegate('[data-action="delete"]',"click",e.removeTour)}};return{setup:e.setup}});
|
||||
define(["jquery","core/ajax","core/str","core/notification"],function(a,b,c,d){var e={removeTour:function(b){b.preventDefault();var e=a(b.currentTarget).attr("href");c.get_strings([{key:"confirmtourremovaltitle",component:"tool_usertours"},{key:"confirmtourremovalquestion",component:"tool_usertours"},{key:"yes",component:"moodle"},{key:"no",component:"moodle"}]).then(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){window.location=e})})["catch"]()},setup:function(){a("body").delegate('[data-action="delete"]',"click",e.removeTour)}};return{setup:e.setup}});
|
||||
@@ -18,6 +18,7 @@ function($, str, notification) {
|
||||
*/
|
||||
removeStep: function(e) {
|
||||
e.preventDefault();
|
||||
var targetUrl = $(e.currentTarget).attr('href');
|
||||
str.get_strings([
|
||||
{
|
||||
key: 'confirmstepremovaltitle',
|
||||
@@ -35,11 +36,15 @@ function($, str, notification) {
|
||||
key: 'no',
|
||||
component: 'moodle'
|
||||
}
|
||||
]).done(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], $.proxy(function() {
|
||||
window.location = $(this).attr('href');
|
||||
}, e.currentTarget));
|
||||
});
|
||||
])
|
||||
.then(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
window.location = targetUrl;
|
||||
});
|
||||
|
||||
return;
|
||||
})
|
||||
.catch();
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,7 @@ function($, ajax, str, notification) {
|
||||
*/
|
||||
removeTour: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var targetUrl = $(e.currentTarget).attr('href');
|
||||
str.get_strings([
|
||||
{
|
||||
key: 'confirmtourremovaltitle',
|
||||
@@ -36,11 +36,15 @@ function($, ajax, str, notification) {
|
||||
key: 'no',
|
||||
component: 'moodle'
|
||||
}
|
||||
]).done(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], $.proxy(function() {
|
||||
window.location = $(this).attr('href');
|
||||
}, e.currentTarget));
|
||||
});
|
||||
])
|
||||
.then(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], function() {
|
||||
window.location = targetUrl;
|
||||
});
|
||||
|
||||
return;
|
||||
})
|
||||
.catch();
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -497,6 +497,12 @@ class helper {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($PAGE->pagelayout, ['maintenance', 'print', 'redirect'])) {
|
||||
// Do not try to show user tours inside iframe, in maintenance mode,
|
||||
// when printing, or during redirects.
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::$bootstrapped) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ $string['newtable'] = 'New table';
|
||||
$string['newtablefrommysql'] = 'New table from MySQL';
|
||||
$string['new_table_from_mysql'] = 'New table from MySQL';
|
||||
$string['nofieldsspecified'] = 'No fields specified';
|
||||
$string['nomasterprimaryuniquefound'] = 'The column(s) that you foreign key references must be included in a primary or unique KEY in the referenced table. Note, the column being in a UNIQUE INDEX is not good enough.';
|
||||
$string['nomasterprimaryuniquefound'] = 'The column(s) that your foreign key references must be included in a primary or unique KEY in the referenced table. Note that the column being in a UNIQUE INDEX is not good enough.';
|
||||
$string['nomissingindexesfound'] = 'No missing indexes have been found, your DB doesn\'t need further actions.';
|
||||
$string['noreffieldsspecified'] = 'No reference fields specified';
|
||||
$string['noreftablespecified'] = 'Specified reference table not found';
|
||||
@@ -217,6 +217,6 @@ $string['yeswrongdefaultsfound'] = '<p>Some inconsistent defaults have been foun
|
||||
<p>After doing that, it\'s highly recommended to execute this utility again to check that no more inconsistent defaults are found.</p>';
|
||||
$string['yeswrongintsfound'] = '<p>Some wrong integers have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to fix them. Remember to backup your data first!</p>
|
||||
<p>After fixing them, it is highly recommended to execute this utility again to check that no more wrong integers are found.</p>';
|
||||
$string['yeswrongoraclesemanticsfound'] = '<p>Some Oracle columns using BYTE semantics have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to create all them. Remember to backup your data first!</p>
|
||||
$string['yeswrongoraclesemanticsfound'] = '<p>Some Oracle columns using BYTE semantics have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to convert them all. Remember to backup your data first!</p>
|
||||
<p>After doing that, it\'s highly recommended to execute this utility again to check that no more wrong semantics are found.</p>';
|
||||
$string['privacy:metadata'] = 'The XMLDB editor plugin does not store any personal data.';
|
||||
|
||||
@@ -577,9 +577,14 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Remove samples the target consider invalid.
|
||||
$this->analysabletarget->add_sample_data($samplesdata);
|
||||
$this->analysabletarget->filter_out_invalid_samples($sampleids, $analysable, $includetarget);
|
||||
try {
|
||||
// Remove samples the target consider invalid.
|
||||
$this->analysabletarget->add_sample_data($samplesdata);
|
||||
$this->analysabletarget->filter_out_invalid_samples($sampleids, $analysable, $includetarget);
|
||||
} catch (\Throwable $e) {
|
||||
$dataset->close_process();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (!$sampleids) {
|
||||
$result->status = \core_analytics\model::NO_DATASET;
|
||||
@@ -588,19 +593,24 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($this->indicators as $key => $indicator) {
|
||||
// The analyser attaches the main entities the sample depends on and are provided to the
|
||||
// indicator to calculate the sample.
|
||||
$this->indicators[$key]->add_sample_data($samplesdata);
|
||||
}
|
||||
try {
|
||||
foreach ($this->indicators as $key => $indicator) {
|
||||
// The analyser attaches the main entities the sample depends on and are provided to the
|
||||
// indicator to calculate the sample.
|
||||
$this->indicators[$key]->add_sample_data($samplesdata);
|
||||
}
|
||||
|
||||
// Here we start the memory intensive process that will last until $data var is
|
||||
// unset (until the method is finished basically).
|
||||
if ($includetarget) {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges,
|
||||
$this->analysabletarget);
|
||||
} else {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges);
|
||||
// Here we start the memory intensive process that will last until $data var is
|
||||
// unset (until the method is finished basically).
|
||||
if ($includetarget) {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges,
|
||||
$this->analysabletarget);
|
||||
} else {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$dataset->close_process();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
@@ -610,11 +620,16 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Add extra metadata.
|
||||
$this->add_model_metadata($data);
|
||||
try {
|
||||
// Add extra metadata.
|
||||
$this->add_model_metadata($data);
|
||||
|
||||
// Write all calculated data to a file.
|
||||
$file = $dataset->store($data);
|
||||
// Write all calculated data to a file.
|
||||
$file = $dataset->store($data);
|
||||
} catch (\Throwable $e) {
|
||||
$dataset->close_process();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Flag the model + analysable + timesplitting as analysed.
|
||||
$dataset->close_process();
|
||||
|
||||
@@ -169,10 +169,7 @@ abstract class base extends \core_analytics\calculable {
|
||||
|
||||
if (!is_null($calculatedvalue)) {
|
||||
$notnulls[$sampleid] = $sampleid;
|
||||
if ($calculatedvalue > self::MAX_VALUE || $calculatedvalue < self::MIN_VALUE) {
|
||||
throw new \coding_exception('Calculated values should be higher than ' . self::MIN_VALUE .
|
||||
' and lower than ' . self::MAX_VALUE . ' ' . $calculatedvalue . ' received');
|
||||
}
|
||||
$this->validate_calculated_value($calculatedvalue);
|
||||
}
|
||||
|
||||
$calculations[$sampleid] = $calculatedvalue;
|
||||
@@ -182,4 +179,19 @@ abstract class base extends \core_analytics\calculable {
|
||||
|
||||
return array($features, $newcalculations, $notnulls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the calculated value.
|
||||
*
|
||||
* @throws \coding_exception
|
||||
* @param float $calculatedvalue
|
||||
* @return true
|
||||
*/
|
||||
protected function validate_calculated_value($calculatedvalue) {
|
||||
if ($calculatedvalue > self::MAX_VALUE || $calculatedvalue < self::MIN_VALUE) {
|
||||
throw new \coding_exception('Calculated values should be higher than ' . self::MIN_VALUE .
|
||||
' and lower than ' . self::MAX_VALUE . ' ' . $calculatedvalue . ' received');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,7 @@ abstract class binary extends discrete {
|
||||
* @return array
|
||||
*/
|
||||
public static final function get_classes() {
|
||||
// It does not really matter, all \core_analytics\local\indicator\discrete get_classes calls have been overwriten as we
|
||||
// only need 1 column here.
|
||||
return array(0);
|
||||
return [-1, 1];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,8 +52,7 @@ abstract class discrete extends base {
|
||||
public static function get_feature_headers() {
|
||||
$fullclassname = '\\' . get_called_class();
|
||||
|
||||
$headers = array($fullclassname);
|
||||
foreach (self::get_classes() as $class) {
|
||||
foreach (static::get_classes() as $class) {
|
||||
$headers[] = $fullclassname . '/' . $class;
|
||||
}
|
||||
|
||||
@@ -116,26 +115,45 @@ abstract class discrete extends base {
|
||||
*/
|
||||
protected function to_features($calculatedvalues) {
|
||||
|
||||
$classes = self::get_classes();
|
||||
$classes = static::get_classes();
|
||||
|
||||
foreach ($calculatedvalues as $sampleid => $calculatedvalue) {
|
||||
|
||||
$classindex = array_search($calculatedvalue, $classes, true);
|
||||
// Using intval as it may come as a float from the db.
|
||||
$classindex = array_search(intval($calculatedvalue), $classes, true);
|
||||
|
||||
if (!$classindex) {
|
||||
throw new \coding_exception(get_class($this) . ' calculated "' . $calculatedvalue .
|
||||
'" which is not one of its defined classes (' . json_encode($classes) . ')');
|
||||
if ($classindex === false && !is_null($calculatedvalue)) {
|
||||
throw new \coding_exception(get_class($this) . ' calculated value "' . $calculatedvalue .
|
||||
'" is not one of its defined classes (' . json_encode($classes) . ')');
|
||||
}
|
||||
|
||||
// We transform the calculated value into multiple features, one for each of the possible classes.
|
||||
$features = array_fill(0, count($classes), 0);
|
||||
|
||||
// 1 to the selected value.
|
||||
$features[$classindex] = 1;
|
||||
if (!is_null($calculatedvalue)) {
|
||||
$features[$classindex] = 1;
|
||||
}
|
||||
|
||||
$calculatedvalues[$sampleid] = $features;
|
||||
}
|
||||
|
||||
return $calculatedvalues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the calculated value.
|
||||
*
|
||||
* @param float $calculatedvalue
|
||||
* @return true
|
||||
*/
|
||||
protected function validate_calculated_value($calculatedvalue) {
|
||||
|
||||
// Using intval as it may come as a float from the db.
|
||||
if (!in_array(intval($calculatedvalue), static::get_classes())) {
|
||||
throw new \coding_exception(get_class($this) . ' calculated value "' . $calculatedvalue .
|
||||
'" is not one of its defined classes (' . json_encode(static::get_classes()) . ')');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ abstract class base extends \core_analytics\calculable {
|
||||
$message->component = 'moodle';
|
||||
$message->name = 'insights';
|
||||
|
||||
$message->userfrom = get_admin();
|
||||
$message->userfrom = \core_user::get_noreply_user();
|
||||
$message->userto = $user;
|
||||
|
||||
$insighturl = new \moodle_url('/report/insights/insights.php?modelid=' . $modelid . '&contextid=' . $context->id);
|
||||
|
||||
@@ -1480,7 +1480,10 @@ class model {
|
||||
// 1 db read per context.
|
||||
$this->purge_insights_cache();
|
||||
|
||||
$this->model->trained = 0;
|
||||
if (!$this->is_static()) {
|
||||
$this->model->trained = 0;
|
||||
}
|
||||
|
||||
$this->model->timemodified = time();
|
||||
$this->model->usermodified = $USER->id;
|
||||
$DB->update_record('analytics_models', $this->model);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test indicator.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_indicator_discrete extends \core_analytics\local\indicator\discrete {
|
||||
|
||||
/**
|
||||
* Returns the name.
|
||||
*
|
||||
* If there is a corresponding '_help' string this will be shown as well.
|
||||
*
|
||||
* @return \lang_string
|
||||
*/
|
||||
public static function get_name() : \lang_string {
|
||||
// Using a string that exists and contains a corresponding '_help' string.
|
||||
return new \lang_string('allowstealthmodules');
|
||||
}
|
||||
|
||||
/**
|
||||
* The different classes this discrete indicator provides.
|
||||
* @return [type] [description]
|
||||
*/
|
||||
protected static function get_classes() {
|
||||
return [0, 1, 2, 3, 4];
|
||||
}
|
||||
|
||||
/**
|
||||
* Just for testing.
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $subtype
|
||||
* @return string
|
||||
*/
|
||||
public function get_calculation_outcome($value, $subtype = false) {
|
||||
return self::OUTCOME_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom indicator calculated value display as otherwise we would display meaningless numbers to users.
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $subtype
|
||||
* @return string
|
||||
*/
|
||||
public function get_display_value($value, $subtype = false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate_sample
|
||||
*
|
||||
* @param int $sampleid
|
||||
* @param string $sampleorigin
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return float
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -31,7 +31,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_indicator_random extends \core_analytics\local\indicator\binary {
|
||||
class test_indicator_random extends \core_analytics\local\indicator\linear {
|
||||
|
||||
/**
|
||||
* Returns a lang_string object representing the name for the indicator.
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the indicator API.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2019 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_max.php');
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_discrete.php');
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_min.php');
|
||||
|
||||
/**
|
||||
* Unit tests for the model.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class analytics_indicator_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* test_validate_calculated_value
|
||||
*
|
||||
* @param string $indicatorclass
|
||||
* @param array $returnedvalue
|
||||
* @dataProvider validate_calculated_value
|
||||
* @return null
|
||||
*/
|
||||
public function test_validate_calculated_value($indicatorclass, $returnedvalue) {
|
||||
$indicator = new $indicatorclass();
|
||||
list($values, $unused) = $indicator->calculate([1], 'notrelevanthere');
|
||||
$this->assertEquals($returnedvalue, $values[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_validate_calculated_value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validate_calculated_value() {
|
||||
return [
|
||||
'max' => ['test_indicator_max', [1]],
|
||||
'min' => ['test_indicator_min', [-1]],
|
||||
'discrete' => ['test_indicator_discrete', [0, 0, 0, 0, 1]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* test_validate_calculated_value_exceptions
|
||||
*
|
||||
* @param string $indicatorclass
|
||||
* @param string $willreturn
|
||||
* @dataProvider validate_calculated_value_exceptions
|
||||
* @expectedException \coding_exception
|
||||
* @return null
|
||||
*/
|
||||
public function test_validate_calculated_value_exceptions($indicatorclass, $willreturn) {
|
||||
|
||||
$indicator = new $indicatorclass();
|
||||
$indicatormock = $this->getMockBuilder(get_class($indicator))
|
||||
->setMethods(['calculate_sample'])
|
||||
->getMock();
|
||||
$indicatormock->method('calculate_sample')->willReturn($willreturn);
|
||||
list($values, $unused) = $indicatormock->calculate([1], 'notrelevanthere');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_validate_calculated_value_exceptions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function validate_calculated_value_exceptions() {
|
||||
return [
|
||||
'max' => ['test_indicator_max', 2],
|
||||
'min' => ['test_indicator_min', -2],
|
||||
'discrete' => ['test_indicator_discrete', 7],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -168,10 +168,34 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
$this->assertEmpty($DB->count_records('analytics_predict_samples'));
|
||||
$this->assertEmpty($DB->count_records('analytics_used_files'));
|
||||
|
||||
// Check that the model is marked as not trained after clearing (as it is not a static one).
|
||||
$this->assertEquals(0, $DB->get_field('analytics_models', 'trained', array('id' => $this->modelobj->id)));
|
||||
|
||||
set_config('enabled_stores', '', 'tool_log');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test behaviour of {\core_analytics\model::clear()} for static models.
|
||||
*/
|
||||
public function test_clear_static() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$statictarget = new test_static_target_shortname();
|
||||
$indicators['test_indicator_max'] = \core_analytics\manager::get_indicator('test_indicator_max');
|
||||
$model = \core_analytics\model::create($statictarget, $indicators, '\core\analytics\time_splitting\quarters');
|
||||
$modelobj = $model->get_model_obj();
|
||||
|
||||
// Static models are always considered trained.
|
||||
$this->assertEquals(1, $DB->get_field('analytics_models', 'trained', array('id' => $modelobj->id)));
|
||||
|
||||
$model->clear();
|
||||
|
||||
// Check that the model is still marked as trained even after clearing.
|
||||
$this->assertEquals(1, $DB->get_field('analytics_models', 'trained', array('id' => $modelobj->id)));
|
||||
}
|
||||
|
||||
public function test_model_manager() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
|
||||
@@ -455,7 +455,7 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$indicator = $this->getMockBuilder('test_indicator_max')->setMethods(['calculate_sample'])->getMock();
|
||||
$indicator->expects($this->never())->method('calculate_sample');
|
||||
|
||||
$existingcalcs = array(111 => 1, 222 => 0.5);
|
||||
$existingcalcs = array(111 => 1, 222 => -1);
|
||||
$sampleids = array(111 => 111, 222 => 222);
|
||||
list($values, $unused) = $indicator->calculate($sampleids, $sampleorigin, $starttime, $endtime, $existingcalcs);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ $string['auth_dbextencodinghelp'] = 'Encoding used in external database';
|
||||
$string['auth_dbextrafields'] = 'These fields are optional. You can choose to pre-fill some Moodle user fields with information from the <b>external database fields</b> that you specify here. <p>If you leave these blank, then defaults will be used.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
|
||||
$string['auth_dbfieldpass'] = 'Name of the field containing passwords';
|
||||
$string['auth_dbfieldpass_key'] = 'Password field';
|
||||
$string['auth_dbfielduser'] = 'Name of the field containing usernames';
|
||||
$string['auth_dbfielduser'] = 'Name of the field containing usernames. This field must be a varchar data type.';
|
||||
$string['auth_dbfielduser_key'] = 'Username field';
|
||||
$string['auth_dbhost'] = 'The computer hosting the database server. Use a system DSN entry if using ODBC. Use a PDO DSN entry if using PDO.';
|
||||
$string['auth_dbhost_key'] = 'Host';
|
||||
|
||||
@@ -99,6 +99,13 @@ class auth_db_testcase extends advanced_testcase {
|
||||
case 'mssql':
|
||||
set_config('type', 'mssqlnative', 'auth_db');
|
||||
set_config('sybasequoting', '1', 'auth_db');
|
||||
|
||||
// The native sqlsrv driver uses a comma as separator between host and port.
|
||||
$dbhost = $CFG->dbhost;
|
||||
if (!empty($dboptions['dbport'])) {
|
||||
$dbhost .= ',' . $dboptions['dbport'];
|
||||
}
|
||||
set_config('host', $dbhost, 'auth_db');
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -63,3 +63,36 @@ Feature: User must accept policy when logging in and signing up
|
||||
And I log in as "user1"
|
||||
And I open my profile in edit mode
|
||||
And the field "First name" matches value "User1"
|
||||
|
||||
Scenario Outline: Email validation during email registration
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
| registerauth | email |
|
||||
| passwordpolicy | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
And I am on site homepage
|
||||
And I follow "Log in"
|
||||
When I press "Create new account"
|
||||
And I set the following fields to these values:
|
||||
| Username | s2 |
|
||||
| Password | test |
|
||||
| Email address | <email1> |
|
||||
| Email (again) | <email2> |
|
||||
| First name | Jane |
|
||||
| Surname | Doe |
|
||||
And I press "Create my new account"
|
||||
Then I should <expect> "This email address is already registered. Perhaps you created an account in the past?"
|
||||
And I should <expect2> "Invalid email address"
|
||||
|
||||
Examples:
|
||||
| allowsameemail | email1 | email2 | expect | expect2 |
|
||||
| 0 | s1@example.com | s1@example.com | see | not see |
|
||||
| 0 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | see | not see |
|
||||
| 0 | s1@example.com | S1@EXAMPLE.COM | see | not see |
|
||||
| 0 | s2@example.com | s1@example.com | not see | see |
|
||||
| 1 | s1@example.com | s1@example.com | not see | not see |
|
||||
| 1 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | not see | not see |
|
||||
| 1 | s1@example.com | S1@EXAMPLE.COM | not see | not see |
|
||||
| 1 | s1@example.com | s2@example.com | not see | see |
|
||||
|
||||
@@ -36,11 +36,7 @@ $string['auth_ldap_contexts_key'] = 'Contexts';
|
||||
$string['auth_ldap_create_context'] = 'If you enable user creation with email confirmation, specify the context where users are created. This context should be different from other users to prevent security issues. You don\'t need to add this context to ldap_context-variable, Moodle will search for users from this context automatically.<br /><b>Note!</b> You have to modify the method user_create() in file auth/ldap/auth.php to make user creation work';
|
||||
$string['auth_ldap_create_context_key'] = 'Context for new users';
|
||||
$string['auth_ldap_create_error'] = 'Error creating user in LDAP.';
|
||||
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server.
|
||||
If the given username and password are valid, Moodle creates a new user
|
||||
entry in its database. This module can read user attributes from LDAP and prefill
|
||||
wanted fields in Moodle. For following logins only the username and
|
||||
password are checked.';
|
||||
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server. If the given username and password are valid, Moodle creates a new user entry in its database. This plugin can read user attributes from LDAP and prefill wanted fields in Moodle. For following logins only the username and password are checked.';
|
||||
$string['auth_ldap_expiration_desc'] = 'Select \'{$a->no}\' to disable expired password checking or \'{$a->ldapserver}\' to read the password expiry time directly from the LDAP server.';
|
||||
$string['auth_ldap_expiration_key'] = 'Expiry';
|
||||
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiry warning is issued.';
|
||||
|
||||
@@ -273,13 +273,15 @@ class provider implements
|
||||
return;
|
||||
}
|
||||
|
||||
$userid = $contextlist->get_user()->id;
|
||||
foreach ($contextlist->get_contexts() as $context) {
|
||||
if ($context->contextlevel != CONTEXT_USER) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
if ($context->instanceid == $userid) {
|
||||
// Because we only use user contexts the instance ID is the user ID.
|
||||
$DB->delete_records('mnet_log', ['userid' => $context->instanceid]);
|
||||
}
|
||||
|
||||
// Because we only use user contexts the instance ID is the user ID.
|
||||
$DB->delete_records('mnet_log', ['userid' => $context->instanceid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,12 +178,15 @@ class provider implements
|
||||
if (empty($contextlist->count())) {
|
||||
return;
|
||||
}
|
||||
$userid = $contextlist->get_user()->id;
|
||||
foreach ($contextlist->get_contexts() as $context) {
|
||||
if ($context->contextlevel != CONTEXT_USER) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
if ($context->instanceid == $userid) {
|
||||
// Because we only use user contexts the instance ID is the user ID.
|
||||
static::delete_user_data($context->instanceid);
|
||||
}
|
||||
// Because we only use user contexts the instance ID is the user ID.
|
||||
static::delete_user_data($context->instanceid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ $string['auth_shibboleth_login'] = 'Shibboleth login';
|
||||
$string['auth_shibboleth_login_long'] = 'Login to Moodle via Shibboleth';
|
||||
$string['auth_shibboleth_manual_login'] = 'Manual login';
|
||||
$string['auth_shibboleth_select_member'] = 'I\'m a member of ...';
|
||||
$string['auth_shibboleth_select_organization'] = 'For authentication via Shibboleth, please select your organization from the drop down list:';
|
||||
$string['auth_shibboleth_select_organization'] = 'For authentication via Shibboleth, please select your organisation from the drop-down menu:';
|
||||
$string['auth_shib_convert_data'] = 'Data modification API';
|
||||
$string['auth_shib_convert_data_description'] = 'You can use this API to further modify the data provided by Shibboleth. Read the <a href="../auth/shibboleth/README.txt">README</a> for further instructions.';
|
||||
$string['auth_shib_convert_data_warning'] = 'The file does not exist or is not readable by the webserver process!';
|
||||
|
||||
@@ -311,11 +311,11 @@ class backup_controller extends base_controller {
|
||||
// Basic/initial prevention against time/memory limits
|
||||
core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
|
||||
raise_memory_limit(MEMORY_EXTRA);
|
||||
// If this is not a course backup, inform the plan we are not
|
||||
// If this is not a course backup, or single activity backup (e.g. duplicate) inform the plan we are not
|
||||
// including all the activities for sure. This will affect any
|
||||
// task/step executed conditionally to stop including information
|
||||
// for section and activity backup. MDL-28180.
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE) {
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) {
|
||||
$this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
|
||||
$this->plan->set_excluding_activities();
|
||||
}
|
||||
|
||||
@@ -326,11 +326,11 @@ class restore_controller extends base_controller {
|
||||
// Basic/initial prevention against time/memory limits
|
||||
core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
|
||||
raise_memory_limit(MEMORY_EXTRA);
|
||||
// If this is not a course restore, inform the plan we are not
|
||||
// If this is not a course restore or single activity restore (e.g. duplicate), inform the plan we are not
|
||||
// including all the activities for sure. This will affect any
|
||||
// task/step executed conditionally to stop processing information
|
||||
// for section and activity restore. MDL-28180.
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE) {
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) {
|
||||
$this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
|
||||
$this->plan->set_excluding_activities();
|
||||
}
|
||||
|
||||
@@ -2270,7 +2270,11 @@ class backup_questions_structure_step extends backup_structure_step {
|
||||
FROM {tag} t
|
||||
JOIN {tag_instance} ti ON ti.tagid = t.id
|
||||
WHERE ti.itemid = ?
|
||||
AND ti.itemtype = 'question'", array(backup::VAR_PARENTID));
|
||||
AND ti.itemtype = 'question'
|
||||
AND ti.component = 'core_question'",
|
||||
[
|
||||
backup::VAR_PARENTID
|
||||
]);
|
||||
|
||||
// don't need to annotate ids nor files
|
||||
// (already done by {@link backup_annotate_all_question_files}
|
||||
|
||||
@@ -580,6 +580,17 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
'backup_import_competencies' => 'competencies'
|
||||
);
|
||||
self::apply_admin_config_defaults($controller, $settings, true);
|
||||
if ((!$controller->get_interactive()) &&
|
||||
$controller->get_type() == backup::TYPE_1ACTIVITY) {
|
||||
// This is duplicate - there is no concept of defaults - these settings must be on.
|
||||
$settings = array(
|
||||
'activities',
|
||||
'blocks',
|
||||
'filters',
|
||||
'questionbank'
|
||||
);
|
||||
self::force_enable_settings($controller, $settings);
|
||||
}
|
||||
break;
|
||||
case backup::MODE_AUTOMATED:
|
||||
// Load the automated defaults.
|
||||
@@ -607,6 +618,30 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn these settings on. No defaults from admin settings.
|
||||
*
|
||||
* @param backup_controller $controller
|
||||
* @param array $settings a map from admin config names to setting names (Config name => Setting name)
|
||||
*/
|
||||
private static function force_enable_settings(backup_controller $controller, array $settings) {
|
||||
$plan = $controller->get_plan();
|
||||
foreach ($settings as $config => $settingname) {
|
||||
$value = true;
|
||||
if ($plan->setting_exists($settingname)) {
|
||||
$setting = $plan->get_setting($settingname);
|
||||
// We do not allow this setting to be locked for a duplicate function.
|
||||
if ($setting->get_status() !== base_setting::NOT_LOCKED) {
|
||||
$setting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
$setting->set_value($value);
|
||||
$setting->set_status(base_setting::LOCKED_BY_CONFIG);
|
||||
} else {
|
||||
$controller->log('Unknown setting: ' . $setting, BACKUP::LOG_DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller settings default values from the admin config.
|
||||
*
|
||||
|
||||
@@ -183,6 +183,18 @@ abstract class restore_controller_dbops extends restore_dbops {
|
||||
);
|
||||
self::apply_admin_config_defaults($controller, $settings, true);
|
||||
}
|
||||
if ($controller->get_mode() == backup::MODE_IMPORT &&
|
||||
(!$controller->get_interactive()) &&
|
||||
$controller->get_type() == backup::TYPE_1ACTIVITY) {
|
||||
// This is duplicate - there is no concept of defaults - these settings must be on.
|
||||
$settings = array(
|
||||
'activities',
|
||||
'blocks',
|
||||
'filters',
|
||||
'questionbank'
|
||||
);
|
||||
self::force_enable_settings($controller, $settings);
|
||||
};
|
||||
|
||||
// Add some dependencies.
|
||||
$plan = $controller->get_plan();
|
||||
@@ -233,6 +245,30 @@ abstract class restore_controller_dbops extends restore_dbops {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn these settings on. No defaults from admin settings.
|
||||
*
|
||||
* @param restore_controller $controller
|
||||
* @param array $settings a map from admin config names to setting names (Config name => Setting name)
|
||||
*/
|
||||
private static function force_enable_settings(restore_controller $controller, array $settings) {
|
||||
$plan = $controller->get_plan();
|
||||
foreach ($settings as $config => $settingname) {
|
||||
$value = true;
|
||||
if ($plan->setting_exists($settingname)) {
|
||||
$setting = $plan->get_setting($settingname);
|
||||
// We do not allow this setting to be locked for a duplicate function.
|
||||
if ($setting->get_status() !== base_setting::NOT_LOCKED) {
|
||||
$setting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
$setting->set_value($value);
|
||||
$setting->set_status(base_setting::LOCKED_BY_CONFIG);
|
||||
} else {
|
||||
$controller->log('Unknown setting: ' . $settingname, BACKUP::LOG_DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller settings default values from the admin config.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
@@ -82,7 +81,7 @@ abstract class setting_dependency {
|
||||
* Destroy all circular references. It helps PHP 5.2 a lot!
|
||||
*/
|
||||
public function destroy() {
|
||||
// No need to destroy anything recursively here, direct reset
|
||||
// No need to destroy anything recursively here, direct reset.
|
||||
$this->setting = null;
|
||||
$this->dependentsetting = null;
|
||||
}
|
||||
@@ -94,16 +93,19 @@ abstract class setting_dependency {
|
||||
* @return bool
|
||||
*/
|
||||
final public function process_change($changetype, $oldvalue) {
|
||||
// Check the type of change requested
|
||||
// Check the type of change requested.
|
||||
switch ($changetype) {
|
||||
// Process a status change
|
||||
case base_setting::CHANGED_STATUS: return $this->process_status_change($oldvalue);
|
||||
// Process a visibility change
|
||||
case base_setting::CHANGED_VISIBILITY: return $this->process_visibility_change($oldvalue);
|
||||
// Process a value change
|
||||
case base_setting::CHANGED_VALUE: return $this->process_value_change($oldvalue);
|
||||
// Process a status change.
|
||||
case base_setting::CHANGED_STATUS:
|
||||
return $this->process_status_change($oldvalue);
|
||||
// Process a visibility change.
|
||||
case base_setting::CHANGED_VISIBILITY:
|
||||
return $this->process_visibility_change($oldvalue);
|
||||
// Process a value change.
|
||||
case base_setting::CHANGED_VALUE:
|
||||
return $this->process_value_change($oldvalue);
|
||||
}
|
||||
// Throw an exception if we get this far
|
||||
// Throw an exception if we get this far.
|
||||
throw new backup_ui_exception('unknownchangetype');
|
||||
}
|
||||
/**
|
||||
@@ -112,11 +114,11 @@ abstract class setting_dependency {
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_visibility_change($oldvisibility) {
|
||||
// Store the current dependent settings visibility for comparison
|
||||
// Store the current dependent settings visibility for comparison.
|
||||
$prevalue = $this->dependentsetting->get_visibility();
|
||||
// Set it regardless of whether we need to
|
||||
// Set it regardless of whether we need to.
|
||||
$this->dependentsetting->set_visibility($this->setting->get_visibility());
|
||||
// Return true if it changed
|
||||
// Return true if it changed.
|
||||
return ($prevalue != $this->dependentsetting->get_visibility());
|
||||
}
|
||||
/**
|
||||
@@ -182,15 +184,16 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
*/
|
||||
public function __construct(base_setting $setting, base_setting $dependentsetting, $value, $defaultvalue = false) {
|
||||
parent::__construct($setting, $dependentsetting, $defaultvalue);
|
||||
$this->value = ($value)?(string)$value:0;
|
||||
$this->value = ($value) ? (string)$value : 0;
|
||||
}
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || $this->setting->get_value() == $this->value) {
|
||||
// If the setting is locked or the dependent setting should be locked then return true.
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED ||
|
||||
$this->evaluate_disabled_condition($this->setting->get_value())) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
@@ -208,17 +211,25 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
return false;
|
||||
}
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
if ($this->setting->get_value() == $this->value) {
|
||||
// If the setting is the desired value enact the dependency.
|
||||
$settingvalue = $this->setting->get_value();
|
||||
if ($this->evaluate_disabled_condition($settingvalue)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
|
||||
// For checkboxes the default value is false, but when the setting is
|
||||
// locked, the value should inherit from the parent setting.
|
||||
if ($this->defaultvalue === false) {
|
||||
$this->dependentsetting->set_value($settingvalue);
|
||||
} else {
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
}
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
// We can unlock the dependent setting.
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
// Return true if the value has changed for the dependent setting.
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
/**
|
||||
@@ -227,17 +238,18 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_status_change($oldstatus) {
|
||||
// Store the dependent status
|
||||
// Store the dependent status.
|
||||
$prevalue = $this->dependentsetting->get_status();
|
||||
// Store the current status
|
||||
// Store the current status.
|
||||
$currentstatus = $this->setting->get_status();
|
||||
if ($currentstatus == base_setting::NOT_LOCKED) {
|
||||
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY && $this->setting->get_value() != $this->value) {
|
||||
// Dependency has changes, is not fine, unlock the dependent setting
|
||||
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY &&
|
||||
!$this->evaluate_disabled_condition($this->setting->get_value())) {
|
||||
// Dependency has changes, is not fine, unlock the dependent setting.
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
} else {
|
||||
// Make sure the dependent setting is also locked, in this case by hierarchy
|
||||
// Make sure the dependent setting is also locked, in this case by hierarchy.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
}
|
||||
// Return true if the dependent setting has changed.
|
||||
@@ -248,17 +260,17 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
* @return bool True if there were changes
|
||||
*/
|
||||
public function enforce() {
|
||||
// This will be set to true if ANYTHING changes
|
||||
// This will be set to true if ANYTHING changes.
|
||||
$changes = false;
|
||||
// First process any value changes
|
||||
// First process any value changes.
|
||||
if ($this->process_value_change($this->setting->get_value())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Second process any status changes
|
||||
// Second process any status changes.
|
||||
if ($this->process_status_change($this->setting->get_status())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Finally process visibility changes
|
||||
// Finally process visibility changes.
|
||||
if ($this->process_visibility_change($this->setting->get_visibility())) {
|
||||
$changes = true;
|
||||
}
|
||||
@@ -271,152 +283,72 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'eq',
|
||||
'value'=>$this->value
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'eq',
|
||||
'value' => $this->value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* This function should be abstract, but there will probably be existing sub-classes so we must provide a default
|
||||
* implementation.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return $value == $this->value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency that disables the secondary setting if the primary setting is
|
||||
* not equal to the provided value
|
||||
*
|
||||
* @copyright 2011 Darko Miletic <dmiletic@moodlerooms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class setting_dependency_disabledif_not_equals extends setting_dependency_disabledif_equals {
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return $value != $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
* @return array
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notequal',
|
||||
'value' => $this->value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency that disables the secondary setting if the primary setting is
|
||||
* not equal to the provided value
|
||||
*
|
||||
* @copyright 2011 Darko Miletic <dmiletic@moodlerooms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class setting_dependency_disabledif_not_equals extends setting_dependency_disabledif_equals {
|
||||
/**
|
||||
* Enforces the dependency if required.
|
||||
* @return bool True if there were changes
|
||||
*/
|
||||
public function enforce() {
|
||||
// This will be set to true if ANYTHING changes
|
||||
$changes = false;
|
||||
// First process any value changes
|
||||
if (!$this->process_value_change($this->setting->get_value())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Second process any status changes
|
||||
if ($this->process_status_change($this->setting->get_status())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Finally process visibility changes
|
||||
if ($this->process_visibility_change($this->setting->get_visibility())) {
|
||||
$changes = true;
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
* @return array
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notequal',
|
||||
'value'=>$this->value
|
||||
);
|
||||
}
|
||||
}
|
||||
* Disable if a value is in a list.
|
||||
*/
|
||||
class setting_dependency_disabledif_in_array extends setting_dependency_disabledif_equals {
|
||||
|
||||
//with array
|
||||
class setting_dependency_disabledif_equals2 extends setting_dependency {
|
||||
/**
|
||||
* The value to compare to
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
/**
|
||||
* Creates the dependency
|
||||
*
|
||||
* @param base_setting $setting
|
||||
* @param base_setting $dependentsetting
|
||||
* @param mixed $value
|
||||
* @param mixed $defaultvalue
|
||||
*/
|
||||
public function __construct(base_setting $setting, base_setting $dependentsetting, array $value, $defaultvalue = false) {
|
||||
parent::__construct($setting, $dependentsetting, $defaultvalue);
|
||||
$this->value = $value;
|
||||
}
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || in_array($this->setting->get_value(), $this->value)) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Processes a value change in the primary setting
|
||||
* @param mixed $oldvalue
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_value_change($oldvalue) {
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
if (in_array($this->setting->get_value(), $this->value)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
/**
|
||||
* Processes a status change in the primary setting
|
||||
* @param mixed $oldstatus
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_status_change($oldstatus) {
|
||||
// Store the dependent status
|
||||
$prevalue = $this->dependentsetting->get_status();
|
||||
// Store the current status
|
||||
$currentstatus = $this->setting->get_status();
|
||||
if ($currentstatus == base_setting::NOT_LOCKED) {
|
||||
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY && !in_array($this->setting->get_value(), $this->value)) {
|
||||
// Dependency has changes, is not fine, unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
} else {
|
||||
// Make sure the dependent setting is also locked, in this case by hierarchy
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
}
|
||||
// Return true if the dependent setting has changed.
|
||||
return ($prevalue != $this->dependentsetting->get_status());
|
||||
}
|
||||
/**
|
||||
* Enforces the dependency if required.
|
||||
* @return bool True if there were changes
|
||||
*/
|
||||
public function enforce() {
|
||||
// This will be set to true if ANYTHING changes
|
||||
$changes = false;
|
||||
// First process any value changes
|
||||
if ($this->process_value_change($this->setting->get_value())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Second process any status changes
|
||||
if ($this->process_status_change($this->setting->get_status())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Finally process visibility changes
|
||||
if ($this->process_visibility_change($this->setting->get_visibility())) {
|
||||
$changes = true;
|
||||
}
|
||||
return $changes;
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return in_array($value, $this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
@@ -424,14 +356,19 @@ class setting_dependency_disabledif_equals2 extends setting_dependency {
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'eq',
|
||||
'value'=>$this->value
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'eq',
|
||||
'value' => $this->value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is here for backwards compatibility (terrible name).
|
||||
*/
|
||||
class setting_dependency_disabledif_equals2 extends setting_dependency_disabledif_in_array {
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency that disables the secondary element if the primary element is
|
||||
@@ -452,9 +389,9 @@ class setting_dependency_disabledif_checked extends setting_dependency_disabledi
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'checked'
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'checked'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -478,9 +415,9 @@ class setting_dependency_disabledif_not_checked extends setting_dependency_disab
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notchecked'
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notchecked'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -497,6 +434,16 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
|
||||
parent::__construct($setting, $dependentsetting, false, $defaultvalue);
|
||||
$this->value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return !empty($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
@@ -504,50 +451,12 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notequal',
|
||||
'value'=>''
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notequal',
|
||||
'value' => ''
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Processes a value change in the primary setting
|
||||
* @param mixed $oldvalue
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_value_change($oldvalue) {
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
$value = $this->setting->get_value();
|
||||
if (!empty($value)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
if ($this->defaultvalue === false) {
|
||||
$this->dependentsetting->set_value($value);
|
||||
} else {
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
}
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || !empty($value)) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,6 +471,16 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
|
||||
parent::__construct($setting, $dependentsetting, false, $defaultvalue);
|
||||
$this->value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return empty($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
@@ -569,47 +488,10 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notequal',
|
||||
'value'=>''
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notequal',
|
||||
'value' => ''
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Processes a value change in the primary setting
|
||||
* @param mixed $oldvalue
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_value_change($oldvalue) {
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
$value = $this->setting->get_value();
|
||||
if (empty($value)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
if ($this->defaultvalue === false) {
|
||||
$this->dependentsetting->set_value($value);
|
||||
} else {
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
}
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || empty($value)) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test base_setting class
|
||||
*/
|
||||
function test_base_setting() {
|
||||
public function test_base_setting() {
|
||||
// Instantiate base_setting and check everything
|
||||
$bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
|
||||
$this->assertTrue($bs instanceof base_setting);
|
||||
@@ -290,10 +290,54 @@ class backp_settings_testcase extends basic_testcase {
|
||||
$this->assertEquals($ubs3->get_status(), $ubs1->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that locked and unlocked states on dependent backup settings at the same level
|
||||
* correctly do not flow from the parent to the child setting when the setting is locked by permissions.
|
||||
*/
|
||||
public function test_dependency_empty_locked_by_permission_child_is_not_unlocked() {
|
||||
// Check dependencies are working ok.
|
||||
$bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 2);
|
||||
$bs1->set_level(1);
|
||||
$bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 2);
|
||||
$bs2->set_level(1); // Same level *must* work.
|
||||
$bs1->add_dependency($bs2, setting_dependency::DISABLED_EMPTY);
|
||||
|
||||
$bs1->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
$this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
|
||||
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
|
||||
$bs2->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
|
||||
|
||||
// Unlocking the parent should NOT unlock the child.
|
||||
$bs1->set_status(base_setting::NOT_LOCKED);
|
||||
|
||||
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs2->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that locked and unlocked states on dependent backup settings at the same level
|
||||
* correctly do flow from the parent to the child setting when the setting is locked by config.
|
||||
*/
|
||||
public function test_dependency_not_empty_locked_by_config_parent_is_unlocked() {
|
||||
$bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 0);
|
||||
$bs1->set_level(1);
|
||||
$bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 0);
|
||||
$bs2->set_level(1); // Same level *must* work.
|
||||
$bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
|
||||
|
||||
$bs1->set_status(base_setting::LOCKED_BY_CONFIG);
|
||||
$this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
|
||||
$this->assertEquals(base_setting::LOCKED_BY_CONFIG, $bs1->get_status());
|
||||
|
||||
// Unlocking the parent should unlock the child.
|
||||
$bs1->set_status(base_setting::NOT_LOCKED);
|
||||
$this->assertEquals(base_setting::NOT_LOCKED, $bs2->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* test backup_setting class
|
||||
*/
|
||||
function test_backup_setting() {
|
||||
public function test_backup_setting() {
|
||||
// Instantiate backup_setting class and set level
|
||||
$bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$bs->set_level(1);
|
||||
@@ -340,7 +384,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test activity_backup_setting class
|
||||
*/
|
||||
function test_activity_backup_setting() {
|
||||
public function test_activity_backup_setting() {
|
||||
$bs = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$this->assertEquals($bs->get_level(), backup_setting::ACTIVITY_LEVEL);
|
||||
|
||||
@@ -355,7 +399,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test section_backup_setting class
|
||||
*/
|
||||
function test_section_backup_setting() {
|
||||
public function test_section_backup_setting() {
|
||||
$bs = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$this->assertEquals($bs->get_level(), backup_setting::SECTION_LEVEL);
|
||||
|
||||
@@ -370,7 +414,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test course_backup_setting class
|
||||
*/
|
||||
function test_course_backup_setting() {
|
||||
public function test_course_backup_setting() {
|
||||
$bs = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$this->assertEquals($bs->get_level(), backup_setting::COURSE_LEVEL);
|
||||
|
||||
|
||||
@@ -751,8 +751,11 @@ class backup_setting_ui_defaultcustom extends backup_setting_ui_text {
|
||||
if ($value === false) {
|
||||
$value = $this->attributes['defaultvalue'];
|
||||
}
|
||||
if (!empty($value) && $this->attributes['type'] === 'date_selector') {
|
||||
return userdate($value);
|
||||
if (!empty($value)) {
|
||||
if ($this->attributes['type'] === 'date_selector' ||
|
||||
$this->attributes['type'] === 'date_time_selector') {
|
||||
return userdate($value);
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ Feature: Duplicate activities
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And I log in as "admin"
|
||||
And I set the following administration settings values:
|
||||
| backup_import_activities | 0 |
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Database" to section "1" and I fill the form with:
|
||||
|
||||
@@ -188,7 +188,7 @@ class award_criteria_activity extends award_criteria {
|
||||
* @return bool Whether criteria is complete
|
||||
*/
|
||||
public function review($userid, $filtered = false) {
|
||||
$completionstates = array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS);
|
||||
$completionstates = array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL);
|
||||
|
||||
if ($this->course->startdate > time()) {
|
||||
return false;
|
||||
|
||||
+6
-5
@@ -50,14 +50,15 @@ function badge_review_cron() {
|
||||
if (empty($CFG->badges_allowcoursebadges)) {
|
||||
$coursesql = '';
|
||||
} else {
|
||||
$coursesql = ' OR EXISTS (SELECT id FROM {course} WHERE visible = :visible AND startdate < :current) ';
|
||||
$coursesql = ' OR EXISTS (SELECT c.id FROM {course} c WHERE c.visible = :visible AND c.startdate < :current'
|
||||
. ' AND c.id = b.courseid)';
|
||||
$courseparams = array('visible' => true, 'current' => time());
|
||||
}
|
||||
|
||||
$sql = 'SELECT id
|
||||
FROM {badge}
|
||||
WHERE (status = :active OR status = :activelocked)
|
||||
AND (type = :site ' . $coursesql . ')';
|
||||
$sql = 'SELECT b.id
|
||||
FROM {badge} b
|
||||
WHERE (b.status = :active OR b.status = :activelocked)
|
||||
AND (b.type = :site ' . $coursesql . ')';
|
||||
$badgeparams = array(
|
||||
'active' => BADGE_STATUS_ACTIVE,
|
||||
'activelocked' => BADGE_STATUS_ACTIVE_LOCKED,
|
||||
|
||||
@@ -96,6 +96,7 @@ if (!has_any_capability(array(
|
||||
'moodle/badges:viewawarded',
|
||||
'moodle/badges:createbadge',
|
||||
'moodle/badges:awardbadge',
|
||||
'moodle/badges:configurecriteria',
|
||||
'moodle/badges:configuremessages',
|
||||
'moodle/badges:configuredetails',
|
||||
'moodle/badges:deletebadge'), $PAGE->context)) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
@mod @mod_quiz @core @core_badges @_file_upload @javascript
|
||||
Feature: Award badges based on activity completion
|
||||
In order to ensure a student has learned the material before being marked complete
|
||||
As a teacher
|
||||
I need to set a quiz to award a badge when upon completion when the student receives a passing grade, or completed_fail if they use all attempts without passing
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following config values are set as admin:
|
||||
| grade_item_advanced | hiddenuntil |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | First question | Answer the first question |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber | attempts | gradepass | completion | completionattemptsexhausted | completionpass | completionusegrade |
|
||||
| quiz | Test quiz name | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 | 1 |
|
||||
And quiz "Test quiz name" contains the following questions:
|
||||
| question | page |
|
||||
| First question | 1 |
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test quiz name"
|
||||
And I press "Attempt quiz now"
|
||||
And I click on "False" "radio" in the "Answer the first question" "question"
|
||||
And I press "Finish attempt ..."
|
||||
And I press "Submit all and finish"
|
||||
And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
| issuername | Tester of course badge |
|
||||
And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
|
||||
And I press "Create badge"
|
||||
And I set the field "type" to "Activity completion"
|
||||
And I set the field "Quiz - Test quiz name" to "1"
|
||||
And I press "Save"
|
||||
And I press "Enable access"
|
||||
And I press "Continue"
|
||||
And I should see "Recipients (0)"
|
||||
And I log out
|
||||
|
||||
Scenario: Student earns a badge using activity completion, but does not get passing grade
|
||||
When I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And the "Test quiz name" "quiz" activity with "auto" completion should be marked as not complete
|
||||
And I follow "Test quiz name"
|
||||
And I press "Re-attempt quiz"
|
||||
And I set the field "False" to "1"
|
||||
And I press "Finish attempt ..."
|
||||
And I press "Submit all and finish"
|
||||
And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
|
||||
And I log out
|
||||
Then I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
And I follow "Course Badge"
|
||||
And I should see "Recipients (1)"
|
||||
@@ -66,12 +66,16 @@ class block_login extends block_base {
|
||||
|
||||
$this->content->text .= "\n".'<form class="loginform" id="login" method="post" action="'.get_login_url().'">';
|
||||
|
||||
$this->content->text .= '<div class="form-group"><label for="login_username">'.$strusername.'</label>';
|
||||
$this->content->text .= '<input type="text" name="username" id="login_username" class="form-control" value="'.s($username).'" /></div>';
|
||||
$this->content->text .= '<div class="form-group">';
|
||||
$this->content->text .= '<label for="login_username">'.$strusername.'</label>';
|
||||
$this->content->text .= '<input type="text" name="username" id="login_username" ';
|
||||
$this->content->text .= ' class="form-control" value="'.s($username).'" autocomplete="username"/></div>';
|
||||
|
||||
$this->content->text .= '<div class="form-group"><label for="login_password">'.get_string('password').'</label>';
|
||||
|
||||
$this->content->text .= '<input type="password" name="password" id="login_password" class="form-control" value="" /></div>';
|
||||
$this->content->text .= '<input type="password" name="password" id="login_password" ';
|
||||
$this->content->text .= ' class="form-control" value="" autocomplete="current-password"/>';
|
||||
$this->content->text .= '</div>';
|
||||
|
||||
if (isset($CFG->rememberusername) and $CFG->rememberusername == 2) {
|
||||
$checked = $username ? 'checked="checked"' : '';
|
||||
|
||||
@@ -396,13 +396,13 @@ class block_base {
|
||||
function html_attributes() {
|
||||
$attributes = array(
|
||||
'id' => 'inst' . $this->instance->id,
|
||||
'class' => 'block_' . $this->name(). ' block',
|
||||
'class' => 'block_' . $this->name() . ' block',
|
||||
'role' => $this->get_aria_role()
|
||||
);
|
||||
if ($this->hide_header()) {
|
||||
$attributes['class'] .= ' no-header';
|
||||
}
|
||||
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_'.$this->instance->id, 0)) {
|
||||
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_' . $this->instance->id, 0)) {
|
||||
$attributes['class'] .= ' dock_on_load';
|
||||
}
|
||||
return $attributes;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
$string['everything'] = 'Everything';
|
||||
$string['courses'] = 'Categories and courses';
|
||||
$string['coursestructures'] = 'Categories, courses, and course structures';
|
||||
$string['courseactivities'] = 'Categories, courses, and course Activities';
|
||||
$string['courseactivities'] = 'Categories, courses, and course activities';
|
||||
$string['enabledockdesc'] = 'Allow the user to dock this block';
|
||||
$string['expansionlimit'] = 'Generate navigation for the following';
|
||||
$string['linkcategoriesdesc'] = 'Display categories as links';
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
M.block_private_files = {};
|
||||
|
||||
M.block_private_files.init_tree = function(Y, expand_all, htmlid) {
|
||||
Y.use('yui2-treeview', function(Y) {
|
||||
Y.use('yui2-treeview', 'node-event-simulate', function(Y) {
|
||||
var tree = new Y.YUI2.widget.TreeView(htmlid);
|
||||
|
||||
tree.subscribe("clickEvent", function(node, event) {
|
||||
@@ -32,6 +32,12 @@ M.block_private_files.init_tree = function(Y, expand_all, htmlid) {
|
||||
return false;
|
||||
});
|
||||
|
||||
tree.subscribe("enterKeyPressed", function(node) {
|
||||
// We want keyboard activation to trigger a click on the first link.
|
||||
Y.one(node.getContentEl()).one('a').simulate('click');
|
||||
return false;
|
||||
});
|
||||
|
||||
if (expand_all) {
|
||||
tree.expandAll();
|
||||
}
|
||||
|
||||
Vendored
+8
-4
@@ -1284,6 +1284,13 @@ class cache implements cache_loader {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may support purging cache of all data belonging to the
|
||||
* current user.
|
||||
*/
|
||||
public function purge_current_user() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1712,6 +1719,7 @@ class cache_session extends cache {
|
||||
public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
|
||||
// First up copy the loadeduserid to the current user id.
|
||||
$this->currentuserid = self::$loadeduserid;
|
||||
$this->set_session_id();
|
||||
parent::__construct($definition, $store, $loader);
|
||||
|
||||
// This will trigger check tracked user. If this gets removed a call to that will need to be added here in its place.
|
||||
@@ -1771,8 +1779,6 @@ class cache_session extends cache {
|
||||
// Purge the data we have for the old user.
|
||||
// This way we don't bloat the session.
|
||||
$this->purge();
|
||||
// Update the session id just in case!
|
||||
$this->set_session_id();
|
||||
}
|
||||
self::$loadeduserid = $new;
|
||||
$this->currentuserid = $new;
|
||||
@@ -1780,8 +1786,6 @@ class cache_session extends cache {
|
||||
// The current user matches the loaded user but not the user last used by this cache.
|
||||
$this->purge_current_user();
|
||||
$this->currentuserid = $new;
|
||||
// Update the session id just in case!
|
||||
$this->set_session_id();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+47
@@ -2327,4 +2327,51 @@ class core_cache_testcase extends advanced_testcase {
|
||||
$this->assertEquals('test data 2', $cache->get('testkey1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that values set in different sessions are stored with different key prefixes.
|
||||
*/
|
||||
public function test_session_distinct_storage_key() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Prepare a dummy session cache configuration.
|
||||
$config = cache_config_testing::instance();
|
||||
$config->phpunit_add_definition('phpunit/test_session_distinct_storage_key', array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'test_session_distinct_storage_key'
|
||||
));
|
||||
|
||||
// First anonymous user's session cache.
|
||||
cache_phpunit_session::phpunit_mockup_session_id('foo');
|
||||
$this->setUser(0);
|
||||
$cache1 = cache::make('phpunit', 'test_session_distinct_storage_key');
|
||||
|
||||
// Reset cache instances to emulate a new request.
|
||||
cache_factory::instance()->reset_cache_instances();
|
||||
|
||||
// Another anonymous user's session cache.
|
||||
cache_phpunit_session::phpunit_mockup_session_id('bar');
|
||||
$this->setUser(0);
|
||||
$cache2 = cache::make('phpunit', 'test_session_distinct_storage_key');
|
||||
|
||||
cache_factory::instance()->reset_cache_instances();
|
||||
|
||||
// Guest user's session cache.
|
||||
cache_phpunit_session::phpunit_mockup_session_id('baz');
|
||||
$this->setGuestUser();
|
||||
$cache3 = cache::make('phpunit', 'test_session_distinct_storage_key');
|
||||
|
||||
cache_factory::instance()->reset_cache_instances();
|
||||
|
||||
// Same guest user's session cache but in another browser window.
|
||||
cache_phpunit_session::phpunit_mockup_session_id('baz');
|
||||
$this->setGuestUser();
|
||||
$cache4 = cache::make('phpunit', 'test_session_distinct_storage_key');
|
||||
|
||||
// Assert that different PHP session implies different key prefix for storing values.
|
||||
$this->assertNotEquals($cache1->phpunit_get_key_prefix(), $cache2->phpunit_get_key_prefix());
|
||||
|
||||
// Assert that same PHP session implies same key prefix for storing values.
|
||||
$this->assertEquals($cache3->phpunit_get_key_prefix(), $cache4->phpunit_get_key_prefix());
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+28
@@ -465,6 +465,9 @@ class cache_phpunit_application extends cache_application {
|
||||
*/
|
||||
class cache_phpunit_session extends cache_session {
|
||||
|
||||
/** @var Static member used for emulating the behaviour of session_id() during the tests. */
|
||||
protected static $sessionidmockup = 'phpunitmockupsessionid';
|
||||
|
||||
/**
|
||||
* Returns the class of the store immediately associated with this cache.
|
||||
* @return string
|
||||
@@ -480,6 +483,31 @@ class cache_phpunit_session extends cache_session {
|
||||
public function phpunit_get_store_implements() {
|
||||
return class_implements($this->get_store());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to the {@link cache_session::get_key_prefix()} method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function phpunit_get_key_prefix() {
|
||||
return $this->get_key_prefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to inject the session identifier.
|
||||
*
|
||||
* @param string $sessionid
|
||||
*/
|
||||
public static function phpunit_mockup_session_id($sessionid) {
|
||||
static::$sessionidmockup = $sessionid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the parent behaviour so that it does not need the actual session_id() call.
|
||||
*/
|
||||
protected function set_session_id() {
|
||||
$this->sessionid = static::$sessionidmockup;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,7 @@ class core_calendar_export_form extends moodleform {
|
||||
|
||||
$export = array();
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsall', 'calendar'), 'all');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocategories', 'calendar'), 'categories');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocourses', 'calendar'), 'courses');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtogroups', 'calendar'), 'groups');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventspersonal', 'calendar'), 'user');
|
||||
|
||||
@@ -531,8 +531,7 @@ class provider implements
|
||||
AND ctx.contextlevel = :categorycontext)
|
||||
OR (e.courseid = ctx.instanceid
|
||||
AND (e.eventtype = 'course'
|
||||
OR e.eventtype = 'group'
|
||||
OR e.modulename != '0')
|
||||
OR e.eventtype = 'group')
|
||||
AND ctx.contextlevel = :coursecontext)
|
||||
OR (e.userid = ctx.instanceid
|
||||
AND e.eventtype = 'user'
|
||||
|
||||
@@ -40,8 +40,8 @@ $time = optional_param('preset_time', 'weeknow', PARAM_ALPHA);
|
||||
$now = $calendartype->timestamp_to_date_array(time());
|
||||
|
||||
// Let's see if we have sufficient and correct data
|
||||
$allowed_what = array('all', 'user', 'groups', 'courses');
|
||||
$allowed_time = array('weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming', 'custom');
|
||||
$allowedwhat = ['all', 'user', 'groups', 'courses', 'categories'];
|
||||
$allowedtime = ['weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming', 'custom'];
|
||||
|
||||
if (!empty($generateurl)) {
|
||||
$authtoken = sha1($user->id . $user->password . $CFG->calendar_exportsalt);
|
||||
@@ -56,9 +56,9 @@ if (!empty($generateurl)) {
|
||||
redirect($link->out());
|
||||
die;
|
||||
}
|
||||
|
||||
$paramcategory = false;
|
||||
if(!empty($what) && !empty($time)) {
|
||||
if(in_array($what, $allowed_what) && in_array($time, $allowed_time)) {
|
||||
if(in_array($what, $allowedwhat) && in_array($time, $allowedtime)) {
|
||||
$courses = enrol_get_users_courses($user->id, true, 'id, visible, shortname');
|
||||
// Array of courses that we will pass to calendar_get_legacy_events() which
|
||||
// is initially set to the list of the user's courses.
|
||||
@@ -78,6 +78,7 @@ if(!empty($what) && !empty($time)) {
|
||||
$courses[SITEID] = new stdClass;
|
||||
$courses[SITEID]->shortname = get_string('globalevents', 'calendar');
|
||||
$paramcourses[SITEID] = $courses[SITEID];
|
||||
$paramcategory = true;
|
||||
} else if ($what == 'groups') {
|
||||
$users = false;
|
||||
$paramcourses = array();
|
||||
@@ -85,6 +86,11 @@ if(!empty($what) && !empty($time)) {
|
||||
$users = $user->id;
|
||||
$groups = false;
|
||||
$paramcourses = array();
|
||||
} else if ($what == 'categories') {
|
||||
$users = $user->id;
|
||||
$groups = false;
|
||||
$paramcourses = array();
|
||||
$paramcategory = true;
|
||||
} else {
|
||||
$users = false;
|
||||
$groups = false;
|
||||
@@ -180,7 +186,9 @@ if(!empty($what) && !empty($time)) {
|
||||
die();
|
||||
}
|
||||
}
|
||||
$events = calendar_get_legacy_events($timestart, $timeend, $users, $groups, array_keys($paramcourses), false);
|
||||
|
||||
$events = calendar_get_legacy_events($timestart, $timeend, $users, $groups, array_keys($paramcourses), false, true,
|
||||
$paramcategory);
|
||||
|
||||
$ical = new iCalendar;
|
||||
$ical->add_property('method', 'PUBLISH');
|
||||
|
||||
@@ -3582,6 +3582,11 @@ function calendar_output_fragment_event_form($args) {
|
||||
$mform->set_data($data);
|
||||
} else {
|
||||
$event = calendar_event::load($eventid);
|
||||
|
||||
if (!calendar_edit_event_allowed($event)) {
|
||||
print_error('nopermissiontoupdatecalendar');
|
||||
}
|
||||
|
||||
$mapper = new \core_calendar\local\event\mappers\create_update_form_mapper();
|
||||
$eventdata = $mapper->from_legacy_event_to_data($event);
|
||||
$data = array_merge((array) $eventdata, $data);
|
||||
|
||||
@@ -48,6 +48,14 @@ Feature: Export calendar events
|
||||
When I click on "Get calendar URL" "button"
|
||||
Then I should see "&preset_what=groups&"
|
||||
|
||||
Scenario: Generating calendar URL for category events
|
||||
Given I follow "This month"
|
||||
And I click on "Export calendar" "button"
|
||||
And I set the field "Events related to categories" to "1"
|
||||
And I set the field "Recent and next 60 days" to "1"
|
||||
When I click on "Get calendar URL" "button"
|
||||
Then I should see "&preset_what=categories&"
|
||||
|
||||
Scenario: Generating calendar URL for user events
|
||||
Given I follow "This month"
|
||||
And I click on "Export calendar" "button"
|
||||
|
||||
@@ -365,12 +365,23 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
// Delete all Calendar Events for all Users by Context for Course 2.
|
||||
provider::delete_data_for_all_users_in_context($course2context);
|
||||
|
||||
// Verify all Calendar Events for Course 2 were deleted.
|
||||
$events = $DB->get_records('event', array('courseid' => $course2->id));
|
||||
// Verify all Calendar Events for Course 2 context were deleted.
|
||||
$events = $DB->get_records('event', array('courseid' => $course2->id, 'modulename' => '0'));
|
||||
$this->assertCount(0, $events);
|
||||
// Verify all Calendar Subscriptions for Course 2 were deleted.
|
||||
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course2->id));
|
||||
$this->assertCount(0, $subscriptions);
|
||||
|
||||
// Verify all Calendar Events for the assignment exists still.
|
||||
$events = $DB->get_records('event', array('modulename' => 'assign'));
|
||||
$this->assertCount(2, $events);
|
||||
|
||||
// Delete all Calendar Events for all Users by Context for the assignment.
|
||||
provider::delete_data_for_all_users_in_context($modulecontext);
|
||||
|
||||
// Verify all Calendar Events for the assignment context were deleted.
|
||||
$events = $DB->get_records('event', array('modulename' => 'assign'));
|
||||
$this->assertCount(0, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,9 +18,11 @@ Feature: Upload users to a cohort
|
||||
And I am on "Course 1" course homepage
|
||||
And I add "Cohort sync" enrolment method with:
|
||||
| Cohort | Cohort 1 |
|
||||
And I should see "Cohort sync (Cohort 1 - Student)"
|
||||
And I am on "Course 2" course homepage
|
||||
And I add "Cohort sync" enrolment method with:
|
||||
| Cohort | Cohort 2 |
|
||||
And I should see "Cohort sync (Cohort 2 - Student)"
|
||||
When I navigate to "Upload users" node in "Site administration > Users > Accounts"
|
||||
And I upload "lib/tests/fixtures/upload_users_cohorts.csv" file to "File" filemanager
|
||||
And I press "Upload users"
|
||||
|
||||
+2
-7
@@ -28,7 +28,6 @@ require_once($CFG->dirroot.'/cohort/upload_form.php');
|
||||
require_once($CFG->libdir . '/csvlib.class.php');
|
||||
|
||||
$contextid = optional_param('contextid', 0, PARAM_INT);
|
||||
$returnurl = optional_param('returnurl', '', PARAM_URL);
|
||||
|
||||
require_login();
|
||||
|
||||
@@ -56,13 +55,9 @@ if ($context->contextlevel == CONTEXT_COURSECAT) {
|
||||
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
|
||||
}
|
||||
|
||||
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id, 'returnurl' => $returnurl));
|
||||
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id));
|
||||
|
||||
if ($returnurl) {
|
||||
$returnurl = new moodle_url($returnurl);
|
||||
} else {
|
||||
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
|
||||
}
|
||||
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
|
||||
|
||||
if ($uploadform->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
|
||||
@@ -48,9 +48,6 @@ class cohort_upload_form extends moodleform {
|
||||
$mform = $this->_form;
|
||||
$data = (object)$this->_customdata;
|
||||
|
||||
$mform->addElement('hidden', 'returnurl');
|
||||
$mform->setType('returnurl', PARAM_URL);
|
||||
|
||||
$mform->addElement('header', 'cohortfileuploadform', get_string('uploadafile'));
|
||||
|
||||
$filepickeroptions = array();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user