Compare commits
422 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aba54964ab | |||
| 5f84553fab | |||
| f6b0de1361 | |||
| 20e9dac8cb | |||
| 71c7374d5d | |||
| 897f31a5d0 | |||
| 12c2857486 | |||
| fada069133 | |||
| 7873e36f0c | |||
| 6336c56259 | |||
| aca2ff0ad1 | |||
| 33a358331f | |||
| 28908d2d6a | |||
| fc8c321aa9 | |||
| 5b4be737f9 | |||
| 0e38101745 | |||
| 54196b7acd | |||
| 89724d0c1c | |||
| 8bc54c07a2 | |||
| 7c54533cdc | |||
| 34e8b2dbd0 | |||
| d4f2c87bca | |||
| 2a6ff0c2be | |||
| 685a238e8d | |||
| c9e71927d4 | |||
| 19eb7b80fa | |||
| df74d3d557 | |||
| 3ed9d1c176 | |||
| 488087cb69 | |||
| 4e5732e7fe | |||
| d9d8e9c3fe | |||
| 4ee7394c8b | |||
| a0cd21cd5c | |||
| a0034bb017 | |||
| 89b97390d0 | |||
| 190757854d | |||
| dc8421575f | |||
| 0f989a4c70 | |||
| b74d0f8404 | |||
| 71beedee8c | |||
| d63ac148b9 | |||
| 8f95eac163 | |||
| 8e12ce7e28 | |||
| 3703791954 | |||
| a457f23484 | |||
| 608beccda3 | |||
| 51eb1032de | |||
| b7d5dffc77 | |||
| d3142bcdbe | |||
| 75066aabb0 | |||
| 16fce86c8b | |||
| 22277cfabf | |||
| 02d953d1b2 | |||
| 03fb8a8769 | |||
| 962bf40a1e | |||
| 00c165d8f3 | |||
| 54ba33e91b | |||
| e96ea8d846 | |||
| 3edce27167 | |||
| ac60c71cb6 | |||
| 63e9112aec | |||
| 2e8e92b0f1 | |||
| cd654c06a7 | |||
| b25f428173 | |||
| 535fd9678f | |||
| 627e723096 | |||
| 81f444886a | |||
| fe3db63c77 | |||
| 0b08576ea7 | |||
| 71d630fd59 | |||
| ae9a34b2d4 | |||
| e0db7da9e2 | |||
| 32ea431a2f | |||
| 56068f1aeb | |||
| bd696650b7 | |||
| 9157b95733 | |||
| 025ed8a332 | |||
| 397f50e376 | |||
| 4a702ed500 | |||
| b6e2489fd3 | |||
| fc542633e8 | |||
| f988f63bc6 | |||
| f0f739f4be | |||
| df2527eb4a | |||
| ade73d44bf | |||
| 0fae5133a0 | |||
| ff65861526 | |||
| 7920d1f08b | |||
| 6193543174 | |||
| 097fe73bab | |||
| b4f7f20a74 | |||
| 9856eadb1b | |||
| 027baac82e | |||
| 8102ded93a | |||
| 7cd705334e | |||
| 86eaf83174 | |||
| e8f0c133eb | |||
| 8535cc180e | |||
| e74ff55ff5 | |||
| 5d2e3c1b97 | |||
| 120b3ad637 | |||
| ed611437a6 | |||
| c1c1dc7a58 | |||
| 7511a99d0b | |||
| 3afbdcb211 | |||
| 4d47398bdc | |||
| f8c0d18110 | |||
| d84ddfda00 | |||
| ae1854f55c | |||
| 394cba177e | |||
| 9d9cfb8736 | |||
| 8e2cd6b91a | |||
| 6b9c205a99 | |||
| 512633461a | |||
| 61ecc0d5f0 | |||
| 02d8c8ca39 | |||
| 11d4f442bb | |||
| 5c16db4fc5 | |||
| c03882584d | |||
| 720d88779e | |||
| 5dd381d25c | |||
| cb2220bfde | |||
| c9d8b0d29a | |||
| 29d6115e65 | |||
| 2dffff79c7 | |||
| 9be0bf1d10 | |||
| 9ba8006955 | |||
| fe9bd2b8bb | |||
| d7b80c7dd4 | |||
| 57d281f554 | |||
| d41fa94a69 | |||
| d28eedd536 | |||
| 72885a5725 | |||
| 7bf5c6a542 | |||
| b4f4232e1c | |||
| f03ec4ce85 | |||
| 50632a0681 | |||
| dcb42c9ed1 | |||
| 7db05ae3b2 | |||
| 8e093acc9d | |||
| 5bd1977645 | |||
| 63d70b1eca | |||
| 2c6d1dd02d | |||
| 5c31064d5e | |||
| 6dfb7f0eca | |||
| 80ef82e3f3 | |||
| 336f2e073b | |||
| 27f5d818de | |||
| c2da1ffea9 | |||
| 7f50bc1d2f | |||
| 12f93caf45 | |||
| 170bb2e118 | |||
| 56272bb56d | |||
| 992591e0e1 | |||
| efb26ed011 | |||
| 3c5a90d52d | |||
| fc3ef232d1 | |||
| b4ccb13855 | |||
| 9e0a879c0a | |||
| ca2d074d95 | |||
| 5a22246905 | |||
| 262289493d | |||
| 1dcf6b339a | |||
| 6dce2f64b8 | |||
| 29f19e7e3e | |||
| b43abbdccc | |||
| 1af0fa4d1c | |||
| 1a5b8567c8 | |||
| c12979c7df | |||
| 1d8090a7a6 | |||
| 318f7454e0 | |||
| 2d0a9d0255 | |||
| 15dc45e953 | |||
| eb1e78be45 | |||
| 9323277c03 | |||
| 017c71cfcf | |||
| c07b265f77 | |||
| 4b573f4d42 | |||
| ba7fcebb79 | |||
| 53378d63e2 | |||
| 2ff58a3b05 | |||
| 7fa0d47cda | |||
| 6f3a4fb5c3 | |||
| 9486c3fe5c | |||
| 5daffb32f3 | |||
| 3561600a37 | |||
| d784ad13e8 | |||
| a6cb45542d | |||
| c4350c40f5 | |||
| cbb94a638e | |||
| 7eb88da0af | |||
| 5631e50ced | |||
| 23eb923bcc | |||
| 80bea94ffe | |||
| 1430b4dc8f | |||
| 388efc9233 | |||
| 9383deb075 | |||
| 102ffb8e18 | |||
| 37ccc13b0f | |||
| 6cf4677e78 | |||
| e5ec55cbdd | |||
| 4ae6340719 | |||
| 140bd7b3ca | |||
| 1b851d1b1f | |||
| 99482eb0bc | |||
| 2ed5192a84 | |||
| e1f8a35f27 | |||
| 03f0dec4a4 | |||
| 2ecb8fdea7 | |||
| 200522e7c6 | |||
| 131679eddc | |||
| fb33a080dc | |||
| 61ba945d8d | |||
| 0429a76370 | |||
| 0f0f997e61 | |||
| e82ed9333b | |||
| 52b58e7cea | |||
| dff755c3e2 | |||
| 70f315edbc | |||
| d1bffa672f | |||
| 764057b0c3 | |||
| 2aea111faa | |||
| db4c084a63 | |||
| ec7af5aa0e | |||
| 60e3abff94 | |||
| 5e90e70643 | |||
| 62897237f7 | |||
| d55e3b1fc5 | |||
| a6e1c0afa7 | |||
| 9a9bfb41e2 | |||
| 19ab1865e3 | |||
| d058095357 | |||
| f7393e447c | |||
| ae4e4e11ff | |||
| 0da8db7426 | |||
| 2b6e9e40dc | |||
| 5f21490c11 | |||
| 2148eb566c | |||
| 2b18ca5e3f | |||
| 45d6ea0832 | |||
| a8f9db70c6 | |||
| a7f765a7a9 | |||
| 206e677358 | |||
| 2d4cf584ca | |||
| f58afe07e0 | |||
| c07f0799ed | |||
| 7914b0a2cf | |||
| 1cf1b279da | |||
| 182a393e06 | |||
| ee8072e2a1 | |||
| bf0e51d1a1 | |||
| 378f34a469 | |||
| 2c9c7b1130 | |||
| a78d3fa10b | |||
| 43654c4872 | |||
| 989298d309 | |||
| 8eb8681b5e | |||
| 025b4cb5e9 | |||
| 0273933079 | |||
| 28e4326cf1 | |||
| fb48641b5f | |||
| 907093e131 | |||
| ce4c237787 | |||
| 284ef7be8f | |||
| cc595febe4 | |||
| 6ff47714ac | |||
| c3ece30f18 | |||
| 1670fc84a7 | |||
| b961b1c142 | |||
| d6e6b308c5 | |||
| 66ce9e9f1c | |||
| c21fca27fd | |||
| 9b132c5b23 | |||
| 16ef0ac033 | |||
| cce2034d5e | |||
| f1afb82337 | |||
| 8ffc94ff69 | |||
| 545163440b | |||
| 5c66e986ee | |||
| e4f2fb2665 | |||
| 6d8dbfee53 | |||
| 41c8edf46d | |||
| 287e6a9772 | |||
| 735ef8ba92 | |||
| a112d4ebfb | |||
| 797ac16e88 | |||
| 1ce643eaad | |||
| 936d11f137 | |||
| c4dc0748a4 | |||
| cee7167b07 | |||
| e8da65449a | |||
| 77712faa11 | |||
| 71175baff9 | |||
| 6641b6fb8a | |||
| 4fbad85763 | |||
| e33152d912 | |||
| 758b2d9b29 | |||
| b5b3a37049 | |||
| 06cb6e421b | |||
| 08544e3bdb | |||
| 192509762b | |||
| f2ac95dfdf | |||
| f0d7264769 | |||
| 36d44b206a | |||
| 2bec647d91 | |||
| dc3850fc20 | |||
| 35f4f19770 | |||
| 3262330d95 | |||
| 00f1a102ca | |||
| 474b1e0040 | |||
| 68dcf2dded | |||
| 9194f5911b | |||
| 4143c33d27 | |||
| ccefec7360 | |||
| b68783c870 | |||
| 9c46e281cb | |||
| 773582db11 | |||
| 964c02c3fe | |||
| 868fb3adf9 | |||
| 0b54d6d971 | |||
| a6bd6b3cad | |||
| 46067fbd30 | |||
| acd86d0cf2 | |||
| 365644eecf | |||
| 51d6415ef3 | |||
| c022a86c38 | |||
| fa3652ef9c | |||
| 08db4b5843 | |||
| 3497350e2f | |||
| 396f954b05 | |||
| 997e7a966b | |||
| 94249aa198 | |||
| 1916b2863c | |||
| 0c2ddb687a | |||
| 8ae33bc947 | |||
| 8589530727 | |||
| b9e92d05e1 | |||
| 45e591a85a | |||
| a59f24b904 | |||
| 58d7de73ac | |||
| e87c74e9f5 | |||
| d8edd556ab | |||
| 68629452fa | |||
| 3377cbc3ba | |||
| 07f3c02ec7 | |||
| 837c4dab86 | |||
| 551dce86ba | |||
| f7666835c7 | |||
| f9dfe738aa | |||
| 3bf8439e3a | |||
| c6a6ce7914 | |||
| 599eeea2a5 | |||
| bfd6727b42 | |||
| 606cf274a7 | |||
| 0dbd2594e8 | |||
| 0c73e5335c | |||
| 18fd195086 | |||
| 05c252e9b0 | |||
| a23c44cddf | |||
| f8e0b4ece1 | |||
| 76ebf76be1 | |||
| a91caeefc7 | |||
| e1efcbd1db | |||
| 210349f647 | |||
| 193f5d4029 | |||
| 489db61206 | |||
| 685aa0b7c9 | |||
| 57caaace44 | |||
| 42ba6f011e | |||
| 8ada99201a | |||
| 2fab7166b5 | |||
| f27c022e5c | |||
| 16eedce0c6 | |||
| 5d4bccd775 | |||
| 2648522e3d | |||
| e13126f85a | |||
| 1237ba1962 | |||
| 3e08b5c04e | |||
| 978ef29e47 | |||
| 5c03acf518 | |||
| 8d5092cd84 | |||
| add6322e5a | |||
| c87b932f1c | |||
| 78eb3ee407 | |||
| 38e0d68f0a | |||
| 6c974ef376 | |||
| 17c975874f | |||
| 07d52ad818 | |||
| 3f121ac514 | |||
| eddc63f4e8 | |||
| 3f72e79ee1 | |||
| 000be9c016 | |||
| 8a6c5244c4 | |||
| 5319119569 | |||
| 6dd7f1d452 | |||
| 59d26c45d9 | |||
| 596950852c | |||
| 6deeba6bcc | |||
| 85cf55ebaf | |||
| a03a01f2ad | |||
| 80764a301f | |||
| 596e4603ae | |||
| 355e943c4b | |||
| 8102007398 | |||
| d881f12cde | |||
| 7b027ea79c | |||
| adb65a80f9 | |||
| f558ddcc4f | |||
| f19717481d | |||
| 8f4379b3d5 | |||
| dbeeebc0f6 | |||
| e4d7edbf4f | |||
| 0e0c8c0dd7 | |||
| f44ac5d3bf | |||
| f3661a7ea8 | |||
| fa36f1d1ab | |||
| 235d8a4196 | |||
| 12259f01ce | |||
| 82c123c722 | |||
| 981379ffc2 | |||
| 61662e6896 | |||
| 5d9bc0acc3 |
@@ -64,6 +64,8 @@ echo "<form id=\"authmenu\" method=\"post\" action=\"auth_config.php\">\n";
|
||||
echo "<div>\n";
|
||||
echo "<input type=\"hidden\" name=\"sesskey\" value=\"".sesskey()."\" />\n";
|
||||
echo "<input type=\"hidden\" name=\"auth\" value=\"".$auth."\" />\n";
|
||||
// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
|
||||
echo prevent_form_autofill_password();
|
||||
|
||||
// auth plugin description
|
||||
echo $OUTPUT->box_start();
|
||||
@@ -146,8 +148,8 @@ function print_auth_lock_options($auth, $user_fields, $helptext, $retrieveopts,
|
||||
// If custom field then pick name from database.
|
||||
$fieldshortname = str_replace('profile_field_', '', $fieldname);
|
||||
$fieldname = $customfieldname[$fieldshortname]->name;
|
||||
} elseif (preg_match('/^(.+?)(\d+)$/', $fieldname, $matches)) {
|
||||
$fieldname = get_string($matches[1]) . ' ' . $matches[2];
|
||||
} elseif ($fieldname == 'phone1') {
|
||||
$fieldname = get_string('phone');
|
||||
} elseif ($fieldname == 'url') {
|
||||
$fieldname = get_string('webpage');
|
||||
} else {
|
||||
|
||||
@@ -1420,4 +1420,282 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.0" requires="2.2">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="5.5.31" />
|
||||
<VENDOR name="mysql" version="5.5.31" />
|
||||
<VENDOR name="postgres" version="9.1" />
|
||||
<VENDOR name="mssql" version="10.0" />
|
||||
<VENDOR name="oracle" version="10.2" />
|
||||
</DATABASE>
|
||||
<PHP version="5.4.4" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="mbstringrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opensslrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="intlrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.1" requires="2.7">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="5.5.31" />
|
||||
<VENDOR name="mysql" version="5.5.31" />
|
||||
<VENDOR name="postgres" version="9.1" />
|
||||
<VENDOR name="mssql" version="10.0" />
|
||||
<VENDOR name="oracle" version="10.2" />
|
||||
</DATABASE>
|
||||
<PHP version="5.4.4" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="mbstringrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opensslrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="intlrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -39,6 +39,8 @@ require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/forms.php');
|
||||
require_once($CFG->dirroot . '/webservice/lib.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
|
||||
|
||||
require_sesskey();
|
||||
|
||||
$huburl = required_param('huburl', PARAM_URL);
|
||||
$huburl = rtrim($huburl, "/");
|
||||
|
||||
|
||||
+6
-2
@@ -50,7 +50,8 @@ $table->set_attribute('class', 'admintable generaltable');
|
||||
$table->setup();
|
||||
|
||||
$plugins = array();
|
||||
foreach (core_component::get_plugin_list('report') as $plugin => $plugindir) {
|
||||
$availableplugins = core_component::get_plugin_list('report');
|
||||
foreach ($availableplugins as $plugin => $plugindir) {
|
||||
if (get_string_manager()->string_exists('pluginname', 'report_' . $plugin)) {
|
||||
$strpluginname = get_string('pluginname', 'report_' . $plugin);
|
||||
} else {
|
||||
@@ -80,7 +81,10 @@ foreach ($plugins as $plugin => $name) {
|
||||
$uninstall = html_writer::link($uninstallurl, $struninstall);
|
||||
}
|
||||
|
||||
$stores = $logmanager->get_supported_logstores('report_' . $plugin);
|
||||
$stores = array();
|
||||
if (isset($availableplugins[$plugin])) {
|
||||
$stores = $logmanager->get_supported_logstores('report_' . $plugin);
|
||||
}
|
||||
if ($stores === false) {
|
||||
$supportedstores = get_string('logstorenotrequired', 'admin');
|
||||
} else if (!empty($stores)) {
|
||||
|
||||
@@ -64,7 +64,8 @@ mybadges,badges|/badges/mybadges.php|award',
|
||||
array('0' => new lang_string('default', 'calendar'),
|
||||
'%I:%M %p' => new lang_string('timeformat_12', 'calendar'),
|
||||
'%H:%M' => new lang_string('timeformat_24', 'calendar'))));
|
||||
$temp->add(new admin_setting_configselect('calendar_startwday', new lang_string('configstartwday', 'admin'), new lang_string('helpstartofweek', 'admin'), 0,
|
||||
$temp->add(new admin_setting_configselect('calendar_startwday', new lang_string('configstartwday', 'admin'),
|
||||
new lang_string('helpstartofweek', 'admin'), get_string('firstdayofweek', 'langconfig'),
|
||||
array(
|
||||
0 => new lang_string('sunday', 'calendar'),
|
||||
1 => new lang_string('monday', 'calendar'),
|
||||
|
||||
@@ -35,7 +35,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$params = new stdClass();
|
||||
$params->bytes = $defaultuserquota;
|
||||
$params->displaysize = display_size($defaultuserquota);
|
||||
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'), new lang_string('configuserquota', 'admin', $params), $defaultuserquota));
|
||||
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
|
||||
new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
|
||||
|
||||
@@ -11,7 +11,7 @@ $hassiteconfig = has_capability('moodle/site:config', $systemcontext);
|
||||
$ADMIN->add('root', new admin_externalpage('adminnotifications', new lang_string('notifications'), "$CFG->wwwroot/$CFG->admin/index.php"));
|
||||
|
||||
$ADMIN->add('root', new admin_externalpage('registrationmoodleorg', new lang_string('registration', 'admin'),
|
||||
"$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org"));
|
||||
"$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org&sesskey=" . sesskey()));
|
||||
$ADMIN->add('root', new admin_externalpage('registrationhub', new lang_string('registerwith', 'hub'),
|
||||
"$CFG->wwwroot/$CFG->admin/registration/register.php", 'moodle/site:config', true));
|
||||
$ADMIN->add('root', new admin_externalpage('registrationhubs', new lang_string('hubs', 'admin'),
|
||||
|
||||
@@ -137,6 +137,17 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I press "Save my choice"
|
||||
And the field "one" matches value "1"
|
||||
And the field "two" matches value ""
|
||||
# Check if field xpath set/match works.
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Edit settings" node in "Course administration"
|
||||
And I set the field with xpath "//input[@id='id_idnumber']" to "Course id number"
|
||||
And the field with xpath "//input[@name='idnumber']" matches value "Course id number"
|
||||
And the field with xpath "//input[@name='idnumber']" does not match value ""
|
||||
And I press "Save changes"
|
||||
And I navigate to "Edit settings" node in "Course administration"
|
||||
And the field "Course ID number" matches value "Course id number"
|
||||
|
||||
|
||||
Scenario: with JS disabled all form fields getters and setters works as expected
|
||||
|
||||
|
||||
@@ -208,10 +208,13 @@ class tool_generator_course_backend extends tool_generator_backend {
|
||||
*/
|
||||
private function create_course() {
|
||||
$this->log('createcourse', $this->shortname);
|
||||
$courserecord = array('shortname' => $this->shortname,
|
||||
'fullname' => get_string('fullname', 'tool_generator',
|
||||
array('size' => get_string('shortsize_' . $this->size, 'tool_generator'))),
|
||||
'numsections' => self::$paramsections[$this->size]);
|
||||
$courserecord = array(
|
||||
'shortname' => $this->shortname,
|
||||
'fullname' => get_string('fullname', 'tool_generator',
|
||||
array('size' => get_string('shortsize_' . $this->size, 'tool_generator'))),
|
||||
'numsections' => self::$paramsections[$this->size],
|
||||
'startdate' => usergetmidnight(time())
|
||||
);
|
||||
return $this->generator->create_course($courserecord, array('createsections' => true));
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,10 @@ class manager {
|
||||
try {
|
||||
$this->client->login();
|
||||
mtrace("Connection established.");
|
||||
|
||||
// Ensure that mailboxes exist.
|
||||
$this->ensure_mailboxes_exist();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Horde_Imap_Client_Exception $e) {
|
||||
@@ -735,6 +739,27 @@ class manager {
|
||||
return in_array($flag, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that all mailboxes exist.
|
||||
*/
|
||||
private function ensure_mailboxes_exist() {
|
||||
$requiredmailboxes = array(
|
||||
self::MAILBOX,
|
||||
self::CONFIRMATIONFOLDER,
|
||||
);
|
||||
|
||||
$existingmailboxes = $this->client->listMailboxes($requiredmailboxes);
|
||||
foreach ($requiredmailboxes as $mailbox) {
|
||||
if (isset($existingmailboxes[$mailbox])) {
|
||||
// This mailbox was found.
|
||||
continue;
|
||||
}
|
||||
|
||||
mtrace("Unable to find the '{$mailbox}' mailbox - creating it.");
|
||||
$this->client->createMailbox($mailbox);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to determine whether this message is a bulk message (e.g. automated reply).
|
||||
*
|
||||
|
||||
@@ -219,10 +219,12 @@ class rules extends \table_sql implements \renderable {
|
||||
if (has_capability('tool/monitor:subscribe', \context_system::instance())) {
|
||||
$options[0] = get_string('site');
|
||||
}
|
||||
if ($courses = get_user_capability_course('tool/monitor:subscribe', null, true, 'fullname', $orderby)) {
|
||||
if ($courses = get_user_capability_course('tool/monitor:subscribe', null, true, 'fullname, visible', $orderby)) {
|
||||
foreach ($courses as $course) {
|
||||
$options[$course->id] = format_string($course->fullname, true,
|
||||
array('context' => \context_course::instance($course->id)));
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
if ($course->visible || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
$options[$course->id] = format_string($course->fullname, true, array('context' => $coursecontext));
|
||||
}
|
||||
}
|
||||
}
|
||||
$url = new \moodle_url('/admin/tool/monitor/index.php');
|
||||
|
||||
+5
-1
@@ -78,8 +78,12 @@
|
||||
echo $OUTPUT->header();
|
||||
$fullname = fullname($user, true);
|
||||
echo $OUTPUT->heading(get_string('deleteuser', 'admin'));
|
||||
|
||||
$optionsyes = array('delete'=>$delete, 'confirm'=>md5($delete), 'sesskey'=>sesskey());
|
||||
echo $OUTPUT->confirm(get_string('deletecheckfull', '', "'$fullname'"), new moodle_url($returnurl, $optionsyes), $returnurl);
|
||||
$deleteurl = new moodle_url($returnurl, $optionsyes);
|
||||
$deletebutton = new single_button($deleteurl, get_string('delete'), 'post');
|
||||
|
||||
echo $OUTPUT->confirm(get_string('deletecheckfull', '', "'$fullname'"), $deletebutton, $returnurl);
|
||||
echo $OUTPUT->footer();
|
||||
die;
|
||||
} else if (data_submitted() and !$user->deleted) {
|
||||
|
||||
@@ -314,6 +314,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
$updateuser = new stdClass();
|
||||
$updateuser->id = $user->id;
|
||||
$updateuser->suspended = 1;
|
||||
$updateuser = $this->clean_data($updateuser);
|
||||
user_update_user($updateuser, false);
|
||||
$trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
|
||||
}
|
||||
@@ -400,6 +401,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
$updateuser = new stdClass();
|
||||
$updateuser->id = $olduser->id;
|
||||
$updateuser->suspended = 0;
|
||||
$updateuser = $this->clean_data($updateuser);
|
||||
user_update_user($updateuser);
|
||||
$trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username,
|
||||
'id' => $olduser->id)), 1);
|
||||
@@ -422,6 +424,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
$trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
|
||||
continue;
|
||||
}
|
||||
$user = $this->clean_data($user);
|
||||
try {
|
||||
$id = user_create_user($user, false); // It is truly a new user.
|
||||
$trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
|
||||
@@ -563,6 +566,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
}
|
||||
if ($needsupdate) {
|
||||
require_once($CFG->dirroot . '/user/lib.php');
|
||||
$updateuser = $this->clean_data($updateuser);
|
||||
user_update_user($updateuser);
|
||||
}
|
||||
return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
|
||||
@@ -888,6 +892,30 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
error_reporting($CFG->debug);
|
||||
ob_end_flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the user data that comes from an external database.
|
||||
*
|
||||
* @param array $user the user data to be validated against properties definition.
|
||||
* @return stdClass $user the cleaned user data.
|
||||
*/
|
||||
public function clean_data($user) {
|
||||
if (empty($user)) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
foreach ($user as $field => $value) {
|
||||
// Get the property parameter type and do the cleaning.
|
||||
try {
|
||||
$property = core_user::get_property_definition($field);
|
||||
$user->$field = clean_param($value, $property['type']);
|
||||
} catch (coding_exception $e) {
|
||||
debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -374,4 +374,77 @@ class auth_db_testcase extends advanced_testcase {
|
||||
|
||||
$this->cleanup_auth_database();
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing the clean_data() method.
|
||||
*/
|
||||
public function test_clean_data() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(false);
|
||||
$this->preventResetByRollback();
|
||||
$this->init_auth_database();
|
||||
$auth = get_auth_plugin('db');
|
||||
$auth->db_init();
|
||||
|
||||
// Create users on external table.
|
||||
$extdbuser1 = (object)array('name'=>'u1', 'pass'=>'heslo', 'email'=>'u1@example.com');
|
||||
$extdbuser1->id = $DB->insert_record('auth_db_users', $extdbuser1);
|
||||
|
||||
// User with malicious data on the name.
|
||||
$extdbuser2 = (object)array('name'=>'user<script>alert(1);</script>xss', 'pass'=>'heslo', 'email'=>'xssuser@example.com');
|
||||
$extdbuser2->id = $DB->insert_record('auth_db_users', $extdbuser2);
|
||||
|
||||
$trace = new null_progress_trace();
|
||||
|
||||
// Let's test user sync make sure still works as expected..
|
||||
$auth->sync_users($trace, true);
|
||||
|
||||
// Get the user on moodle user table.
|
||||
$user2 = $DB->get_record('user', array('email'=> $extdbuser2->email, 'auth'=>'db'));
|
||||
|
||||
// The malicious code should be sanitized.
|
||||
$this->assertEquals($user2->username, 'userscriptalert1scriptxss');
|
||||
$this->assertNotEquals($user2->username, $extdbuser2->name);
|
||||
|
||||
// User with correct data, should be equal to external db.
|
||||
$user1 = $DB->get_record('user', array('email'=> $extdbuser1->email, 'auth'=>'db'));
|
||||
$this->assertEquals($extdbuser1->name, $user1->username);
|
||||
$this->assertEquals($extdbuser1->email, $user1->email);
|
||||
|
||||
// Now, let's update the name.
|
||||
$extdbuser2->name = 'user no xss anymore';
|
||||
$DB->update_record('auth_db_users', $extdbuser2);
|
||||
|
||||
// Run sync again to update the user data.
|
||||
$auth->sync_users($trace, true);
|
||||
|
||||
// The user information should be updated.
|
||||
$user2 = $DB->get_record('user', array('username' => 'usernoxssanymore', 'auth' => 'db'));
|
||||
// The spaces should be removed, as it's the username.
|
||||
$this->assertEquals($user2->username, 'usernoxssanymore');
|
||||
|
||||
// Now let's test just the clean_data() method isolated.
|
||||
// Testing PARAM_USERNAME, PARAM_NOTAGS, PARAM_RAW_TRIMMED and others.
|
||||
$user3 = new stdClass();
|
||||
$user3->firstname = 'John <script>alert(1)</script> Doe';
|
||||
$user3->username = 'john%#&~%*_doe';
|
||||
$user3->email = ' john@testing.com ';
|
||||
$user3->deleted = 'no';
|
||||
$user3->description = '<b>A description about myself.</b>';
|
||||
$user3cleaned = $auth->clean_data($user3);
|
||||
|
||||
// Expected results.
|
||||
$this->assertEquals($user3cleaned->firstname, 'John alert(1) Doe');
|
||||
$this->assertEquals($user3cleaned->email, 'john@testing.com');
|
||||
$this->assertEquals($user3cleaned->deleted, 0);
|
||||
$this->assertEquals($user3->description, '<b>A description about myself.</b>');
|
||||
$this->assertEquals($user3->username, 'john_doe');
|
||||
|
||||
// Try to clean an invalid property (fullname).
|
||||
$user3->fullname = 'John Doe';
|
||||
$auth->clean_data($user3);
|
||||
$this->assertDebuggingCalled("The property 'fullname' could not be cleaned.");
|
||||
$this->cleanup_auth_database();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ $string['auth_ldap_expiration_desc'] = 'Select No to disable expired password ch
|
||||
$string['auth_ldap_expiration_key'] = 'Expiration';
|
||||
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiration warning is issued.';
|
||||
$string['auth_ldap_expiration_warning_key'] = 'Expiration warning';
|
||||
$string['auth_ldap_expireattr_desc'] = 'Optional: overrides ldap-attribute that stores password expiration time';
|
||||
$string['auth_ldap_expireattr_desc'] = 'Optional: Overrides the LDAP attribute that stores password expiration time.';
|
||||
$string['auth_ldap_expireattr_key'] = 'Expiration attribute';
|
||||
$string['auth_ldapextrafields'] = 'These fields are optional. You can choose to pre-fill some Moodle user fields with information from the <b>LDAP fields</b> that you specify here. <p>If you leave these fields blank, then nothing will be transferred from LDAP and Moodle defaults will be used instead.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
|
||||
$string['auth_ldap_graceattr_desc'] = 'Optional: Overrides gracelogin attribute';
|
||||
@@ -71,7 +71,7 @@ $string['auth_ldap_noconnect_all'] = 'LDAP-module cannot connect to any servers:
|
||||
$string['auth_ldap_noextension'] = '<em>The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this authentication plugin.</em>';
|
||||
$string['auth_ldap_no_mbstring'] = 'You need the mbstring extension to create users in Active Directory.';
|
||||
$string['auth_ldapnotinstalled'] = 'Cannot use LDAP authentication. The PHP LDAP module is not installed.';
|
||||
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you dont need to chage this.';
|
||||
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you don\'t need to change this.';
|
||||
$string['auth_ldap_objectclass_key'] = 'Object class';
|
||||
$string['auth_ldap_opt_deref'] = 'Determines how aliases are handled during search. Select one of the following values: "No" (LDAP_DEREF_NEVER) or "Yes" (LDAP_DEREF_ALWAYS)';
|
||||
$string['auth_ldap_opt_deref_key'] = 'Dereference aliases';
|
||||
|
||||
@@ -60,7 +60,7 @@ if (!isset($config->changepasswordurl)) {
|
||||
</tr>
|
||||
|
||||
<tr valign="top" >
|
||||
<td align="right"><?php echo html_writer::label(get_string('auth_radiustype_key', 'auth_radius'), 'menuradiustype'); ?>: </td>
|
||||
<td align="right"><?php echo html_writer::label(get_string('auth_radiustype_key', 'auth_radius') . ':', 'menuradiustype'); ?> </td>
|
||||
<td>
|
||||
<?php
|
||||
|
||||
|
||||
@@ -210,7 +210,8 @@ class auth_plugin_shibboleth extends auth_plugin_base {
|
||||
}
|
||||
|
||||
// Overwrite redirect in order to send user to Shibboleth logout page and let him return back
|
||||
$redirect = $this->config->logout_handler.'?return='.urlencode($temp_redirect);
|
||||
$redirecturl = new moodle_url($this->config->logout_handler, array('return' => $temp_redirect));
|
||||
$redirect = $redirecturl->out();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -328,7 +328,14 @@ class tree extends tree_node {
|
||||
// Loop through all valid children.
|
||||
foreach ($this->children as $index => $child) {
|
||||
if (!$child->is_applied_to_user_lists()) {
|
||||
continue;
|
||||
if ($andoperator) {
|
||||
continue;
|
||||
} else {
|
||||
// OR condition with one option that doesn't restrict user
|
||||
// lists = everyone is allowed.
|
||||
$anyconditions = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$childresult = $child->filter_user_list($users, $innernot, $info, $checker);
|
||||
if ($andoperator) {
|
||||
@@ -359,7 +366,14 @@ class tree extends tree_node {
|
||||
$childresults = array();
|
||||
foreach ($this->children as $index => $child) {
|
||||
if (!$child->is_applied_to_user_lists()) {
|
||||
continue;
|
||||
if ($andoperator) {
|
||||
continue;
|
||||
} else {
|
||||
// OR condition with one option that doesn't restrict user
|
||||
// lists = everyone is allowed.
|
||||
$childresults = array();
|
||||
break;
|
||||
}
|
||||
}
|
||||
$childresult = $child->get_user_list_sql($innernot, $info, $onlyactive);
|
||||
if ($childresult[0]) {
|
||||
|
||||
@@ -234,4 +234,68 @@ class condition extends \core_availability\condition {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes all date restrictions on a course by the specified shift amount.
|
||||
* Used by the course reset feature.
|
||||
*
|
||||
* @param int $courseid Course id
|
||||
* @param int $timeshift Offset in seconds
|
||||
*/
|
||||
public static function update_all_dates($courseid, $timeshift) {
|
||||
global $DB;
|
||||
|
||||
$modinfo = get_fast_modinfo($courseid);
|
||||
$anychanged = false;
|
||||
|
||||
// Adjust dates from all course modules.
|
||||
foreach ($modinfo->cms as $cm) {
|
||||
if (!$cm->availability) {
|
||||
continue;
|
||||
}
|
||||
$info = new \core_availability\info_module($cm);
|
||||
$tree = $info->get_availability_tree();
|
||||
$dates = $tree->get_all_children('availability_date\condition');
|
||||
$changed = false;
|
||||
foreach ($dates as $date) {
|
||||
$date->time += $timeshift;
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
// Save the updated course module.
|
||||
if ($changed) {
|
||||
$DB->set_field('course_modules', 'availability', json_encode($tree->save()),
|
||||
array('id' => $cm->id));
|
||||
$anychanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust dates from all course sections.
|
||||
foreach ($modinfo->get_section_info_all() as $section) {
|
||||
if (!$section->availability) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$info = new \core_availability\info_section($section);
|
||||
$tree = $info->get_availability_tree();
|
||||
$dates = $tree->get_all_children('availability_date\condition');
|
||||
$changed = false;
|
||||
foreach ($dates as $date) {
|
||||
$date->time += $timeshift;
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
// Save the updated course module.
|
||||
if ($changed) {
|
||||
$DB->set_field('course_sections', 'availability', json_encode($tree->save()),
|
||||
array('id' => $section->id));
|
||||
$anychanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure course cache is cleared if required.
|
||||
if ($anychanged) {
|
||||
rebuild_course_cache($courseid, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use availability_date\condition;
|
||||
use core_availability\tree;
|
||||
|
||||
/**
|
||||
* Unit tests for the date condition.
|
||||
@@ -233,4 +234,53 @@ class availability_date_condition_testcase extends advanced_testcase {
|
||||
$information = $date->get_standalone_description(true, true, $info);
|
||||
$this->assertRegExp('~from.*5 March 2014([^0-9]*)$~', $information);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the update_all_dates function.
|
||||
*/
|
||||
public function test_update_all_dates() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a course with 3 pages.
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$rec = array('course' => $course);
|
||||
$page1 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
|
||||
$page2 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
|
||||
$page3 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
|
||||
|
||||
// Set the availability page 2 to a simple date condition. You can access
|
||||
// it from 1337 onwards.
|
||||
$simplecondition = tree::get_root_json(array(
|
||||
condition::get_json(condition::DIRECTION_FROM, 1337)));
|
||||
$DB->set_field('course_modules', 'availability',
|
||||
json_encode($simplecondition), array('id' => $page2->cmid));
|
||||
|
||||
// Set page 3 to a complex set of conditions including a nested date condition.
|
||||
// You can access it until 1459, *or* after 2810 if you belong to a group.
|
||||
$complexcondition = tree::get_root_json(array(
|
||||
condition::get_json(condition::DIRECTION_UNTIL, 1459),
|
||||
tree::get_nested_json(array(
|
||||
condition::get_json(condition::DIRECTION_FROM, 2810),
|
||||
\availability_group\condition::get_json()))),
|
||||
tree::OP_OR);
|
||||
$DB->set_field('course_modules', 'availability',
|
||||
json_encode($complexcondition), array('id' => $page3->cmid));
|
||||
|
||||
// Now use the update_all_dates function to move date forward 100000.
|
||||
condition::update_all_dates($course->id, 100000);
|
||||
|
||||
// Get the expected conditions after adjusting time, and compare to database.
|
||||
$simplecondition->c[0]->t = 101337;
|
||||
$complexcondition->c[0]->t = 101459;
|
||||
$complexcondition->c[1]->c[0]->t = 102810;
|
||||
$this->assertEquals($simplecondition, json_decode(
|
||||
$DB->get_field('course_modules', 'availability', array('id' => $page2->cmid))));
|
||||
$this->assertEquals($complexcondition, json_decode(
|
||||
$DB->get_field('course_modules', 'availability', array('id' => $page3->cmid))));
|
||||
|
||||
// The one without availability conditions should still be null.
|
||||
$this->assertNull($DB->get_field('course_modules', 'availability', array('id' => $page1->cmid)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,12 @@ class condition extends \core_availability\condition {
|
||||
* @return stdClass Object representing condition
|
||||
*/
|
||||
public static function get_json($groupid = 0) {
|
||||
return (object)array('type' => 'group', 'id' => (int)$groupid);
|
||||
$result = (object)array('type' => 'group');
|
||||
// Id is only included if set.
|
||||
if ($groupid) {
|
||||
$result->id = (int)$groupid;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
|
||||
|
||||
@@ -35,7 +35,7 @@ use core_availability\info_section;
|
||||
* @copyright 2014 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class info_testcase extends \advanced_testcase {
|
||||
class info_testcase extends advanced_testcase {
|
||||
public function setUp() {
|
||||
// Load the mock condition so that it can be used.
|
||||
require_once(__DIR__ . '/fixtures/mock_condition.php');
|
||||
@@ -87,8 +87,8 @@ class info_testcase extends \advanced_testcase {
|
||||
// Check invalid one.
|
||||
$info = new info_module($cm3);
|
||||
$this->assertFalse($info->is_available($information));
|
||||
$debugging = phpunit_util::get_debugging_messages();
|
||||
phpunit_util::reset_debugging();
|
||||
$debugging = $this->getDebuggingMessages();
|
||||
$this->resetDebugging();
|
||||
$this->assertEquals(1, count($debugging));
|
||||
$this->assertContains('Invalid availability', $debugging[0]->message);
|
||||
|
||||
@@ -141,8 +141,8 @@ class info_testcase extends \advanced_testcase {
|
||||
// Check invalid one.
|
||||
$info = new info_section($sections[3]);
|
||||
$this->assertFalse($info->is_available($information));
|
||||
$debugging = phpunit_util::get_debugging_messages();
|
||||
phpunit_util::reset_debugging();
|
||||
$debugging = $this->getDebuggingMessages();
|
||||
$this->resetDebugging();
|
||||
$this->assertEquals(1, count($debugging));
|
||||
$this->assertContains('Invalid availability', $debugging[0]->message);
|
||||
|
||||
|
||||
@@ -600,6 +600,15 @@ class tree_testcase extends \advanced_testcase {
|
||||
ksort($result);
|
||||
$this->assertEquals(array(1, 3), array_keys($result));
|
||||
|
||||
// Tree with OR condition one of which doesn't filter.
|
||||
$structure = tree::get_root_json(array(
|
||||
self::mock(array('filter' => array(3))),
|
||||
self::mock(array())), tree::OP_OR);
|
||||
$tree = new tree($structure);
|
||||
$result = $tree->filter_user_list($users, false, $info, $checker);
|
||||
ksort($result);
|
||||
$this->assertEquals(array(1, 2, 3), array_keys($result));
|
||||
|
||||
// Tree with two condition that both filter (&).
|
||||
$structure = tree::get_root_json(array(
|
||||
self::mock(array('filter' => array(2, 3))),
|
||||
@@ -718,6 +727,16 @@ class tree_testcase extends \advanced_testcase {
|
||||
$result = $DB->get_fieldset_sql($sql, $params);
|
||||
sort($result);
|
||||
$this->assertEquals(array($userinneither->id), $result);
|
||||
|
||||
// Tree with 'OR' of group conditions and a non-filtering condition.
|
||||
// The non-filtering condition should mean that ALL users are included.
|
||||
$tree = new tree(tree::get_root_json(array(
|
||||
\availability_group\condition::get_json($group1->id),
|
||||
\availability_date\condition::get_json(\availability_date\condition::DIRECTION_UNTIL, 3)
|
||||
), tree::OP_OR));
|
||||
list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
|
||||
$this->assertEquals('', $sql);
|
||||
$this->assertEquals(array(), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,8 +27,18 @@ class cc_assesment_question_truefalse extends cc_assesment_question_proc_base {
|
||||
public function __construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir) {
|
||||
parent::__construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
|
||||
$this->qtype = cc_qti_profiletype::true_false;
|
||||
$this->correct_answer_node_id = $this->questions->nodeValue(
|
||||
'plugin_qtype_truefalse_question/truefalse/trueanswer', $this->question_node);
|
||||
|
||||
// Determine the correct answer by finding out which answer has the non zero fraction...
|
||||
// This is because a true / false question type can have 'false' as the correct answer.
|
||||
$answers = $this->questions->nodeList('plugin_qtype_truefalse_question/answers/answer', $this->question_node);
|
||||
foreach ($answers as $answer) {
|
||||
$fraction = $this->questions->nodeValue('fraction', $answer);
|
||||
|
||||
if ($fraction != 0) {
|
||||
$this->correct_answer_node_id = (int)$this->questions->nodeValue('@id', $answer);
|
||||
}
|
||||
}
|
||||
|
||||
$maximum_quiz_grade = (int)$this->quiz->nodeValue('/activity/quiz/grade');
|
||||
$this->total_grade_value = ($maximum_quiz_grade + 1).'.0000000';
|
||||
}
|
||||
|
||||
@@ -885,27 +885,30 @@ class cc_quiz extends entities {
|
||||
|
||||
$sheet_question_categories_question = cc2moodle::loadsheet(SHEET_COURSE_QUESTION_CATEGORIES_QUESTION_CATEGORY_QUESTION_TRUE_FALSE);
|
||||
|
||||
$max_score = 0;
|
||||
$true_answer_id = 0;
|
||||
$false_answer_id = 0;
|
||||
$trueanswer = null;
|
||||
$falseanswer = null;
|
||||
|
||||
if (!empty($question['answers'])) {
|
||||
|
||||
// Identify the true and false answers.
|
||||
foreach ($question['answers'] as $answer) {
|
||||
if ($answer['score'] > $max_score) {
|
||||
$max_score = $answer['score'];
|
||||
$true_answer_id = $answer['id'];
|
||||
if ($answer['identifier'] == 'true') {
|
||||
$trueanswer = $answer;
|
||||
} else if ($answer['identifier'] == 'false') {
|
||||
$falseanswer = $answer;
|
||||
} else {
|
||||
// Should not happen, but just in case.
|
||||
throw new coding_exception("Unknown answer identifier detected" .
|
||||
" in true/false quiz question with id {$question['id']}.");
|
||||
}
|
||||
|
||||
$node_course_question_categories_question_answer .= $this->create_node_course_question_categories_question_category_question_answer($answer);
|
||||
}
|
||||
|
||||
foreach ($question['answers'] as $answer) {
|
||||
|
||||
if ($answer['id'] != $true_answer_id) {
|
||||
$max_score = $answer['score'];
|
||||
$false_answer_id = $answer['id'];
|
||||
}
|
||||
// Make sure the true and false answer was found.
|
||||
if (is_null($trueanswer) || is_null($falseanswer)) {
|
||||
throw new coding_exception("Unable to correctly identify the " .
|
||||
"true and false answers in the question with id {$question['id']}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -914,8 +917,8 @@ class cc_quiz extends entities {
|
||||
'[#false_answer_id#]');
|
||||
|
||||
$replace_values = array($node_course_question_categories_question_answer,
|
||||
$true_answer_id,
|
||||
$false_answer_id);
|
||||
$trueanswer['id'],
|
||||
$falseanswer['id']);
|
||||
|
||||
$node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question);
|
||||
|
||||
|
||||
@@ -908,27 +908,30 @@ class cc11_quiz extends entities11 {
|
||||
|
||||
$sheet_question_categories_question = cc112moodle::loadsheet(SHEET_COURSE_QUESTION_CATEGORIES_QUESTION_CATEGORY_QUESTION_TRUE_FALSE);
|
||||
|
||||
$max_score = 0;
|
||||
$true_answer_id = 0;
|
||||
$false_answer_id = 0;
|
||||
$trueanswer = null;
|
||||
$falseanswer = null;
|
||||
|
||||
if (!empty($question['answers'])) {
|
||||
|
||||
// Identify the true and false answers.
|
||||
foreach ($question['answers'] as $answer) {
|
||||
if ($answer['score'] > $max_score) {
|
||||
$max_score = $answer['score'];
|
||||
$true_answer_id = $answer['id'];
|
||||
if ($answer['identifier'] == 'true') {
|
||||
$trueanswer = $answer;
|
||||
} else if ($answer['identifier'] == 'false') {
|
||||
$falseanswer = $answer;
|
||||
} else {
|
||||
// Should not happen, but just in case.
|
||||
throw new coding_exception("Unknown answer identifier detected " .
|
||||
"in true/false quiz question with id {$question['id']}.");
|
||||
}
|
||||
|
||||
$node_course_question_categories_question_answer .= $this->create_node_course_question_categories_question_category_question_answer($answer);
|
||||
}
|
||||
|
||||
foreach ($question['answers'] as $answer) {
|
||||
|
||||
if ($answer['id'] != $true_answer_id) {
|
||||
$max_score = $answer['score'];
|
||||
$false_answer_id = $answer['id'];
|
||||
}
|
||||
// Make sure the true and false answer was found.
|
||||
if (is_null($trueanswer) || is_null($falseanswer)) {
|
||||
throw new coding_exception("Unable to correctly identify the " .
|
||||
"true and false answers in the question with id {$question['id']}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,8 +940,8 @@ class cc11_quiz extends entities11 {
|
||||
'[#false_answer_id#]');
|
||||
|
||||
$replace_values = array($node_course_question_categories_question_answer,
|
||||
$true_answer_id,
|
||||
$false_answer_id);
|
||||
$trueanswer['id'],
|
||||
$falseanswer['id']);
|
||||
|
||||
$node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question);
|
||||
|
||||
|
||||
@@ -1686,27 +1686,54 @@ class restore_course_structure_step extends restore_structure_step {
|
||||
*/
|
||||
public function process_course($data) {
|
||||
global $CFG, $DB;
|
||||
$context = context::instance_by_id($this->task->get_contextid());
|
||||
$userid = $this->task->get_userid();
|
||||
$target = $this->get_task()->get_target();
|
||||
$isnewcourse = $target != backup::TARGET_CURRENT_ADDING && $target != backup::TARGET_EXISTING_ADDING;
|
||||
|
||||
// When restoring to a new course we can set all the things except for the ID number.
|
||||
$canchangeidnumber = $isnewcourse || has_capability('moodle/course:changeidnumber', $context, $userid);
|
||||
$canchangeshortname = $isnewcourse || has_capability('moodle/course:changeshortname', $context, $userid);
|
||||
$canchangefullname = $isnewcourse || has_capability('moodle/course:changefullname', $context, $userid);
|
||||
$canchangesummary = $isnewcourse || has_capability('moodle/course:changesummary', $context, $userid);
|
||||
|
||||
$data = (object)$data;
|
||||
$data->id = $this->get_courseid();
|
||||
|
||||
$fullname = $this->get_setting_value('course_fullname');
|
||||
$shortname = $this->get_setting_value('course_shortname');
|
||||
$startdate = $this->get_setting_value('course_startdate');
|
||||
|
||||
// Calculate final course names, to avoid dupes
|
||||
// Calculate final course names, to avoid dupes.
|
||||
list($fullname, $shortname) = restore_dbops::calculate_course_names($this->get_courseid(), $fullname, $shortname);
|
||||
|
||||
// Need to change some fields before updating the course record
|
||||
$data->id = $this->get_courseid();
|
||||
$data->fullname = $fullname;
|
||||
$data->shortname= $shortname;
|
||||
if ($canchangefullname) {
|
||||
$data->fullname = $fullname;
|
||||
} else {
|
||||
unset($data->fullname);
|
||||
}
|
||||
|
||||
if ($canchangeshortname) {
|
||||
$data->shortname = $shortname;
|
||||
} else {
|
||||
unset($data->shortname);
|
||||
}
|
||||
|
||||
if (!$canchangesummary) {
|
||||
unset($data->summary);
|
||||
unset($data->summaryformat);
|
||||
}
|
||||
|
||||
// Only allow the idnumber to be set if the user has permission and the idnumber is not already in use by
|
||||
// another course on this site.
|
||||
$context = context::instance_by_id($this->task->get_contextid());
|
||||
if (!empty($data->idnumber) && has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid()) &&
|
||||
$this->task->is_samesite() && !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
|
||||
if (!empty($data->idnumber) && $canchangeidnumber && $this->task->is_samesite()
|
||||
&& !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
|
||||
// Do not reset idnumber.
|
||||
|
||||
} else if (!$isnewcourse) {
|
||||
// Prevent override when restoring as merge.
|
||||
unset($data->idnumber);
|
||||
|
||||
} else {
|
||||
$data->idnumber = '';
|
||||
}
|
||||
|
||||
@@ -169,13 +169,39 @@ abstract class restore_check {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the user has the rolldates capability. If not we want to lock this
|
||||
// settings so that they cannot change it.
|
||||
$hasrolldatescap = has_capability('moodle/restore:rolldates', $coursectx, $userid);
|
||||
if ($type == backup::TYPE_1COURSE && !$hasrolldatescap) {
|
||||
$datesetting = $restore_controller->get_plan()->get_setting('course_startdate');
|
||||
if ($datesetting) {
|
||||
$datesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
if ($type == backup::TYPE_1COURSE) {
|
||||
// Ensure the user has the rolldates capability. If not we want to lock this
|
||||
// settings so that they cannot change it.
|
||||
$hasrolldatescap = has_capability('moodle/restore:rolldates', $coursectx, $userid);
|
||||
if (!$hasrolldatescap) {
|
||||
$datesetting = $restore_controller->get_plan()->get_setting('course_startdate');
|
||||
if ($datesetting) {
|
||||
$datesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the user has the changefullname capability. If not we want to lock
|
||||
// the setting so that they cannot change it.
|
||||
$haschangefullnamecap = has_capability('moodle/course:changefullname', $coursectx, $userid);
|
||||
if (!$haschangefullnamecap) {
|
||||
$fullnamesetting = $restore_controller->get_plan()->get_setting('course_fullname');
|
||||
$fullnamesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
}
|
||||
|
||||
// Ensure the user has the changeshortname capability. If not we want to lock
|
||||
// the setting so that they cannot change it.
|
||||
$haschangeshortnamecap = has_capability('moodle/course:changeshortname', $coursectx, $userid);
|
||||
if (!$haschangeshortnamecap) {
|
||||
$shortnamesetting = $restore_controller->get_plan()->get_setting('course_shortname');
|
||||
$shortnamesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
}
|
||||
|
||||
// Ensure the user has the update capability. If not we want to lock
|
||||
// the overwrite setting so that they cannot change it.
|
||||
$hasupdatecap = has_capability('moodle/course:update', $coursectx, $userid);
|
||||
if (!$hasupdatecap) {
|
||||
$overwritesetting = $restore_controller->get_plan()->get_setting('overwrite_conf');
|
||||
$overwritesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class edit_backpack_form extends moodleform {
|
||||
$mform->addElement('html', html_writer::tag('span', '', array('class' => 'notconnected', 'id' => 'connection-error')));
|
||||
$mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
|
||||
$mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
|
||||
$mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
|
||||
$mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
|
||||
$status = html_writer::tag('span', get_string('notconnected', 'badges'),
|
||||
array('class' => 'notconnected', 'id' => 'connection-status'));
|
||||
$mform->addElement('static', 'status', get_string('status'), $status);
|
||||
@@ -67,7 +67,7 @@ class edit_backpack_form extends moodleform {
|
||||
$mform->addElement('hidden', 'userid', $USER->id);
|
||||
$mform->setType('userid', PARAM_INT);
|
||||
|
||||
$mform->addElement('hidden', 'backpackurl', 'http://' . BADGE_BACKPACKURL);
|
||||
$mform->addElement('hidden', 'backpackurl', BADGE_BACKPACKURL);
|
||||
$mform->setType('backpackurl', PARAM_URL);
|
||||
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class edit_collections_form extends moodleform {
|
||||
|
||||
$mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
|
||||
$mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
|
||||
$mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
|
||||
$mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
|
||||
|
||||
$status = html_writer::tag('span', get_string('connected', 'badges'), array('class' => 'connected'));
|
||||
$mform->addElement('static', 'status', get_string('status'), $status);
|
||||
|
||||
@@ -87,7 +87,7 @@ if (!isset($data->status) || $data->status != 'okay') {
|
||||
|
||||
// Make sure email matches a backpack.
|
||||
$check = new stdClass();
|
||||
$check->backpackurl = 'http://' . BADGE_BACKPACKURL;
|
||||
$check->backpackurl = BADGE_BACKPACKURL;
|
||||
$check->email = $data->email;
|
||||
|
||||
$bp = new OpenBadgesBackpackHandler($check);
|
||||
@@ -106,7 +106,7 @@ if (isset($request->status) && $request->status == 'missing') {
|
||||
$obj = new stdClass();
|
||||
$obj->userid = $USER->id;
|
||||
$obj->email = $data->email;
|
||||
$obj->backpackurl = 'http://' . BADGE_BACKPACKURL;
|
||||
$obj->backpackurl = BADGE_BACKPACKURL;
|
||||
$obj->backpackuid = $backpackuid;
|
||||
$obj->autosync = 0;
|
||||
$obj->password = '';
|
||||
|
||||
@@ -170,33 +170,31 @@ class award_criteria_profile extends award_criteria {
|
||||
}
|
||||
|
||||
$join = '';
|
||||
$where = '';
|
||||
$whereparts = array();
|
||||
$sqlparams = array();
|
||||
$rule = ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) ? ' OR ' : ' AND ';
|
||||
|
||||
foreach ($this->params as $param) {
|
||||
if (is_numeric($param['field'])) {
|
||||
$infodata[] = " uid.fieldid = :fieldid{$param['field']} ";
|
||||
$sqlparams["fieldid{$param['field']}"] = $param['field'];
|
||||
// This is a custom field.
|
||||
$idx = count($whereparts) + 1;
|
||||
$join .= " LEFT JOIN {user_info_data} uid{$idx} ON uid{$idx}.userid = u.id AND uid{$idx}.fieldid = :fieldid{$idx} ";
|
||||
$sqlparams["fieldid{$idx}"] = $param['field'];
|
||||
$whereparts[] = "uid{$idx}.id IS NOT NULL";
|
||||
} else {
|
||||
$userdata[] = $DB->sql_isnotempty('u', "u.{$param['field']}", false, true);
|
||||
// This is a field from {user} table.
|
||||
$whereparts[] = $DB->sql_isnotempty('u', "u.{$param['field']}", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Add user custom field parameters if there are any.
|
||||
if (!empty($infodata)) {
|
||||
$extraon = implode($rule, $infodata);
|
||||
$join = " LEFT JOIN {user_info_data} uid ON uid.userid = u.id AND ({$extraon})";
|
||||
}
|
||||
|
||||
// Add user table field parameters if there are any.
|
||||
if (!empty($userdata)) {
|
||||
$extraon = implode($rule, $userdata);
|
||||
$where = " AND ({$extraon})";
|
||||
}
|
||||
|
||||
$sqlparams['userid'] = $userid;
|
||||
$sql = "SELECT u.* FROM {user} u " . $join . " WHERE u.id = :userid " . $where;
|
||||
|
||||
if ($whereparts) {
|
||||
$where = " AND (" . implode($rule, $whereparts) . ")";
|
||||
} else {
|
||||
$where = '';
|
||||
}
|
||||
$sql = "SELECT 1 FROM {user} u " . $join . " WHERE u.id = :userid $where";
|
||||
$overall = $DB->record_exists_sql($sql, $sqlparams);
|
||||
|
||||
return $overall;
|
||||
@@ -212,29 +210,26 @@ class award_criteria_profile extends award_criteria {
|
||||
global $DB;
|
||||
|
||||
$join = '';
|
||||
$where = '';
|
||||
$whereparts = array();
|
||||
$params = array();
|
||||
$rule = ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) ? ' OR ' : ' AND ';
|
||||
|
||||
foreach ($this->params as $param) {
|
||||
if (is_numeric($param['field'])) {
|
||||
$infodata[] = " uid.fieldid = :fieldid{$param['field']} ";
|
||||
$params["fieldid{$param['field']}"] = $param['field'];
|
||||
// This is a custom field.
|
||||
$idx = count($whereparts);
|
||||
$join .= " LEFT JOIN {user_info_data} uid{$idx} ON uid{$idx}.userid = u.id AND uid{$idx}.fieldid = :fieldid{$idx} ";
|
||||
$params["fieldid{$idx}"] = $param['field'];
|
||||
$whereparts[] = "uid{$idx}.id IS NOT NULL";
|
||||
} else {
|
||||
$userdata[] = $DB->sql_isnotempty('u', "u.{$param['field']}", false, true);
|
||||
$whereparts[] = $DB->sql_isnotempty('u', "u.{$param['field']}", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Add user custom fields if there are any.
|
||||
if (!empty($infodata)) {
|
||||
$extraon = implode($rule, $infodata);
|
||||
$join = " LEFT JOIN {user_info_data} uid ON uid.userid = u.id AND ({$extraon})";
|
||||
}
|
||||
|
||||
// Add user table fields if there are any.
|
||||
if (!empty($userdata)) {
|
||||
$extraon = implode($rule, $userdata);
|
||||
$where = " AND ({$extraon})";
|
||||
if ($whereparts) {
|
||||
$where = " AND (" . implode($rule, $whereparts) . ")";
|
||||
} else {
|
||||
$where = '';
|
||||
}
|
||||
return array($join, $where, $params);
|
||||
}
|
||||
|
||||
+3
-1
@@ -61,6 +61,8 @@ $PAGE->set_heading($badge->name);
|
||||
$PAGE->set_title($badge->name);
|
||||
$PAGE->navbar->add($badge->name);
|
||||
|
||||
require_capability('moodle/badges:viewbadges', $context);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(print_badge_image($badge, $context, 'small') . ' ' . $badge->name);
|
||||
|
||||
@@ -75,4 +77,4 @@ echo $output->print_badge_status_box($badge);
|
||||
$output->print_badge_tabs($badgeid, $context, 'overview');
|
||||
echo $output->print_badge_overview($badge, $context);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -324,6 +324,11 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
$criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_ACTIVITY, 'badgeid' => $badge->id));
|
||||
$criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'module_'.$this->module->cmid => $this->module->cmid));
|
||||
|
||||
// Assert the badge will not be issued to the user as is.
|
||||
$badge = new badge($this->coursebadge);
|
||||
$badge->review_all_criteria();
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
|
||||
// Set completion for forum activity.
|
||||
$c = new completion_info($this->course);
|
||||
$activities = $c->get_activities();
|
||||
@@ -359,6 +364,11 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
|
||||
$ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id));
|
||||
|
||||
// Assert the badge will not be issued to the user as is.
|
||||
$badge = new badge($this->coursebadge);
|
||||
$badge->review_all_criteria();
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
|
||||
// Mark course as complete.
|
||||
$sink = $this->redirectEmails();
|
||||
$ccompletion->mark_complete();
|
||||
@@ -374,18 +384,33 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
* Test badges observer when user_updated event is fired.
|
||||
*/
|
||||
public function test_badges_observer_profile_criteria_review() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot.'/user/profile/lib.php');
|
||||
|
||||
// Add a custom field of textarea type.
|
||||
$customprofileid = $DB->insert_record('user_info_field', array(
|
||||
'shortname' => 'newfield', 'name' => 'Description of new field', 'categoryid' => 1,
|
||||
'datatype' => 'textarea'));
|
||||
|
||||
$this->preventResetByRollback(); // Messaging is not compatible with transactions.
|
||||
$badge = new badge($this->coursebadge);
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
|
||||
$criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
|
||||
$criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
|
||||
$criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_PROFILE, 'badgeid' => $badge->id));
|
||||
$criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address', 'field_aim' => 'aim'));
|
||||
$criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ALL, 'field_address' => 'address', 'field_aim' => 'aim',
|
||||
'field_' . $customprofileid => $customprofileid));
|
||||
|
||||
// Assert the badge will not be issued to the user as is.
|
||||
$badge = new badge($this->coursebadge);
|
||||
$badge->review_all_criteria();
|
||||
$this->assertFalse($badge->is_issued($this->user->id));
|
||||
|
||||
// Set the required fields and make sure the badge got issued.
|
||||
$this->user->address = 'Test address';
|
||||
$this->user->aim = '999999999';
|
||||
$sink = $this->redirectEmails();
|
||||
profile_save_data((object)array('id' => $this->user->id, 'profile_field_newfield' => 'X'));
|
||||
user_update_user($this->user, false);
|
||||
$this->assertCount(1, $sink->get_messages());
|
||||
$sink->close();
|
||||
|
||||
+3
-1
@@ -75,6 +75,8 @@ if ($type == BADGE_TYPE_SITE) {
|
||||
$PAGE->set_heading($title);
|
||||
}
|
||||
|
||||
require_capability('moodle/badges:viewbadges', $PAGE->context);
|
||||
|
||||
$PAGE->set_title($title);
|
||||
$output = $PAGE->get_renderer('core', 'badges');
|
||||
|
||||
@@ -103,4 +105,4 @@ if ($totalcount) {
|
||||
echo $output->notification(get_string('nobadges', 'badges'));
|
||||
}
|
||||
|
||||
echo $output->footer();
|
||||
echo $output->footer();
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
*/
|
||||
|
||||
class block_course_summary extends block_base {
|
||||
|
||||
/**
|
||||
* @var bool Flag to indicate whether the header should be hidden or not.
|
||||
*/
|
||||
private $headerhidden = true;
|
||||
|
||||
function init() {
|
||||
$this->title = get_string('pluginname', 'block_course_summary');
|
||||
}
|
||||
@@ -32,8 +38,10 @@ class block_course_summary extends block_base {
|
||||
}
|
||||
|
||||
function specialization() {
|
||||
if($this->page->pagetype == PAGE_COURSE_VIEW && $this->page->course->id != SITEID) {
|
||||
// Page type starts with 'course-view' and the page's course ID is not equal to the site ID.
|
||||
if (strpos($this->page->pagetype, PAGE_COURSE_VIEW) === 0 && $this->page->course->id != SITEID) {
|
||||
$this->title = get_string('coursesummary', 'block_course_summary');
|
||||
$this->headerhidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +79,7 @@ class block_course_summary extends block_base {
|
||||
}
|
||||
|
||||
function hide_header() {
|
||||
return true;
|
||||
return $this->headerhidden;
|
||||
}
|
||||
|
||||
function preferred_width() {
|
||||
|
||||
@@ -25,19 +25,22 @@ Feature: Course summary block used in a course
|
||||
Scenario: Student can view course summary
|
||||
When I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
Then "Course/site summary" "block" should exist
|
||||
And I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
Then "Course summary" "block" should exist
|
||||
And I should see "Course summary" in the "Course summary" "block"
|
||||
And I should see "Proved the course summary block works!" in the "Course summary" "block"
|
||||
|
||||
Scenario: Teacher can see an edit icon when edit mode is on and follow it to the course edit page
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
And I click on "Edit" "link" in the "Course/site summary" "block"
|
||||
Then I should see "Proved the course summary block works!" in the "Course summary" "block"
|
||||
And I should see "Course summary" in the "Course summary" "block"
|
||||
And I click on "Edit" "link" in the "Course summary" "block"
|
||||
Then I should see "Edit course settings" in the "h2" "css_element"
|
||||
|
||||
Scenario: Teacher can not see edit icon when edit mode is off
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course/site summary" "block"
|
||||
Then I should see "Proved the course summary block works!" in the "Course summary" "block"
|
||||
And I should see "Course summary" in the "Course summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course summary" "block"
|
||||
|
||||
@@ -16,6 +16,7 @@ Feature: Course summary block used on the frontpage
|
||||
Scenario: Guest can view site summary
|
||||
When I am on homepage
|
||||
Then "Course/site summary" "block" should exist
|
||||
And I should not see "Course summary" in the "Course/site summary" "block"
|
||||
And I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
|
||||
Scenario: Admin can see an edit icon when edit mode is on and follow it to the front page settings
|
||||
@@ -23,6 +24,7 @@ Feature: Course summary block used on the frontpage
|
||||
And I am on homepage
|
||||
And I follow "Turn editing on"
|
||||
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
And I should not see "Course summary" in the "Course/site summary" "block"
|
||||
And I click on "Edit" "link" in the "Course/site summary" "block"
|
||||
Then I should see "Front page settings" in the "h2" "css_element"
|
||||
|
||||
@@ -30,4 +32,5 @@ Feature: Course summary block used on the frontpage
|
||||
When I log in as "admin"
|
||||
And I am on homepage
|
||||
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
And I should not see "Course summary" in the "Course/site summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course/site summary" "block"
|
||||
|
||||
@@ -104,6 +104,23 @@ class block_html extends block_base {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy any block-specific data when copying to a new block instance.
|
||||
* @param int $fromid the id number of the block instance to copy from
|
||||
* @return boolean
|
||||
*/
|
||||
public function instance_copy($fromid) {
|
||||
$fromcontext = context_block::instance($fromid);
|
||||
$fs = get_file_storage();
|
||||
// This extra check if file area is empty adds one query if it is not empty but saves several if it is.
|
||||
if (!$fs->is_area_empty($fromcontext->id, 'block_html', 'content', 0, false)) {
|
||||
$draftitemid = 0;
|
||||
file_prepare_draft_area($draftitemid, $fromcontext->id, 'block_html', 'content', 0, array('subdirs' => true));
|
||||
file_save_draft_area_files($draftitemid, $this->context->id, 'block_html', 'content', 0, array('subdirs' => true));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function content_is_trusted() {
|
||||
global $SCRIPT;
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ class block_myprofile extends block_base {
|
||||
|
||||
if(!empty($this->config->display_phone2) && !empty($USER->phone2)) {
|
||||
$this->content->text .= '<div class="myprofileitem phone2">';
|
||||
$this->content->text .= get_string('phone').': ' . s($USER->phone2);
|
||||
$this->content->text .= get_string('phone2').': ' . s($USER->phone2);
|
||||
$this->content->text .= '</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ $string['display_skype'] = 'Display Skype';
|
||||
$string['display_yahoo'] = 'Display Yahoo';
|
||||
$string['display_aim'] = 'Display AIM';
|
||||
$string['display_msn'] = 'Display MSN';
|
||||
$string['display_phone1'] = 'Display phone number 1';
|
||||
$string['display_phone2'] = 'Display phone number 2';
|
||||
$string['display_phone1'] = 'Display phone';
|
||||
$string['display_phone2'] = 'Display mobile phone';
|
||||
$string['display_institution'] = 'Display institution';
|
||||
$string['display_address'] = 'Display address';
|
||||
$string['display_firstaccess'] = 'Display first access';
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
$string['anycategory'] = 'Any category';
|
||||
$string['apierror'] = 'The Youtube API key is not set. Contact your administrator.';
|
||||
$string['apierror'] = 'The YouTube API key is not set. Contact your administrator.';
|
||||
$string['apikey'] = 'API key';
|
||||
$string['apikeyinfo'] = 'Get a <a href="https://developers.google.com/youtube/v3/getting-started">Google API key</a> for your Moodle site.';
|
||||
$string['autosvehicles'] = 'Autos & Vehicles';
|
||||
@@ -42,7 +42,7 @@ $string['numberofvideos'] = 'Number of videos';
|
||||
$string['peopleblogs'] = 'People & Blogs';
|
||||
$string['petsanimals'] = 'Pets & Animals';
|
||||
$string['pluginname'] = 'YouTube';
|
||||
$string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persist.';
|
||||
$string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persists.';
|
||||
$string['scienceandtech'] = 'Science & Tech';
|
||||
$string['sports'] = 'Sports';
|
||||
$string['tag_youtube:addinstance'] = 'Add a new YouTube block';
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
require_once(dirname(dirname(__FILE__)).'/config.php');
|
||||
require_once('lib.php');
|
||||
require_once('locallib.php');
|
||||
require_once($CFG->dirroot .'/comment/lib.php');
|
||||
|
||||
$action = required_param('action', PARAM_ALPHA);
|
||||
$id = optional_param('entryid', 0, PARAM_INT);
|
||||
@@ -110,6 +111,9 @@ $output = $PAGE->get_renderer('blog');
|
||||
$strblogs = get_string('blogs', 'blog');
|
||||
|
||||
if ($action === 'delete') {
|
||||
// Init comment JS strings.
|
||||
comment::init();
|
||||
|
||||
if (empty($entry->id)) {
|
||||
print_error('wrongentryid', 'blog');
|
||||
}
|
||||
|
||||
@@ -189,10 +189,14 @@ foreach($events as $event) {
|
||||
$ev->add_property('class', 'PUBLIC'); // PUBLIC / PRIVATE / CONFIDENTIAL
|
||||
$ev->add_property('last-modified', Bennu::timestamp_to_datetime($event->timemodified));
|
||||
$ev->add_property('dtstamp', Bennu::timestamp_to_datetime()); // now
|
||||
$ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart)); // when event starts
|
||||
if ($event->timeduration > 0) {
|
||||
//dtend is better than duration, because it works in Microsoft Outlook and works better in Korganizer
|
||||
$ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart)); // when event starts.
|
||||
$ev->add_property('dtend', Bennu::timestamp_to_datetime($event->timestart + $event->timeduration));
|
||||
} else {
|
||||
// When no duration is present, ie an all day event, VALUE should be date instead of time and dtend = dtstart + 1 day.
|
||||
$ev->add_property('dtstart', Bennu::timestamp_to_date($event->timestart), array('value' => 'DATE')); // All day event.
|
||||
$ev->add_property('dtend', Bennu::timestamp_to_date($event->timestart + DAYSECS), array('value' => 'DATE')); // All day event.
|
||||
}
|
||||
if ($event->courseid != 0) {
|
||||
$coursecontext = context_course::instance($event->courseid);
|
||||
|
||||
@@ -173,6 +173,8 @@ class core_calendar_external extends external_api {
|
||||
|
||||
// Let us findout courses that we can return events from.
|
||||
if (!$hassystemcap) {
|
||||
$courses = enrol_get_my_courses('id');
|
||||
$courses = array_keys($courses);
|
||||
foreach ($params['events']['courseids'] as $id) {
|
||||
try {
|
||||
$context = context_course::instance($id);
|
||||
@@ -185,7 +187,6 @@ class core_calendar_external extends external_api {
|
||||
'warningcode' => 'nopermissions',
|
||||
'message' => 'No access rights in course context '.$e->getMessage().$e->getTraceAsString()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -221,21 +222,21 @@ class core_calendar_external extends external_api {
|
||||
$funcparam['courses'][] = $SITE->id;
|
||||
}
|
||||
|
||||
// Event list does not check visibility and permissions, we'll check that later.
|
||||
$eventlist = calendar_get_events($params['options']['timestart'], $params['options']['timeend'], $funcparam['users'], $funcparam['groups'],
|
||||
$funcparam['courses'], true, $params['options']['ignorehidden']);
|
||||
|
||||
// WS expects arrays.
|
||||
$events = array();
|
||||
foreach ($eventlist as $id => $event) {
|
||||
$events[$id] = (array) $event;
|
||||
}
|
||||
|
||||
// We need to get events asked for eventids.
|
||||
$eventsbyid = calendar_get_events_by_id($params['events']['eventids']);
|
||||
foreach ($eventsbyid as $eventid => $eventobj) {
|
||||
if ($eventsbyid = calendar_get_events_by_id($params['events']['eventids'])) {
|
||||
$eventlist += $eventsbyid;
|
||||
}
|
||||
|
||||
foreach ($eventlist as $eventid => $eventobj) {
|
||||
$event = (array) $eventobj;
|
||||
if (isset($events[$eventid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($hassystemcap) {
|
||||
// User can see everything, no further check is needed.
|
||||
$events[$eventid] = $event;
|
||||
|
||||
@@ -363,6 +363,37 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
$this->assertEquals(1, count($events['events'])); // site.
|
||||
$this->assertEquals(0, count($events['warnings']));
|
||||
|
||||
// Try getting a course event by its id.
|
||||
$paramevents = array ('eventids' => array($courseevent->id));
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
$this->assertEquals(1, count($events['events']));
|
||||
$this->assertEquals(0, count($events['warnings']));
|
||||
|
||||
// Now, create an activity event.
|
||||
$this->setAdminUser();
|
||||
$nexttime = time() + DAYSECS;
|
||||
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'duedate' => $nexttime));
|
||||
|
||||
$this->setUser($user);
|
||||
$paramevents = array ('courseids' => array($course->id));
|
||||
$options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + WEEKSECS);
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
|
||||
$this->assertCount(5, $events['events']);
|
||||
|
||||
// Hide the assignment.
|
||||
set_coursemodule_visible($assign->cmid, 0);
|
||||
// Empty all the caches that may be affected by this change.
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
course_modinfo::clear_instance_cache();
|
||||
|
||||
$events = core_calendar_external::get_calendar_events($paramevents, $options);
|
||||
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
|
||||
// Expect one less.
|
||||
$this->assertCount(4, $events['events']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,13 +78,8 @@ class core_calendar_lib_testcase extends advanced_testcase {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/lib/cronlib.php');
|
||||
|
||||
// Moodle ICal URL (moodle.org events).
|
||||
$presetwhat = 'all';
|
||||
$presettime = 'recentupcoming';
|
||||
$userid = 1;
|
||||
$authtoken = 'a8bcfee2fb868a05357f650bd65dc0699b026524';
|
||||
$subscriptionurl = 'https://moodle.org/calendar/export_execute.php'
|
||||
. '?preset_what='.$presetwhat.'&preset_time='.$presettime.'&userid='.$userid.'&authtoken='.$authtoken;
|
||||
// ICal URL from external test repo.
|
||||
$subscriptionurl = $this->getExternalTestFileUrl('/ical.ics');
|
||||
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'site';
|
||||
|
||||
+19
-10
@@ -256,15 +256,17 @@ function cohort_get_available_cohorts($currentcontext, $withmembers = 0, $offset
|
||||
$groupbysql = '';
|
||||
$havingsql = '';
|
||||
if ($withmembers) {
|
||||
$groupbysql = " GROUP BY $fieldssql";
|
||||
$fieldssql .= ', s.memberscnt';
|
||||
$subfields = "c.id, COUNT(DISTINCT cm.userid) AS memberscnt";
|
||||
$groupbysql = " GROUP BY c.id";
|
||||
$fromsql = " LEFT JOIN {cohort_members} cm ON cm.cohortid = c.id ";
|
||||
$fieldssql .= ', COUNT(DISTINCT cm.userid) AS memberscnt';
|
||||
if (in_array($withmembers,
|
||||
array(COHORT_COUNT_ENROLLED_MEMBERS, COHORT_WITH_ENROLLED_MEMBERS_ONLY, COHORT_WITH_NOTENROLLED_MEMBERS_ONLY))) {
|
||||
list($esql, $params2) = get_enrolled_sql($currentcontext);
|
||||
$fromsql .= " LEFT JOIN ($esql) u ON u.id = cm.userid ";
|
||||
$params = array_merge($params2, $params);
|
||||
$fieldssql .= ', COUNT(DISTINCT u.id) AS enrolledcnt';
|
||||
$fieldssql .= ', s.enrolledcnt';
|
||||
$subfields .= ', COUNT(DISTINCT u.id) AS enrolledcnt';
|
||||
}
|
||||
if ($withmembers == COHORT_WITH_MEMBERS_ONLY) {
|
||||
$havingsql = " HAVING COUNT(DISTINCT cm.userid) > 0";
|
||||
@@ -280,13 +282,20 @@ function cohort_get_available_cohorts($currentcontext, $withmembers = 0, $offset
|
||||
$params = array_merge($params, $searchparams);
|
||||
}
|
||||
|
||||
$sql = "SELECT $fieldssql
|
||||
FROM {cohort} c
|
||||
$fromsql
|
||||
WHERE $wheresql
|
||||
$groupbysql
|
||||
$havingsql
|
||||
ORDER BY c.name, c.idnumber";
|
||||
if ($withmembers) {
|
||||
$sql = "SELECT " . str_replace('c.', 'cohort.', $fieldssql) . "
|
||||
FROM {cohort} cohort
|
||||
JOIN (SELECT $subfields
|
||||
FROM {cohort} c $fromsql
|
||||
WHERE $wheresql $groupbysql $havingsql
|
||||
) s ON cohort.id = s.id
|
||||
ORDER BY cohort.name, cohort.idnumber";
|
||||
} else {
|
||||
$sql = "SELECT $fieldssql
|
||||
FROM {cohort} c $fromsql
|
||||
WHERE $wheresql
|
||||
ORDER BY c.name, c.idnumber";
|
||||
}
|
||||
|
||||
return $DB->get_records_sql($sql, $params, $offset, $limit);
|
||||
}
|
||||
|
||||
@@ -232,6 +232,20 @@ abstract class completion_criteria extends data_object {
|
||||
*/
|
||||
abstract public function get_details($completion);
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
global $COMPLETION_CRITERIA_TYPES;
|
||||
|
||||
$criteriatype = $COMPLETION_CRITERIA_TYPES[$this->criteriatype];
|
||||
return new pix_icon('i/'.$criteriatype, $alt, 'moodle', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return criteria status text for display in reports
|
||||
*
|
||||
|
||||
@@ -287,4 +287,15 @@ class completion_criteria_activity extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('icon', $alt, 'mod_'.$this->module, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,4 +202,15 @@ class completion_criteria_date extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/calendar', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,4 +248,15 @@ class completion_criteria_grade extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/grades', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,4 +149,15 @@ class completion_criteria_self extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/completion_self', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +131,15 @@ class completion_criteria_unenrol extends completion_criteria {
|
||||
$details['status'] = '';
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/user', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,4 +122,40 @@ class behat_completion extends behat_base {
|
||||
new Given('I press "'.get_string('savechanges').'"')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the activity with specified name is maked as complete.
|
||||
*
|
||||
* @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion should be marked as complete$/
|
||||
* @return array
|
||||
*/
|
||||
public function activity_marked_as_complete($activityname, $activitytype, $completiontype) {
|
||||
if ($completiontype == "manual") {
|
||||
$imgalttext = get_string("completion-alt-manual-y", 'core_completion', $activityname);
|
||||
} else {
|
||||
$imgalttext = get_string("completion-alt-auto-y", 'core_completion', $activityname);
|
||||
}
|
||||
$csselementforactivitytype = "li.modtype_".strtolower($activitytype);
|
||||
|
||||
return new Given('"//img[contains(@alt, \''.$imgalttext.'\')]" "xpath_element" ' .
|
||||
'should exist in the "'.$csselementforactivitytype.'" "css_element"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the activity with specified name is maked as complete.
|
||||
*
|
||||
* @Given /^the "(?P<activityname_string>(?:[^"]|\\")*)" "(?P<activitytype_string>(?:[^"]|\\")*)" activity with "(manual|auto)" completion should be marked as not complete$/
|
||||
* @return array
|
||||
*/
|
||||
public function activity_marked_as_not_complete($activityname, $activitytype, $completiontype) {
|
||||
if ($completiontype == "manual") {
|
||||
$imgalttext = get_string("completion-alt-manual-n", 'core_completion', $activityname);
|
||||
} else {
|
||||
$imgalttext = get_string("completion-alt-auto-n", 'core_completion', $activityname);
|
||||
}
|
||||
$csselementforactivitytype = "li.modtype_".strtolower($activitytype);
|
||||
|
||||
return new Given('"//img[contains(@alt, \''.$imgalttext.'\')]" "xpath_element" ' .
|
||||
'should exist in the "'.$csselementforactivitytype.'" "css_element"');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +71,10 @@ Feature: Restrict sections availability through completion or grade conditions
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Grade" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "min" "checkbox"
|
||||
And I set the following fields to these values:
|
||||
| id | Grade assignment |
|
||||
| minval | 20 |
|
||||
| id | Grade assignment |
|
||||
| min | 1 |
|
||||
| minval | 20 |
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
|
||||
@@ -1130,7 +1130,7 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
$totalpages = ceil($totalcourses / $perpage);
|
||||
if ($showtotals) {
|
||||
if ($totalpages == 0) {
|
||||
$str = get_string('nocoursesfound', 'moodle', $search);
|
||||
$str = get_string('nocoursesfound', 'moodle', s($search));
|
||||
} else if ($totalpages == 1) {
|
||||
$str = get_string('showingacourses', 'moodle', $totalcourses);
|
||||
} else {
|
||||
|
||||
+3
-2
@@ -76,11 +76,12 @@ $strdeletecoursecheck = get_string("deletecoursecheck");
|
||||
$message = "{$strdeletecoursecheck}<br /><br />{$coursefullname} ({$courseshortname})";
|
||||
|
||||
$continueurl = new moodle_url('/course/delete.php', array('id' => $course->id, 'delete' => md5($course->timemodified)));
|
||||
$continuebutton = new single_button($continueurl, get_string('delete'), 'post');
|
||||
|
||||
$PAGE->navbar->add($strdeletecheck);
|
||||
$PAGE->set_title("$SITE->shortname: $strdeletecheck");
|
||||
$PAGE->set_heading($SITE->fullname);
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->confirm($message, $continueurl, $categoryurl);
|
||||
echo $OUTPUT->confirm($message, $continuebutton, $categoryurl);
|
||||
echo $OUTPUT->footer();
|
||||
exit;
|
||||
exit;
|
||||
|
||||
+1
-1
@@ -212,7 +212,7 @@ if (!empty($activities)) {
|
||||
echo $OUTPUT->spacer(array('height'=>30, 'br'=>true)); // should be done with CSS instead
|
||||
}
|
||||
echo $OUTPUT->box_start();
|
||||
if (!empty($activity->name)) {
|
||||
if (strval($activity->name) !== '') {
|
||||
echo html_writer::tag('h2', $activity->name);
|
||||
}
|
||||
$inbox = true;
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ switch($requestmethod) {
|
||||
$module->name = clean_param($title, PARAM_CLEANHTML);
|
||||
}
|
||||
|
||||
if (!empty($module->name)) {
|
||||
if (strval($module->name) !== '') {
|
||||
$DB->update_record($cm->modname, $module);
|
||||
$cm->name = $module->name;
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
|
||||
@@ -55,7 +55,7 @@ Feature: Test we can both create and delete a course.
|
||||
# Redirect
|
||||
And I should see "Delete TCCAC"
|
||||
And I should see "Test course: create a course (TCCAC)"
|
||||
And I press "Continue"
|
||||
And I press "Delete"
|
||||
# Redirect
|
||||
And I should see "Deleting TCCAC"
|
||||
And I should see "TCCAC has been completely deleted"
|
||||
@@ -94,7 +94,7 @@ Feature: Test we can both create and delete a course.
|
||||
# Redirect
|
||||
And I should see "Delete TCCAC"
|
||||
And I should see "Test course: create a course (TCCAC)"
|
||||
And I press "Continue"
|
||||
And I press "Delete"
|
||||
# Redirect
|
||||
And I should see "Deleting TCCAC"
|
||||
And I should see "TCCAC has been completely deleted"
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Course restore tests.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2016 Frédéric Massart - FMCorz.net
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
||||
|
||||
/**
|
||||
* Course restore testcase.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2016 Frédéric Massart - FMCorz.net
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_restore_backup_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Backup a course and return its backup ID.
|
||||
*
|
||||
* @param int $courseid The course ID.
|
||||
* @param int $userid The user doing the backup.
|
||||
* @return string
|
||||
*/
|
||||
protected function backup_course($courseid, $userid = 2) {
|
||||
globaL $CFG;
|
||||
$packer = get_file_packer('application/vnd.moodle.backup');
|
||||
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $userid);
|
||||
$bc->execute_plan();
|
||||
|
||||
$results = $bc->get_results();
|
||||
$results['backup_destination']->extract_to_pathname($packer, "$CFG->tempdir/backup/core_course_testcase");
|
||||
|
||||
$bc->destroy();
|
||||
unset($bc);
|
||||
return 'core_course_testcase';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a role with capabilities and permissions.
|
||||
*
|
||||
* @param string|array $caps Capability names.
|
||||
* @param int $perm Constant CAP_* to apply to the capabilities.
|
||||
* @return int The new role ID.
|
||||
*/
|
||||
protected function create_role_with_caps($caps, $perm) {
|
||||
$caps = (array) $caps;
|
||||
$dg = $this->getDataGenerator();
|
||||
$roleid = $dg->create_role();
|
||||
foreach ($caps as $cap) {
|
||||
assign_capability($cap, $perm, $roleid, context_system::instance()->id, true);
|
||||
}
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
return $roleid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a course.
|
||||
*
|
||||
* @param int $backupid The backup ID.
|
||||
* @param int $courseid The course ID to restore in, or 0.
|
||||
* @param int $userid The ID of the user performing the restore.
|
||||
* @return stdClass The updated course object.
|
||||
*/
|
||||
protected function restore_course($backupid, $courseid, $userid) {
|
||||
global $DB;
|
||||
|
||||
$target = backup::TARGET_CURRENT_ADDING;
|
||||
if (!$courseid) {
|
||||
$target = backup::TARGET_NEW_COURSE;
|
||||
$categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
|
||||
$courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
|
||||
}
|
||||
|
||||
$rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, $target);
|
||||
$target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $rc->get_courseid()));
|
||||
|
||||
$rc->destroy();
|
||||
unset($rc);
|
||||
return $course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a course to an existing course.
|
||||
*
|
||||
* @param int $backupid The backup ID.
|
||||
* @param int $courseid The course ID to restore in.
|
||||
* @param int $userid The ID of the user performing the restore.
|
||||
* @return stdClass The updated course object.
|
||||
*/
|
||||
protected function restore_to_existing_course($backupid, $courseid, $userid = 2) {
|
||||
return $this->restore_course($backupid, $courseid, $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a course to a new course.
|
||||
*
|
||||
* @param int $backupid The backup ID.
|
||||
* @param int $userid The ID of the user performing the restore.
|
||||
* @return stdClass The new course object.
|
||||
*/
|
||||
protected function restore_to_new_course($backupid, $userid = 2) {
|
||||
return $this->restore_course($backupid, 0, $userid);
|
||||
}
|
||||
|
||||
public function test_restore_existing_idnumber_in_new_course() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$c1 = $dg->create_course(['idnumber' => 'ABC']);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
$c2 = $this->restore_to_new_course($backupid);
|
||||
|
||||
// The ID number is set empty.
|
||||
$this->assertEquals('', $c2->idnumber);
|
||||
}
|
||||
|
||||
public function test_restore_non_existing_idnumber_in_new_course() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$c1 = $dg->create_course(['idnumber' => 'ABC']);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
$c1->idnumber = 'BCD';
|
||||
$DB->update_record('course', $c1);
|
||||
|
||||
// The ID number changed.
|
||||
$c2 = $this->restore_to_new_course($backupid);
|
||||
$this->assertEquals('ABC', $c2->idnumber);
|
||||
}
|
||||
|
||||
public function test_restore_existing_idnumber_in_existing_course() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$c1 = $dg->create_course(['idnumber' => 'ABC']);
|
||||
$c2 = $dg->create_course(['idnumber' => 'DEF']);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
// The ID number does not change.
|
||||
$c2 = $this->restore_to_existing_course($backupid, $c2->id);
|
||||
$this->assertEquals('DEF', $c2->idnumber);
|
||||
|
||||
$c1 = $DB->get_record('course', array('id' => $c1->id));
|
||||
$this->assertEquals('ABC', $c1->idnumber);
|
||||
}
|
||||
|
||||
public function test_restore_non_existing_idnumber_in_existing_course() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$c1 = $dg->create_course(['idnumber' => 'ABC']);
|
||||
$c2 = $dg->create_course(['idnumber' => 'DEF']);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
$c1->idnumber = 'XXX';
|
||||
$DB->update_record('course', $c1);
|
||||
|
||||
// The ID number has changed.
|
||||
$c2 = $this->restore_to_existing_course($backupid, $c2->id);
|
||||
$this->assertEquals('ABC', $c2->idnumber);
|
||||
}
|
||||
|
||||
public function test_restore_idnumber_in_existing_course_without_permissions() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
|
||||
$managers = get_archetype_roles('manager');
|
||||
$manager = array_shift($managers);
|
||||
$roleid = $this->create_role_with_caps('moodle/course:changeidnumber', CAP_PROHIBIT);
|
||||
$dg->role_assign($manager->id, $u1->id);
|
||||
$dg->role_assign($roleid, $u1->id);
|
||||
|
||||
$c1 = $dg->create_course(['idnumber' => 'ABC']);
|
||||
$c2 = $dg->create_course(['idnumber' => 'DEF']);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
$c1->idnumber = 'XXX';
|
||||
$DB->update_record('course', $c1);
|
||||
|
||||
// The ID number does not change.
|
||||
$c2 = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
|
||||
$this->assertEquals('DEF', $c2->idnumber);
|
||||
}
|
||||
|
||||
public function test_restore_course_info_in_new_course() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
|
||||
$c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
// The information is restored but adapted because names are already taken.
|
||||
$c2 = $this->restore_to_new_course($backupid);
|
||||
$this->assertEquals('SN_1', $c2->shortname);
|
||||
$this->assertEquals('FN copy 1', $c2->fullname);
|
||||
$this->assertEquals('DESC', $c2->summary);
|
||||
$this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
|
||||
}
|
||||
|
||||
public function test_restore_course_info_in_existing_course() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
|
||||
$c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
|
||||
$c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN]);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
// The information is restored but adapted because names are already taken.
|
||||
$c2 = $this->restore_to_existing_course($backupid, $c2->id);
|
||||
$this->assertEquals('SN_1', $c2->shortname);
|
||||
$this->assertEquals('FN copy 1', $c2->fullname);
|
||||
$this->assertEquals('DESC', $c2->summary);
|
||||
$this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
|
||||
}
|
||||
|
||||
public function test_restore_course_shortname_in_existing_course_without_permissions() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
|
||||
$managers = get_archetype_roles('manager');
|
||||
$manager = array_shift($managers);
|
||||
$roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
|
||||
$dg->role_assign($manager->id, $u1->id);
|
||||
$dg->role_assign($roleid, $u1->id);
|
||||
|
||||
$c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
|
||||
$c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
|
||||
|
||||
// The shortname does not change.
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
$restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
|
||||
$this->assertEquals($c2->shortname, $restored->shortname);
|
||||
$this->assertEquals('FN copy 1', $restored->fullname);
|
||||
$this->assertEquals('DESC', $restored->summary);
|
||||
$this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
|
||||
}
|
||||
|
||||
public function test_restore_course_fullname_in_existing_course_without_permissions() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
|
||||
$managers = get_archetype_roles('manager');
|
||||
$manager = array_shift($managers);
|
||||
$roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
|
||||
$dg->role_assign($manager->id, $u1->id);
|
||||
$dg->role_assign($roleid, $u1->id);
|
||||
|
||||
$c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
|
||||
$c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
|
||||
|
||||
// The fullname does not change.
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
$restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
|
||||
$this->assertEquals('SN_1', $restored->shortname);
|
||||
$this->assertEquals($c2->fullname, $restored->fullname);
|
||||
$this->assertEquals('DESC', $restored->summary);
|
||||
$this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
|
||||
}
|
||||
|
||||
public function test_restore_course_summary_in_existing_course_without_permissions() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
|
||||
$managers = get_archetype_roles('manager');
|
||||
$manager = array_shift($managers);
|
||||
$roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
|
||||
$dg->role_assign($manager->id, $u1->id);
|
||||
$dg->role_assign($roleid, $u1->id);
|
||||
|
||||
$c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
|
||||
$c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
|
||||
|
||||
// The summary and format do not change.
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
$restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
|
||||
$this->assertEquals('SN_1', $restored->shortname);
|
||||
$this->assertEquals('FN copy 1', $restored->fullname);
|
||||
$this->assertEquals($c2->summary, $restored->summary);
|
||||
$this->assertEquals($c2->summaryformat, $restored->summaryformat);
|
||||
}
|
||||
}
|
||||
@@ -689,6 +689,8 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', M.util.get_string(newtitle, 'moodle'));
|
||||
|
||||
// Change the UI
|
||||
var oldAction = button.getData('action');
|
||||
button.replaceClass('editing_' + oldAction, 'editing_' + newtitle);
|
||||
buttonimg.setAttrs({
|
||||
'src': iconsrc
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -689,6 +689,8 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', M.util.get_string(newtitle, 'moodle'));
|
||||
|
||||
// Change the UI
|
||||
var oldAction = button.getData('action');
|
||||
button.replaceClass('editing_' + oldAction, 'editing_' + newtitle);
|
||||
buttonimg.setAttrs({
|
||||
'src': iconsrc
|
||||
});
|
||||
|
||||
+2
@@ -485,6 +485,8 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', M.util.get_string(newtitle, 'moodle'));
|
||||
|
||||
// Change the UI
|
||||
var oldAction = button.getData('action');
|
||||
button.replaceClass('editing_' + oldAction, 'editing_' + newtitle);
|
||||
buttonimg.setAttrs({
|
||||
'src': iconsrc
|
||||
});
|
||||
|
||||
+38
-9
@@ -363,7 +363,10 @@ class core_enrol_external extends external_api {
|
||||
)
|
||||
), 'Option names:
|
||||
* withcapability (string) return only users with this capability. This option requires \'moodle/role:review\' on the course context.
|
||||
* groupid (integer) return only users in this group id. This option requires \'moodle/site:accessallgroups\' on the course context.
|
||||
* groupid (integer) return only users in this group id. If the course has groups enabled and this param
|
||||
isn\'t defined, returns all the viewable users.
|
||||
This option requires \'moodle/site:accessallgroups\' on the course context if the
|
||||
user doesn\'t belong to the group.
|
||||
* onlyactive (integer) return only users with active enrolments and matching time restrictions. This option requires \'moodle/course:enrolreview\' on the course context.
|
||||
* userfields (\'string, string, ...\') return only the values of these user fields.
|
||||
* limitfrom (integer) sql limit from.
|
||||
@@ -451,7 +454,7 @@ class core_enrol_external extends external_api {
|
||||
require_capability('moodle/role:review', $coursecontext);
|
||||
}
|
||||
// need accessallgroups capability if you want to overwrite this option
|
||||
if (!empty($groupid) && groups_is_member($groupid)) {
|
||||
if (!empty($groupid) && !groups_is_member($groupid)) {
|
||||
require_capability('moodle/site:accessallgroups', $coursecontext);
|
||||
}
|
||||
// to overwrite this option, you need course:enrolereview permission
|
||||
@@ -463,10 +466,29 @@ class core_enrol_external extends external_api {
|
||||
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
|
||||
$ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)";
|
||||
$enrolledparams['contextlevel'] = CONTEXT_USER;
|
||||
$sql = "SELECT u.* $ctxselect
|
||||
FROM {user} u $ctxjoin
|
||||
WHERE u.id IN ($enrolledsql)
|
||||
ORDER BY u.id ASC";
|
||||
|
||||
$groupjoin = '';
|
||||
if (empty($groupid) && groups_get_course_groupmode($course) == SEPARATEGROUPS &&
|
||||
!has_capability('moodle/site:accessallgroups', $coursecontext)) {
|
||||
// Filter by groups the user can view.
|
||||
$usergroups = groups_get_user_groups($course->id);
|
||||
if (!empty($usergroups['0'])) {
|
||||
list($groupsql, $groupparams) = $DB->get_in_or_equal($usergroups['0'], SQL_PARAMS_NAMED);
|
||||
$groupjoin = "JOIN {groups_members} gm ON (u.id = gm.userid AND gm.groupid $groupsql)";
|
||||
$enrolledparams = array_merge($enrolledparams, $groupparams);
|
||||
} else {
|
||||
// User doesn't belong to any group, so he can't see any user. Return an empty array.
|
||||
return array();
|
||||
}
|
||||
}
|
||||
$sql = "SELECT us.*
|
||||
FROM {user} us
|
||||
JOIN (
|
||||
SELECT DISTINCT u.id $ctxselect
|
||||
FROM {user} u $ctxjoin $groupjoin
|
||||
WHERE u.id IN ($enrolledsql)
|
||||
) q ON q.id = us.id
|
||||
ORDER BY us.id ASC";
|
||||
$enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams, $limitfrom, $limitnumber);
|
||||
$users = array();
|
||||
foreach ($enrolledusers as $user) {
|
||||
@@ -581,14 +603,21 @@ class core_enrol_external extends external_api {
|
||||
*
|
||||
* @param int $courseid
|
||||
* @return array of course enrolment methods
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_course_enrolment_methods($courseid) {
|
||||
global $DB;
|
||||
|
||||
$params = self::validate_parameters(self::get_course_enrolment_methods_parameters(), array('courseid' => $courseid));
|
||||
|
||||
$coursecontext = context_course::instance($params['courseid']);
|
||||
$categorycontext = $coursecontext->get_parent_context();
|
||||
self::validate_context($categorycontext);
|
||||
// Note that we can't use validate_context because the user is not enrolled in the course.
|
||||
require_login(null, false, null, false, true);
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
|
||||
$context = context_course::instance($course->id);
|
||||
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
|
||||
throw new moodle_exception('coursehidden');
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$enrolinstances = enrol_get_instances($params['courseid'], true);
|
||||
|
||||
@@ -446,7 +446,7 @@ class enrol_flatfile_plugin extends enrol_plugin {
|
||||
$notify = false;
|
||||
if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$user->id))) {
|
||||
// Update only.
|
||||
$this->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, $roleid, $timestart, $timeend);
|
||||
$this->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, $timestart, $timeend);
|
||||
if (!$DB->record_exists('role_assignments', array('contextid'=>$context->id, 'roleid'=>$roleid, 'userid'=>$user->id, 'component'=>'enrol_flatfile', 'itemid'=>$instance->id))) {
|
||||
role_assign($roleid, $user->id, $context->id, 'enrol_flatfile', $instance->id);
|
||||
}
|
||||
|
||||
+7
-6
@@ -1091,13 +1091,14 @@ class course_enrolment_manager {
|
||||
*/
|
||||
private function prepare_user_for_display($user, $extrafields, $now) {
|
||||
$details = array(
|
||||
'userid' => $user->id,
|
||||
'courseid' => $this->get_course()->id,
|
||||
'picture' => new user_picture($user),
|
||||
'firstname' => fullname($user, has_capability('moodle/site:viewfullnames', $this->get_context())),
|
||||
'lastseen' => get_string('never'),
|
||||
'lastcourseaccess' => get_string('never'),
|
||||
'userid' => $user->id,
|
||||
'courseid' => $this->get_course()->id,
|
||||
'picture' => new user_picture($user),
|
||||
'userfullnamedisplay' => fullname($user, has_capability('moodle/site:viewfullnames', $this->get_context())),
|
||||
'lastseen' => get_string('never'),
|
||||
'lastcourseaccess' => get_string('never'),
|
||||
);
|
||||
|
||||
foreach ($extrafields as $field) {
|
||||
$details[$field] = $user->{$field};
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ navigation_node::override_active_url(new moodle_url('/enrol/otherusers.php', arr
|
||||
|
||||
$userdetails = array (
|
||||
'picture' => false,
|
||||
'userfullnamedisplay' => false,
|
||||
'firstname' => get_string('firstname'),
|
||||
'lastname' => get_string('lastname'),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Empty enrol_self form.
|
||||
*
|
||||
* Useful to mimic valid enrol instances UI when the enrolment instance is not available.
|
||||
*
|
||||
* @package enrol_self
|
||||
* @copyright 2015 David Monllaó
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
|
||||
class enrol_self_empty_form extends moodleform {
|
||||
|
||||
/**
|
||||
* Form definition.
|
||||
* @return void
|
||||
*/
|
||||
public function definition() {
|
||||
$this->_form->addElement('header', 'selfheader', $this->_customdata->header);
|
||||
$this->_form->addElement('static', 'info', '', $this->_customdata->info);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ class enrol_self_external extends external_api {
|
||||
*
|
||||
* @param int $instanceid instance id of self enrolment plugin.
|
||||
* @return array instance information.
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_instance_info($instanceid) {
|
||||
global $DB, $CFG;
|
||||
@@ -66,10 +67,15 @@ class enrol_self_external extends external_api {
|
||||
throw new moodle_exception('invaliddata', 'error');
|
||||
}
|
||||
|
||||
// Note that we can't use validate_context because the user is not enrolled in the course.
|
||||
require_login(null, false, null, false, true);
|
||||
|
||||
$enrolinstance = $DB->get_record('enrol', array('id' => $params['instanceid']), '*', MUST_EXIST);
|
||||
$coursecontext = context_course::instance($enrolinstance->courseid);
|
||||
$categorycontext = $coursecontext->get_parent_context();
|
||||
self::validate_context($categorycontext);
|
||||
$course = $DB->get_record('course', array('id' => $enrolinstance->courseid), '*', MUST_EXIST);
|
||||
$context = context_course::instance($course->id);
|
||||
if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
|
||||
throw new moodle_exception('coursehidden');
|
||||
}
|
||||
|
||||
$instanceinfo = (array) $enrolplugin->get_enrol_info($enrolinstance);
|
||||
if (isset($instanceinfo['requiredparam']->enrolpassword)) {
|
||||
|
||||
@@ -95,9 +95,9 @@ $string['sendcoursewelcomemessage'] = 'Send course welcome message';
|
||||
$string['sendcoursewelcomemessage_help'] = 'If enabled, users receive a welcome message via email when they self-enrol in a course.';
|
||||
$string['showhint'] = 'Show hint';
|
||||
$string['showhint_desc'] = 'Show first letter of the guest access key.';
|
||||
$string['status'] = 'Enable existing enrolments';
|
||||
$string['status'] = 'Allow existing enrolments';
|
||||
$string['status_desc'] = 'Enable self enrolment method in new courses.';
|
||||
$string['status_help'] = 'If disabled all existing self enrolments are suspended and new users can not enrol.';
|
||||
$string['status_help'] = 'If enabled together with \'Allow new enrolments\' disabled, only users who self enrolled previously can access the course. If disabled, this self enrolment method is effectively disabled, since all existing self enrolments are suspended and new users cannot self enrol.';
|
||||
$string['unenrol'] = 'Unenrol user';
|
||||
$string['unenrolselfconfirm'] = 'Do you really want to unenrol yourself from course "{$a}"?';
|
||||
$string['unenroluser'] = 'Do you really want to unenrol "{$a->user}" from course "{$a->course}"?';
|
||||
|
||||
+16
-7
@@ -234,8 +234,8 @@ class enrol_self_plugin extends enrol_plugin {
|
||||
|
||||
$enrolstatus = $this->can_self_enrol($instance);
|
||||
|
||||
// Don't show enrolment instance form, if user can't enrol using it.
|
||||
if (true === $enrolstatus) {
|
||||
// This user can self enrol using this instance.
|
||||
$form = new enrol_self_enrol_form(NULL, $instance);
|
||||
$instanceid = optional_param('instance', 0, PARAM_INT);
|
||||
if ($instance->id == $instanceid) {
|
||||
@@ -243,14 +243,23 @@ class enrol_self_plugin extends enrol_plugin {
|
||||
$this->enrol_self($instance, $data);
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$form->display();
|
||||
$output = ob_get_clean();
|
||||
return $OUTPUT->box($output);
|
||||
} else {
|
||||
return $OUTPUT->box($enrolstatus);
|
||||
// This user can not self enrol using this instance. Using an empty form to keep
|
||||
// the UI consistent with other enrolment plugins that returns a form.
|
||||
$data = new stdClass();
|
||||
$data->header = $this->get_instance_name($instance);
|
||||
$data->info = $enrolstatus;
|
||||
|
||||
// The can_self_enrol call returns a button to the login page if the user is a
|
||||
// guest, setting the login url to the form if that is the case.
|
||||
$url = isguestuser() ? get_login_url() : null;
|
||||
$form = new enrol_self_empty_form($url, $data);
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$form->display();
|
||||
$output = ob_get_clean();
|
||||
return $OUTPUT->box($output);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
|
||||
And I log out
|
||||
When I follow "Course 1"
|
||||
And I press "Log in as a guest"
|
||||
Then I should see "Guests can not access this course, please try to log in."
|
||||
Then I should see "Guests cannot access this course. Please log in."
|
||||
And I press "Continue"
|
||||
And I should see "Log in"
|
||||
|
||||
|
||||
@@ -47,7 +47,9 @@ class enrol_self_external_testcase extends externallib_advanced_testcase {
|
||||
$studentrole = $DB->get_record('role', array('shortname'=>'student'));
|
||||
$this->assertNotEmpty($studentrole);
|
||||
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$coursedata = new stdClass();
|
||||
$coursedata->visible = 0;
|
||||
$course = self::getDataGenerator()->create_course($coursedata);
|
||||
|
||||
// Add enrolment methods for course.
|
||||
$instanceid1 = $selfplugin->add_instance($course, array('status' => ENROL_INSTANCE_ENABLED,
|
||||
@@ -68,6 +70,7 @@ class enrol_self_external_testcase extends externallib_advanced_testcase {
|
||||
$enrolmentmethods = $DB->get_records('enrol', array('courseid' => $course->id, 'status' => ENROL_INSTANCE_ENABLED));
|
||||
$this->assertCount(3, $enrolmentmethods);
|
||||
|
||||
$this->setAdminUser();
|
||||
$instanceinfo1 = enrol_self_external::get_instance_info($instanceid1);
|
||||
$instanceinfo1 = external_api::clean_returnvalue(enrol_self_external::get_instance_info_returns(), $instanceinfo1);
|
||||
|
||||
@@ -95,5 +98,14 @@ class enrol_self_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals('Test instance 3', $instanceinfo3['name']);
|
||||
$this->assertTrue($instanceinfo3['status']);
|
||||
$this->assertEquals(get_string('password', 'enrol_self'), $instanceinfo3['enrolpassword']);
|
||||
|
||||
// Try to retrieve information using a normal user for a hidden course.
|
||||
$user = self::getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
try {
|
||||
enrol_self_external::get_instance_info($instanceid3);
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals('coursehidden', $e->errorcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+504
-130
@@ -33,59 +33,325 @@ require_once($CFG->dirroot . '/enrol/externallib.php');
|
||||
class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users
|
||||
* dataProvider for test_get_enrolled_users_visibility().
|
||||
*/
|
||||
public function test_get_enrolled_users() {
|
||||
public function get_enrolled_users_visibility_provider() {
|
||||
return array(
|
||||
'Course without groups, default behavior (not filtering by cap, group, active)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => NOGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Everybody can view everybody.
|
||||
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with visible groups, default behavior (not filtering by cap, group, active)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => VISIBLEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Everybody can view everybody.
|
||||
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, default behavior (not filtering by cap, group, active)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Only users from own groups are visible.
|
||||
'user0' => array('canview' => array()), // Poor guy, cannot see anybody, himself included.
|
||||
'user1' => array('canview' => array('user1', 'userall')),
|
||||
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'user31' => array('canview' => array('user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, default behavior (not filtering but having moodle/site:accessallgroups)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => VISIBLEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/site:accessallgroups'),
|
||||
),
|
||||
'results' => array( // Everybody can view everybody.
|
||||
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering onlyactive (missing moodle/course:enrolreview)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => true,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // returns exception, cannot view anybody without the cap.
|
||||
'user2' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review course enrolments')),
|
||||
'userall' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review course enrolments')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering onlyactive (having moodle/course:enrolreview)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => true,
|
||||
'allowedcaps' => array('moodle/course:enrolreview'),
|
||||
),
|
||||
'results' => array( // Suspended are not returned.
|
||||
'user2' => array('canview' => array('user2', 'userall')),
|
||||
'user31' => array('canview' => array('user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user1', 'user2', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by groupid (not having moodle/site:accessallgroups)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => 'group2',
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Only group 2 members and only for members. Exception for non-members.
|
||||
'user0' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Access all groups')),
|
||||
'user1' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Access all groups')),
|
||||
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'userall' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by groupid (having moodle/site:accessallgroups)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => 'group2',
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/site:accessallgroups'),
|
||||
),
|
||||
'results' => array( // All users with 'moodle/site:accessallgroups' can view group 2
|
||||
'user0' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'user1' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'userall' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by withcapability (not having moodle/role:review)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => 'moodle/course:bulkmessaging',
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // No user has 'moodle/role:review' so exception.
|
||||
'user0' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
'user1' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
'user2' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
'userall' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => 'moodle/course:bulkmessaging',
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/role:review'),
|
||||
),
|
||||
'results' => array( // No user has withcapability, but all have 'moodle/role:review'. Empties.
|
||||
'user0' => array('canview' => array()),
|
||||
'user1' => array('canview' => array()),
|
||||
'user2' => array('canview' => array()),
|
||||
'userall' => array('canview' => array()),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => 'moodle/course:bulkmessaging',
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/role:review', 'moodle/course:bulkmessaging'),
|
||||
),
|
||||
'results' => array( // Users (previous) have withcapability, and all have 'moodle/role:review'.
|
||||
'user0' => array('canview' => array()),
|
||||
'user1' => array('canview' => array('user1')),
|
||||
'user2' => array('canview' => array('user2')),
|
||||
'userall' => array('canview' => array('user1', 'user2', 'userall')),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify get_enrolled_users() returned users are the expected in every situation.
|
||||
*
|
||||
* @dataProvider get_enrolled_users_visibility_provider
|
||||
*/
|
||||
public function test_get_enrolled_users_visibility($settings, $results) {
|
||||
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$user1 = self::getDataGenerator()->create_user();
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
$user3 = self::getDataGenerator()->create_user();
|
||||
$this->setUser($user3);
|
||||
// Create the course and the users.
|
||||
$course = $this->getDataGenerator()->create_course(array('groupmode' => $settings['coursegroupmode']));
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$user0 = $this->getDataGenerator()->create_user(array('username' => 'user0')); // A user without group.
|
||||
$user1 = $this->getDataGenerator()->create_user(array('username' => 'user1')); // User for group 1.
|
||||
$user2 = $this->getDataGenerator()->create_user(array('username' => 'user2')); // Two users for group 2.
|
||||
$user2su = $this->getDataGenerator()->create_user(array('username' => 'user2su')); // (one suspended).
|
||||
$user31 = $this->getDataGenerator()->create_user(array('username' => 'user31')); // Two users for group 3.
|
||||
$user32 = $this->getDataGenerator()->create_user(array('username' => 'user32')); // (both enabled).
|
||||
$userall = $this->getDataGenerator()->create_user(array('username' => 'userall')); // A user in all groups.
|
||||
|
||||
// Set the required capabilities by the external function.
|
||||
$context = context_course::instance($course->id);
|
||||
$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id);
|
||||
$this->assignUserCapability('moodle/user:viewdetails', $context->id, $roleid);
|
||||
// Create utility array of created users, to produce better assertion messages.
|
||||
$createdusers = array();
|
||||
foreach (array($user0, $user1, $user2, $user2su, $user31, $user32, $userall) as $createduser) {
|
||||
$createdusers[$createduser->id] = $createduser->username;
|
||||
}
|
||||
|
||||
// Enrol the users in the course.
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course->id, $roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($user3->id, $course->id, $roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($user0->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
|
||||
$this->getDataGenerator()->enrol_user($user31->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user32->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($userall->id, $course->id);
|
||||
|
||||
// Call the external function.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($course->id);
|
||||
// Create 3 groups.
|
||||
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
$group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
// Add the users to the groups.
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2su->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user31->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user32->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $userall->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $userall->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $userall->id));
|
||||
|
||||
// Check the result set.
|
||||
$this->assertEquals(3, count($enrolledusers));
|
||||
$this->assertArrayHasKey('email', $enrolledusers[0]);
|
||||
// Create a role to add the allowedcaps. Users will have this role assigned.
|
||||
$roleid = $this->getDataGenerator()->create_role();
|
||||
// Allow the specified capabilities.
|
||||
if (!empty($settings['allowedcaps'])) {
|
||||
foreach ($settings['allowedcaps'] as $capability) {
|
||||
assign_capability($capability, CAP_ALLOW, $roleid, $coursecontext);
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function with some parameters set.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($course->id, array(
|
||||
array('name' => 'limitfrom', 'value' => 2),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
));
|
||||
// For each of the users, configure everything, perform the call, and assert results.
|
||||
foreach ($results as $user => $expectations) {
|
||||
// Convert canview expectations into a nice array of ids for easier handling.
|
||||
$canview = array();
|
||||
$exception = null;
|
||||
// Analyse the expectations.
|
||||
if (isset($expectations['canview'])) {
|
||||
foreach ($expectations['canview'] as $canviewuser) {
|
||||
$canview[] = $createdusers[${$canviewuser}->id];
|
||||
}
|
||||
} else if (isset($expectations['exception'])) {
|
||||
$exception = $expectations['exception'];
|
||||
$this->setExpectedException($exception['type'], $exception['message']);
|
||||
} else {
|
||||
// Failed, only canview and exception are supported.
|
||||
$this->markTestIncomplete('Incomplete, only canview and exception are supported');
|
||||
}
|
||||
// Switch to the user and assign the role.
|
||||
$this->setUser(${$user});
|
||||
role_assign($roleid, $USER->id, $coursecontext);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
// Convert groupid to proper id.
|
||||
$groupid = 0;
|
||||
if (isset($settings['groupid'])) {
|
||||
$groupid = ${$settings['groupid']}->id;
|
||||
}
|
||||
|
||||
// Check the result set, we should only get the 3rd result, which is $user3.
|
||||
$this->assertCount(1, $enrolledusers);
|
||||
$this->assertEquals($user3->id, $enrolledusers[0]['id']);
|
||||
$this->assertArrayHasKey('id', $enrolledusers[0]);
|
||||
$this->assertArrayNotHasKey('email', $enrolledusers[0]);
|
||||
// Call to the function.
|
||||
$options = array(
|
||||
array('name' => 'withcapability', 'value' => $settings['withcapability']),
|
||||
array('name' => 'groupid', 'value' => $groupid),
|
||||
array('name' => 'onlyactive', 'value' => $settings['onlyactive']),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
);
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($course->id, $options);
|
||||
|
||||
// Call without required capability.
|
||||
$this->unassignUserCapability('moodle/course:viewparticipants', $context->id, $roleid);
|
||||
$this->setExpectedException('moodle_exception');
|
||||
$categories = core_enrol_external::get_enrolled_users($course->id);
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
|
||||
// We are only interested in ids to check visibility.
|
||||
$viewed = array();
|
||||
// Verify the user canview the expected users.
|
||||
foreach ($enrolledusers as $enrolleduser) {
|
||||
$viewed[] = $createdusers[$enrolleduser['id']];
|
||||
}
|
||||
// Verify viewed matches canview expectation (using canonicalize to ignore ordering).
|
||||
$this->assertEquals($canview, $viewed, "Problem checking visible users for '{$createdusers[$USER->id]}'", 0, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,96 +387,6 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals(2, count($enrolledincourses));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users_with_capability
|
||||
*/
|
||||
public function test_get_enrolled_users_with_capability () {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$coursedata['idnumber'] = 'idnumbercourse1';
|
||||
$coursedata['fullname'] = 'Lightwork Course 1';
|
||||
$coursedata['summary'] = 'Lightwork Course 1 description';
|
||||
$coursedata['summaryformat'] = FORMAT_MOODLE;
|
||||
$course1 = self::getDataGenerator()->create_course($coursedata);
|
||||
|
||||
// Create a manual enrolment record.
|
||||
$manual_enrol_data['enrol'] = 'manual';
|
||||
$manual_enrol_data['status'] = 0;
|
||||
$manual_enrol_data['courseid'] = $course1->id;
|
||||
$enrolid = $DB->insert_record('enrol', $manual_enrol_data);
|
||||
|
||||
// Create the users and give them capabilities in the course context.
|
||||
$context = context_course::instance($course1->id);
|
||||
$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
|
||||
|
||||
// Create 2 students.
|
||||
$student1 = self::getDataGenerator()->create_user();
|
||||
$student2 = self::getDataGenerator()->create_user();
|
||||
|
||||
// Give the capability to student2.
|
||||
assign_capability('moodle/course:viewparticipants', CAP_ALLOW, 3, $context->id);
|
||||
role_assign(3, $student2->id, $context->id);
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
|
||||
// Enrol both the user and the students in the course.
|
||||
$user_enrolment_data['status'] = 0;
|
||||
$user_enrolment_data['enrolid'] = $enrolid;
|
||||
$user_enrolment_data['userid'] = $USER->id;
|
||||
$DB->insert_record('user_enrolments', $user_enrolment_data);
|
||||
|
||||
$user_enrolment_data['status'] = 0;
|
||||
$user_enrolment_data['enrolid'] = $enrolid;
|
||||
$user_enrolment_data['userid'] = $student1->id;
|
||||
$DB->insert_record('user_enrolments', $user_enrolment_data);
|
||||
|
||||
$user_enrolment_data['status'] = 0;
|
||||
$user_enrolment_data['enrolid'] = $enrolid;
|
||||
$user_enrolment_data['userid'] = $student2->id;
|
||||
$DB->insert_record('user_enrolments', $user_enrolment_data);
|
||||
|
||||
$params = array("coursecapabilities" => array('courseid' => $course1->id,
|
||||
'capabilities' => array('moodle/course:viewparticipants')));
|
||||
$options = array();
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability($params, $options);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0];
|
||||
$this->assertEquals($course1->id, $expecteduserlist['courseid']);
|
||||
$this->assertEquals('moodle/course:viewparticipants', $expecteduserlist['capability']);
|
||||
$this->assertEquals(2, count($expecteduserlist['users']));
|
||||
|
||||
// Now doing the query again with options.
|
||||
$params = array(
|
||||
"coursecapabilities" => array(
|
||||
'courseid' => $course1->id,
|
||||
'capabilities' => array('moodle/course:viewparticipants')
|
||||
)
|
||||
);
|
||||
$options = array(
|
||||
array('name' => 'limitfrom', 'value' => 1),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
);
|
||||
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability($params, $options);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0]['users'];
|
||||
$expecteduser = reset($expecteduserlist);
|
||||
$this->assertEquals(1, count($expecteduserlist));
|
||||
$this->assertEquals($student2->id, $expecteduser['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_course_enrolment_methods
|
||||
*/
|
||||
@@ -229,7 +405,9 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertNotEmpty($studentrole);
|
||||
|
||||
$course1 = self::getDataGenerator()->create_course();
|
||||
$course2 = self::getDataGenerator()->create_course();
|
||||
$coursedata = new stdClass();
|
||||
$coursedata->visible = 0;
|
||||
$course2 = self::getDataGenerator()->create_course($coursedata);
|
||||
|
||||
// Add enrolment methods for course.
|
||||
$instanceid1 = $selfplugin->add_instance($course1, array('status' => ENROL_INSTANCE_ENABLED,
|
||||
@@ -246,6 +424,8 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
$enrolmentmethods = $DB->get_records('enrol', array('courseid' => $course1->id, 'status' => ENROL_INSTANCE_ENABLED));
|
||||
$this->assertCount(2, $enrolmentmethods);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Check if information is returned.
|
||||
$enrolmentmethods = core_enrol_external::get_course_enrolment_methods($course1->id);
|
||||
$enrolmentmethods = external_api::clean_returnvalue(core_enrol_external::get_course_enrolment_methods_returns(),
|
||||
@@ -275,5 +455,199 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals('self', $enrolmentmethod['type']);
|
||||
$this->assertTrue($enrolmentmethod['status']);
|
||||
$this->assertEquals('enrol_self_get_instance_info', $enrolmentmethod['wsfunction']);
|
||||
|
||||
// Try to retrieve information using a normal user for a hidden course.
|
||||
$user = self::getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
try {
|
||||
core_enrol_external::get_course_enrolment_methods($course2->id);
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals('coursehidden', $e->errorcode);
|
||||
}
|
||||
}
|
||||
|
||||
public function get_enrolled_users_setup($capability) {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$return = new stdClass();
|
||||
|
||||
$return->course = self::getDataGenerator()->create_course();
|
||||
$return->user1 = self::getDataGenerator()->create_user();
|
||||
$return->user2 = self::getDataGenerator()->create_user();
|
||||
$return->user3 = self::getDataGenerator()->create_user();
|
||||
$this->setUser($return->user3);
|
||||
|
||||
// Set the required capabilities by the external function.
|
||||
$return->context = context_course::instance($return->course->id);
|
||||
$return->roleid = $this->assignUserCapability($capability, $return->context->id);
|
||||
$this->assignUserCapability('moodle/user:viewdetails', $return->context->id, $return->roleid);
|
||||
|
||||
// Enrol the users in the course.
|
||||
$this->getDataGenerator()->enrol_user($return->user1->id, $return->course->id, $return->roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($return->user2->id, $return->course->id, $return->roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($return->user3->id, $return->course->id, $return->roleid, 'manual');
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users from core_enrol_external without additional
|
||||
* parameters.
|
||||
*/
|
||||
public function test_get_enrolled_users_without_parameters() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_setup($capability);
|
||||
|
||||
// Call the external function.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
|
||||
// Check the result set.
|
||||
$this->assertEquals(3, count($enrolledusers));
|
||||
$this->assertArrayHasKey('email', $enrolledusers[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users from core_enrol_external with some parameters set.
|
||||
*/
|
||||
public function test_get_enrolled_users_with_parameters() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_setup($capability);
|
||||
|
||||
// Call the function with some parameters set.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($data->course->id, array(
|
||||
array('name' => 'limitfrom', 'value' => 2),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
));
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
|
||||
// Check the result set, we should only get the 3rd result, which is $user3.
|
||||
$this->assertCount(1, $enrolledusers);
|
||||
$this->assertEquals($data->user3->id, $enrolledusers[0]['id']);
|
||||
$this->assertArrayHasKey('id', $enrolledusers[0]);
|
||||
$this->assertArrayNotHasKey('email', $enrolledusers[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users from core_enrol_external with capability to
|
||||
* viewparticipants removed.
|
||||
*/
|
||||
public function test_get_enrolled_users_without_capability() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_setup($capability);
|
||||
|
||||
// Call without required capability.
|
||||
$this->unassignUserCapability($capability, $data->context->id, $data->roleid);
|
||||
$this->setExpectedException('moodle_exception');
|
||||
$categories = core_enrol_external::get_enrolled_users($data->course->id);
|
||||
}
|
||||
|
||||
public function get_enrolled_users_with_capability_setup($capability) {
|
||||
global $USER, $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$return = new stdClass();
|
||||
|
||||
// Create the course and fetch its context.
|
||||
$return->course = self::getDataGenerator()->create_course();
|
||||
$context = context_course::instance($return->course->id);
|
||||
|
||||
// Create one teacher, and two students.
|
||||
$return->teacher = self::getDataGenerator()->create_user();
|
||||
$return->student1 = self::getDataGenerator()->create_user();
|
||||
$return->student2 = self::getDataGenerator()->create_user();
|
||||
|
||||
// Create a new student role based on the student archetype but with the capability prohibitted.
|
||||
$fakestudentroleid = create_role('Fake student role', 'fakestudent', 'Fake student role', 'student');
|
||||
assign_capability($capability, CAP_PROHIBIT, $fakestudentroleid, $context->id);
|
||||
|
||||
// Enrol all of the users in the course.
|
||||
// * 'teacher' is an editing teacher.
|
||||
// * 'student1' is a standard student.
|
||||
// * 'student2' is a student with the capability prohibitted.
|
||||
$editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
||||
$studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
|
||||
$this->getDataGenerator()->enrol_user($return->teacher->id, $return->course->id, $editingteacherroleid);
|
||||
$this->getDataGenerator()->enrol_user($return->student1->id, $return->course->id, $studentroleid);
|
||||
$this->getDataGenerator()->enrol_user($return->student2->id, $return->course->id, $fakestudentroleid);
|
||||
|
||||
// Log in as the teacher.
|
||||
$this->setUser($return->teacher);
|
||||
|
||||
// Clear caches.
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users_with_capability without additional paramaters.
|
||||
*/
|
||||
public function test_get_enrolled_users_with_capability_without_parameters() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_with_capability_setup($capability);
|
||||
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability(
|
||||
array(
|
||||
'coursecapabilities' => array(
|
||||
'courseid' => $data->course->id,
|
||||
'capabilities' => array(
|
||||
$capability,
|
||||
),
|
||||
),
|
||||
),
|
||||
array()
|
||||
);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0];
|
||||
$this->assertEquals($data->course->id, $expecteduserlist['courseid']);
|
||||
$this->assertEquals($capability, $expecteduserlist['capability']);
|
||||
$this->assertEquals(2, count($expecteduserlist['users']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users_with_capability
|
||||
*/
|
||||
public function test_get_enrolled_users_with_capability_with_parameters () {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_with_capability_setup($capability);
|
||||
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability(
|
||||
array(
|
||||
'coursecapabilities' => array(
|
||||
'courseid' => $data->course->id,
|
||||
'capabilities' => array(
|
||||
$capability,
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
array('name' => 'limitfrom', 'value' => 1),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
)
|
||||
);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0]['users'];
|
||||
$expecteduser = reset($expecteduserlist);
|
||||
$this->assertEquals(1, count($expecteduserlist));
|
||||
$this->assertEquals($data->student1->id, $expecteduser['id']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
This files describes API changes in /enrol/* - plugins,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 2.8.9 ===
|
||||
|
||||
* External function core_enrol_external::get_enrolled_users now returns only the viewable participants if the course has groups enabled and no groupid is passed.
|
||||
|
||||
=== 2.8 ===
|
||||
|
||||
* enrol_plugin::instance_deleteable() is deprecated and has been replaced by enrol_plugin::can_delete_instance()
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ if ($action) {
|
||||
|
||||
|
||||
$renderer = $PAGE->get_renderer('core_enrol');
|
||||
$userdetails = array('picture' => false);
|
||||
$userdetails = array('picture' => false, 'userfullnamedisplay' => false);
|
||||
// Get all the user names in a reasonable default order.
|
||||
$allusernames = get_all_user_name_fields(false, null, null, null, true);
|
||||
// Initialise the variable for the user's names in the table header.
|
||||
|
||||
+4
-1
@@ -1015,7 +1015,10 @@ class core_files_renderer extends plugin_renderer_base {
|
||||
<label class="control-label"></label>
|
||||
|
||||
<div class="controls"><select></select></div>
|
||||
</div>
|
||||
</div>';
|
||||
// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
|
||||
$rv .= prevent_form_autofill_password();
|
||||
$rv .= '
|
||||
<div class="fp-login-input control-group clearfix">
|
||||
<label class="control-label"></label>
|
||||
<div class="controls"><input/></div>
|
||||
|
||||
@@ -68,7 +68,6 @@ class grade_export_xml extends grade_export {
|
||||
foreach ($userdata->grades as $itemid => $grade) {
|
||||
$grade_item = $this->grade_items[$itemid];
|
||||
$grade->grade_item =& $grade_item;
|
||||
$gradestr = $this->format_grade($grade, $this->displaytype); // no formating for now
|
||||
|
||||
// MDL-11669, skip exported grades or bad grades (if setting says so)
|
||||
if ($export_tracking) {
|
||||
@@ -88,7 +87,19 @@ class grade_export_xml extends grade_export {
|
||||
fwrite($handle, "\t\t<assignment>{$grade_item->idnumber}</assignment>\n");
|
||||
// this column should be customizable to use either student id, idnumber, uesrname or email.
|
||||
fwrite($handle, "\t\t<student>{$user->idnumber}</student>\n");
|
||||
fwrite($handle, "\t\t<score>$gradestr</score>\n");
|
||||
// Format and display the grade in the selected display type (real, letter, percentage).
|
||||
if (is_array($this->displaytype)) {
|
||||
// Grades display type came from the return of export_bulk_export_data() on grade publishing.
|
||||
foreach ($this->displaytype as $gradedisplayconst) {
|
||||
$gradestr = $this->format_grade($grade, $gradedisplayconst);
|
||||
fwrite($handle, "\t\t<score>$gradestr</score>\n");
|
||||
}
|
||||
} else {
|
||||
// Grade display type submitted directly from the grade export form.
|
||||
$gradestr = $this->format_grade($grade, $this->displaytype);
|
||||
fwrite($handle, "\t\t<score>$gradestr</score>\n");
|
||||
}
|
||||
|
||||
if ($this->export_feedback) {
|
||||
$feedbackstr = $this->format_feedback($userdata->feedbacks[$itemid]);
|
||||
fwrite($handle, "\t\t<feedback>$feedbackstr</feedback>\n");
|
||||
|
||||
@@ -185,7 +185,7 @@ class grading_manager {
|
||||
} else if ($this->get_context()->contextlevel >= CONTEXT_COURSE) {
|
||||
list($context, $course, $cm) = get_context_info_array($this->get_context()->id);
|
||||
|
||||
if (!empty($cm->name)) {
|
||||
if (strval($cm->name) !== '') {
|
||||
$title = $cm->name;
|
||||
} else {
|
||||
debugging('Gradable areas are currently supported at the course module level only', DEBUG_DEVELOPER);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require_once(__DIR__ . "../../../../config.php");
|
||||
require_once(__DIR__ . "/../../../config.php");
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
require_once($CFG->dirroot.'/grade/lib.php');
|
||||
require_once($CFG->dirroot.'/grade/import/lib.php');
|
||||
|
||||
+1
-3
@@ -308,6 +308,7 @@ class graded_users_iterator {
|
||||
$grades[$grade_item->id] =
|
||||
new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false);
|
||||
}
|
||||
$grades[$grade_item->id]->grade_item = $grade_item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3120,9 +3121,6 @@ abstract class grade_helper {
|
||||
JOIN {user_info_category} c ON f.categoryid=c.id
|
||||
WHERE f.shortname $wherefields
|
||||
ORDER BY c.sortorder ASC, f.sortorder ASC", $whereparams);
|
||||
if (!is_array($customfields)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($customfields as $field) {
|
||||
// Make sure we can display this custom field
|
||||
|
||||
@@ -741,7 +741,8 @@ class grade_report_grader extends grade_report {
|
||||
'cfg' => array('ajaxenabled'=>false),
|
||||
'items' => array(),
|
||||
'users' => array(),
|
||||
'feedback' => array()
|
||||
'feedback' => array(),
|
||||
'grades' => array()
|
||||
);
|
||||
$jsscales = array();
|
||||
|
||||
@@ -993,6 +994,14 @@ class grade_report_grader extends grade_report {
|
||||
|
||||
} else if ($USER->gradeediting[$this->courseid]) {
|
||||
|
||||
if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
|
||||
$itemcell->attributes['class'] .= ' grade_type_scale';
|
||||
} else if ($item->gradetype == GRADE_TYPE_VALUE) {
|
||||
$itemcell->attributes['class'] .= ' grade_type_value';
|
||||
} else if ($item->gradetype == GRADE_TYPE_TEXT) {
|
||||
$itemcell->attributes['class'] .= ' grade_type_text';
|
||||
}
|
||||
|
||||
if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
|
||||
$scale = $scalesarray[$item->scaleid];
|
||||
$gradeval = (int)$gradeval; // scales use only integers
|
||||
@@ -1062,16 +1071,16 @@ class grade_report_grader extends grade_report {
|
||||
|
||||
if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
|
||||
$itemcell->attributes['class'] .= ' grade_type_scale';
|
||||
} else if ($item->gradetype != GRADE_TYPE_TEXT) {
|
||||
} else if ($item->gradetype == GRADE_TYPE_VALUE) {
|
||||
$itemcell->attributes['class'] .= ' grade_type_value';
|
||||
} else if ($item->gradetype == GRADE_TYPE_TEXT) {
|
||||
$itemcell->attributes['class'] .= ' grade_type_text';
|
||||
}
|
||||
|
||||
if ($enableajax) {
|
||||
$canoverride = true;
|
||||
if ($item->is_category_item() || $item->is_course_item()) {
|
||||
$canoverride = (bool) get_config('moodle', 'grade_overridecat');
|
||||
}
|
||||
if ($canoverride) {
|
||||
// Only allow edting if the grade is editable (not locked, not in a unoverridable category, etc).
|
||||
if ($enableajax && $grade->is_editable()) {
|
||||
// If a grade item is type text, and we don't have show quick feedback on, it can't be edited.
|
||||
if ($item->gradetype != GRADE_TYPE_TEXT || $showquickfeedback) {
|
||||
$itemcell->attributes['class'] .= ' clickable';
|
||||
}
|
||||
}
|
||||
@@ -1093,6 +1102,14 @@ class grade_report_grader extends grade_report {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable keyboard navigation if the grade is editable (not locked, not in a unoverridable category, etc).
|
||||
if ($enableajax && $grade->is_editable()) {
|
||||
// If a grade item is type text, and we don't have show quick feedback on, it can't be edited.
|
||||
if ($item->gradetype != GRADE_TYPE_TEXT || $showquickfeedback) {
|
||||
$itemcell->attributes['class'] .= ' gbnavigable';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->gradeserror[$item->id][$userid])) {
|
||||
$itemcell->text .= $this->gradeserror[$item->id][$userid];
|
||||
}
|
||||
@@ -1106,7 +1123,8 @@ class grade_report_grader extends grade_report {
|
||||
$jsarguments['cfg']['ajaxenabled'] = true;
|
||||
$jsarguments['cfg']['scales'] = array();
|
||||
foreach ($jsscales as $scale) {
|
||||
$jsarguments['cfg']['scales'][$scale->id] = explode(',', $scale->scale);
|
||||
// Trim the scale values, as they may have a space that is ommitted from values later.
|
||||
$jsarguments['cfg']['scales'][$scale->id] = array_map('trim', explode(',', $scale->scale));
|
||||
}
|
||||
$jsarguments['cfg']['feedbacktrunclength'] = $this->feedback_trunc_length;
|
||||
|
||||
|
||||
+234
-81
@@ -209,9 +209,11 @@ M.gradereport_grader.classes.ajax.prototype.make_editable = function(e) {
|
||||
}
|
||||
|
||||
// Sort out the field type
|
||||
var fieldtype = 'text';
|
||||
var fieldtype = 'value';
|
||||
if (node.hasClass('grade_type_scale')) {
|
||||
fieldtype = 'scale';
|
||||
} else if (node.hasClass('grade_type_text')) {
|
||||
fieldtype = 'text';
|
||||
}
|
||||
// Create the appropriate field widget
|
||||
switch (fieldtype) {
|
||||
@@ -219,6 +221,8 @@ M.gradereport_grader.classes.ajax.prototype.make_editable = function(e) {
|
||||
this.current = new M.gradereport_grader.classes.scalefield(this.report, node);
|
||||
break;
|
||||
case 'text':
|
||||
this.current = new M.gradereport_grader.classes.feedbackfield(this.report, node);
|
||||
break;
|
||||
default:
|
||||
this.current = new M.gradereport_grader.classes.textfield(this.report, node);
|
||||
break;
|
||||
@@ -247,6 +251,7 @@ M.gradereport_grader.classes.ajax.prototype.keypress_enter = function(e) {
|
||||
* @param {Bool} ignoreshift If true and shift is pressed then don't exec
|
||||
*/
|
||||
M.gradereport_grader.classes.ajax.prototype.keypress_tab = function(e, ignoreshift) {
|
||||
e.preventDefault();
|
||||
var next = null;
|
||||
if (e.shiftKey) {
|
||||
if (ignoreshift) {
|
||||
@@ -322,7 +327,11 @@ M.gradereport_grader.classes.ajax.prototype.get_next_cell = function(cell) {
|
||||
next = tr.all('.grade').item(0);
|
||||
}
|
||||
if (!next) {
|
||||
next = this.current.node;
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_next_cell(next);
|
||||
}
|
||||
return next;
|
||||
};
|
||||
@@ -342,7 +351,11 @@ M.gradereport_grader.classes.ajax.prototype.get_prev_cell = function(cell) {
|
||||
next = cells.item(cells.size()-1);
|
||||
}
|
||||
if (!next) {
|
||||
next = this.current.node;
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_prev_cell(next);
|
||||
}
|
||||
return next;
|
||||
};
|
||||
@@ -366,7 +379,11 @@ M.gradereport_grader.classes.ajax.prototype.get_above_cell = function(cell) {
|
||||
next = tr.all('td.cell').item(column);
|
||||
}
|
||||
if (!next) {
|
||||
next = this.current.node;
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_above_cell(next);
|
||||
}
|
||||
return next;
|
||||
};
|
||||
@@ -389,7 +406,13 @@ M.gradereport_grader.classes.ajax.prototype.get_below_cell = function(cell) {
|
||||
}
|
||||
next = tr.all('td.cell').item(column);
|
||||
}
|
||||
// next will be null when we get to the bottom of a column
|
||||
if (!next) {
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_below_cell(next);
|
||||
}
|
||||
return next;
|
||||
};
|
||||
/**
|
||||
@@ -478,6 +501,7 @@ M.gradereport_grader.classes.ajax.prototype.submission_outcome = function(tid, o
|
||||
}
|
||||
// Calculate the final grade for the cell
|
||||
var finalgrade = '';
|
||||
var scalegrade = -1;
|
||||
if (!r.finalgrade) {
|
||||
if (this.report.isediting) {
|
||||
// In edit mode don't put hyphens in the grade text boxes
|
||||
@@ -488,35 +512,51 @@ M.gradereport_grader.classes.ajax.prototype.submission_outcome = function(tid, o
|
||||
}
|
||||
} else {
|
||||
if (r.scale) {
|
||||
finalgrade = this.scales[r.scale][parseFloat(r.finalgrade)-1];
|
||||
scalegrade = parseFloat(r.finalgrade);
|
||||
finalgrade = this.scales[r.scale][scalegrade-1];
|
||||
} else {
|
||||
finalgrade = parseFloat(r.finalgrade).toFixed(info.itemdp);
|
||||
}
|
||||
}
|
||||
if (this.report.isediting) {
|
||||
if (args.properties.itemtype == 'scale') {
|
||||
info.cell.one('#grade_'+r.userid+'_'+r.itemid).all('options').each(function(option){
|
||||
if (option.get('value') == finalgrade) {
|
||||
option.setAttribute('selected', 'selected');
|
||||
} else {
|
||||
option.removeAttribute('selected');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
info.cell.one('#grade_'+r.userid+'_'+r.itemid).set('value', finalgrade);
|
||||
var grade = info.cell.one('#grade_'+r.userid+'_'+r.itemid);
|
||||
if (grade) {
|
||||
// This means the item has a input element to update.
|
||||
var parent = grade.ancestor('td');
|
||||
if (parent.hasClass('grade_type_scale')) {
|
||||
grade.all('option').each(function(option) {
|
||||
if (option.get('value') == scalegrade) {
|
||||
option.setAttribute('selected', 'selected');
|
||||
} else {
|
||||
option.removeAttribute('selected');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
grade.set('value', finalgrade);
|
||||
}
|
||||
} else if (info.cell.one('.gradevalue')) {
|
||||
// This means we are updating a value for something without editing boxed (locked, etc).
|
||||
info.cell.one('.gradevalue').set('innerHTML', finalgrade);
|
||||
}
|
||||
} else {
|
||||
// If there is no currently editing field or if this cell is not being currently edited
|
||||
if (!this.current || info.cell.get('id') != this.current.node.get('id')) {
|
||||
// Update the value
|
||||
info.cell.one('.gradevalue').set('innerHTML',finalgrade);
|
||||
var node = info.cell.one('.gradevalue');
|
||||
var td = node.ancestor('td');
|
||||
// Only scale and value type grades should have their content updated in this way.
|
||||
if (td.hasClass('grade_type_value') || td.hasClass('grade_type_scale')) {
|
||||
node.set('innerHTML', finalgrade);
|
||||
}
|
||||
} else if (this.current && info.cell.get('id') == this.current.node.get('id')) {
|
||||
// If we are here the grade value of the cell currently being edited has changed !!!!!!!!!
|
||||
// If the user has not actually changed the old value yet we will automatically correct it
|
||||
// otherwise we will prompt the user to choose to use their value or the new value!
|
||||
if (!this.current.has_changed() || confirm(M.str.gradereport_grader.ajaxfieldchanged)) {
|
||||
this.current.set_grade(finalgrade);
|
||||
this.current.grade.set('value', finalgrade);
|
||||
if (this.current.grade) {
|
||||
this.current.grade.set('value', finalgrade);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,57 +634,69 @@ M.gradereport_grader.classes.existingfield = function(ajax, userid, itemid) {
|
||||
this.editfeedback = ajax.showquickfeedback;
|
||||
this.grade = this.report.Y.one('#grade_'+userid+'_'+itemid);
|
||||
|
||||
if (this.report.grades) {
|
||||
for (var i = 0; i < this.report.grades.length; i++) {
|
||||
if (this.report.grades[i]['user']==this.userid && this.report.grades[i]['item']==this.itemid) {
|
||||
var i = 0;
|
||||
if (this.grade) {
|
||||
for (i = 0; i < this.report.grades.length; i++) {
|
||||
if (this.report.grades[i]['user'] == this.userid && this.report.grades[i]['item'] == this.itemid) {
|
||||
this.oldgrade = this.report.grades[i]['grade'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.oldgrade) {
|
||||
// Assigning an empty string makes determining whether the grade has been changed easier
|
||||
// This value is never sent to the server
|
||||
this.oldgrade = '';
|
||||
}
|
||||
if (!this.oldgrade) {
|
||||
// Assigning an empty string makes determining whether the grade has been changed easier
|
||||
// This value is never sent to the server
|
||||
this.oldgrade = '';
|
||||
}
|
||||
|
||||
// On blur save any changes in the grade field
|
||||
this.grade.on('blur', this.submit, this);
|
||||
// On blur save any changes in the grade field
|
||||
this.grade.on('blur', this.submit, this);
|
||||
}
|
||||
|
||||
// Check if feedback is enabled
|
||||
if (this.editfeedback) {
|
||||
// Get the feedback fields
|
||||
this.feedback = this.report.Y.one('#feedback_'+userid+'_'+itemid);
|
||||
|
||||
for(var i = 0; i < this.report.feedback.length; i++) {
|
||||
if (this.report.feedback[i]['user']==this.userid && this.report.feedback[i]['item']==this.itemid) {
|
||||
this.oldfeedback = this.report.feedback[i]['content'];
|
||||
if (this.feedback) {
|
||||
for(i = 0; i < this.report.feedback.length; i++) {
|
||||
if (this.report.feedback[i]['user'] == this.userid && this.report.feedback[i]['item'] == this.itemid) {
|
||||
this.oldfeedback = this.report.feedback[i]['content'];
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.oldfeedback) {
|
||||
// Assigning an empty string makes determining whether the feedback has been changed easier
|
||||
// This value is never sent to the server
|
||||
this.oldfeedback = '';
|
||||
}
|
||||
|
||||
// On blur save any changes in the feedback field
|
||||
this.feedback.on('blur', this.submit, this);
|
||||
|
||||
// Override the default tab movements when moving between cells
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.feedback, 'press:9', this, true)); // Handle Tab
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.feedback, 'press:13', this)); // Handle the Enter key being pressed
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.feedback, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys
|
||||
|
||||
if (this.grade) {
|
||||
// Override the default tab movements when moving between cells
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9+shift', this)); // Handle Shift+Tab
|
||||
|
||||
// Override the default tab movements for fields in the same cell
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();this.grade.focus();}, this.feedback, 'press:9+shift', this));
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){if (e.shiftKey) {return;}e.preventDefault();this.feedback.focus();}, this.grade, 'press:9', this));
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.oldfeedback) {
|
||||
// Assigning an empty string makes determining whether the feedback has been changed easier
|
||||
// This value is never sent to the server
|
||||
this.oldfeedback = '';
|
||||
}
|
||||
|
||||
// On blur save any changes in the feedback field
|
||||
this.feedback.on('blur', this.submit, this);
|
||||
|
||||
// Override the default tab movements when moving between cells
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9+shift', this)); // Handle Shift+Tab
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.feedback, 'press:9', this, true)); // Handle Tab
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.feedback, 'press:13', this)); // Handle the Enter key being pressed
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.feedback, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys
|
||||
|
||||
// Override the default tab movements for fields in the same cell
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();this.grade.focus();}, this.feedback, 'press:9+shift', this));
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){if (e.shiftKey) {return;}e.preventDefault();this.feedback.focus();}, this.grade, 'press:9', this));
|
||||
} else {
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9', this)); // Handle Tab and Shift+Tab
|
||||
} else if (this.grade) {
|
||||
// Handle Tab and Shift+Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'down:9', this));
|
||||
}
|
||||
if (this.grade) {
|
||||
// Handle the Enter key being pressed.
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'up:13', this));
|
||||
// Handle CTRL + arrow keys.
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'down:37,38,39,40+ctrl', this));
|
||||
}
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'press:13', this)); // Handle the Enter key being pressed
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys
|
||||
};
|
||||
/**
|
||||
* Attach the required properties and methods to the existing field class
|
||||
@@ -696,6 +748,7 @@ M.gradereport_grader.classes.existingfield.prototype.keypress_tab = function(e,
|
||||
* @param {Event} e
|
||||
*/
|
||||
M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(e) {
|
||||
e.preventDefault();
|
||||
var next = null;
|
||||
switch (e.keyCode) {
|
||||
case 37: // Left
|
||||
@@ -722,6 +775,7 @@ M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(
|
||||
M.gradereport_grader.classes.existingfield.prototype.move_focus = function(node) {
|
||||
if (node) {
|
||||
var properties = this.report.get_cell_info(node);
|
||||
this.report.ajax.current = node;
|
||||
switch(properties.itemtype) {
|
||||
case 'scale':
|
||||
properties.cell.one('select.select').focus();
|
||||
@@ -741,10 +795,17 @@ M.gradereport_grader.classes.existingfield.prototype.move_focus = function(node)
|
||||
* @return {Bool}
|
||||
*/
|
||||
M.gradereport_grader.classes.existingfield.prototype.has_changed = function() {
|
||||
if (this.editfeedback) {
|
||||
return (this.grade.get('value') !== this.oldgrade || this.feedback.get('value') !== this.oldfeedback);
|
||||
if (this.grade) {
|
||||
if (this.grade.get('value') !== this.oldgrade) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (this.grade.get('value') !== this.oldgrade);
|
||||
if (this.editfeedback && this.feedback) {
|
||||
if (this.feedback.get('value') !== this.oldfeedback) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* Submits any changes and then updates the fields accordingly
|
||||
@@ -759,15 +820,19 @@ M.gradereport_grader.classes.existingfield.prototype.submit = function() {
|
||||
|
||||
var properties = this.report.get_cell_info([this.userid,this.itemid]);
|
||||
var values = (function(f){
|
||||
var feedback, oldfeedback = null;
|
||||
if (f.editfeedback) {
|
||||
var feedback, oldfeedback, grade, oldgrade = null;
|
||||
if (f.editfeedback && f.feedback) {
|
||||
feedback = f.feedback.get('value');
|
||||
oldfeedback = f.oldfeedback;
|
||||
}
|
||||
if (f.grade) {
|
||||
grade = f.grade.get('value');
|
||||
oldgrade = f.oldgrade;
|
||||
}
|
||||
return {
|
||||
editablefeedback : f.editfeedback,
|
||||
grade : f.grade.get('value'),
|
||||
oldgrade : f.oldgrade,
|
||||
grade : grade,
|
||||
oldgrade : oldgrade,
|
||||
feedback : feedback,
|
||||
oldfeedback : oldfeedback
|
||||
};
|
||||
@@ -799,11 +864,11 @@ M.gradereport_grader.classes.textfield = function(report, node) {
|
||||
this.gradespan = node.one('.gradevalue');
|
||||
this.inputdiv = this.report.Y.Node.create('<div></div>');
|
||||
this.editfeedback = this.report.ajax.showquickfeedback;
|
||||
this.grade = this.report.Y.Node.create('<input type="text" class="text" value="" />');
|
||||
this.grade = this.report.Y.Node.create('<input type="text" class="text" value="" name="ajaxgrade" />');
|
||||
this.gradetype = 'value';
|
||||
this.inputdiv.append(this.grade);
|
||||
if (this.report.ajax.showquickfeedback) {
|
||||
this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" />');
|
||||
this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" name="ajaxfeedback" />');
|
||||
this.inputdiv.append(this.feedback);
|
||||
}
|
||||
};
|
||||
@@ -832,7 +897,11 @@ M.gradereport_grader.classes.textfield.prototype.replace = function() {
|
||||
this.set_feedback(this.get_feedback());
|
||||
}
|
||||
this.node.replaceChild(this.inputdiv, this.gradespan);
|
||||
this.grade.focus();
|
||||
if (this.grade) {
|
||||
this.grade.focus();
|
||||
} else if (this.feedback) {
|
||||
this.feedback.focus();
|
||||
}
|
||||
this.editable = true;
|
||||
return this;
|
||||
};
|
||||
@@ -845,6 +914,7 @@ M.gradereport_grader.classes.textfield.prototype.replace = function() {
|
||||
M.gradereport_grader.classes.textfield.prototype.commit = function() {
|
||||
// Produce an anonymous result object contianing all values
|
||||
var result = (function(field){
|
||||
// Editable false lets us get the pre-update values.
|
||||
field.editable = false;
|
||||
var oldgrade = field.get_grade();
|
||||
if (oldgrade == '-') {
|
||||
@@ -855,6 +925,8 @@ M.gradereport_grader.classes.textfield.prototype.commit = function() {
|
||||
if (field.editfeedback) {
|
||||
oldfeedback = field.get_feedback();
|
||||
}
|
||||
|
||||
// Now back to editable gives us the values in the edit areas.
|
||||
field.editable = true;
|
||||
if (field.editfeedback) {
|
||||
feedback = field.get_feedback();
|
||||
@@ -931,7 +1003,11 @@ M.gradereport_grader.classes.textfield.prototype.set_grade = function(value) {
|
||||
*/
|
||||
M.gradereport_grader.classes.textfield.prototype.get_feedback = function() {
|
||||
if (this.editable) {
|
||||
return this.feedback.get('value');
|
||||
if (this.feedback) {
|
||||
return this.feedback.get('value');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var properties = this.report.get_cell_info(this.node);
|
||||
if (properties) {
|
||||
@@ -947,7 +1023,9 @@ M.gradereport_grader.classes.textfield.prototype.get_feedback = function() {
|
||||
*/
|
||||
M.gradereport_grader.classes.textfield.prototype.set_feedback = function(value) {
|
||||
if (!this.editable) {
|
||||
this.feedback.set('value', value);
|
||||
if (this.feedback) {
|
||||
this.feedback.set('value', value);
|
||||
}
|
||||
} else {
|
||||
var properties = this.report.get_cell_info(this.node);
|
||||
this.report.update_feedback(properties.userid, properties.itemid, value);
|
||||
@@ -971,7 +1049,12 @@ M.gradereport_grader.classes.textfield.prototype.has_changed = function() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (this.get_grade() != this.gradespan.get('innerHTML'));
|
||||
|
||||
if (this.grade) {
|
||||
return (this.get_grade() != this.gradespan.get('innerHTML'));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Attaches the key listeners for the editable fields and stored the event references
|
||||
@@ -984,22 +1067,92 @@ M.gradereport_grader.classes.textfield.prototype.attach_key_events = function()
|
||||
var a = this.report.ajax;
|
||||
// Setup the default key events for tab and enter
|
||||
if (this.editfeedback) {
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9+shift', a)); // Handle Shift+Tab
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'press:9', a, true)); // Handle Tab
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'press:13', a)); // Handle the Enter key being pressed
|
||||
if (this.grade) {
|
||||
// Handle Shift+Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'down:9+shift', a));
|
||||
}
|
||||
// Handle Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'down:9', a, true));
|
||||
// Handle the Enter key being pressed.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'up:13', a));
|
||||
} else {
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9', a)); // Handle Tab and Shift+Tab
|
||||
if (this.grade) {
|
||||
// Handle Tab and Shift+Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'down:9', a));
|
||||
}
|
||||
}
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'press:13', a)); // Handle the Enter key being pressed
|
||||
|
||||
// Setup the arrow key events
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_arrows, this.grade.ancestor('td'), 'down:37,38,39,40+ctrl', a)); // Handle CTRL + arrow keys
|
||||
// Prevent the default key action on all fields for arrow keys on all key events!
|
||||
// Note: this still does not work in FF!!!!!
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'down:37,38,39,40+ctrl'));
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'press:37,38,39,40+ctrl'));
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'up:37,38,39,40+ctrl'));
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_arrows, this.inputdiv.ancestor('td'), 'down:37,38,39,40+ctrl', a)); // Handle CTRL + arrow keys
|
||||
|
||||
if (this.grade) {
|
||||
// Handle the Enter key being pressed.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'up:13', a));
|
||||
// Prevent the default key action on all fields for arrow keys on all key events!
|
||||
// Note: this still does not work in FF!!!!!
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'down:37,38,39,40+ctrl'));
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'press:37,38,39,40+ctrl'));
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'up:37,38,39,40+ctrl'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Feedback field class
|
||||
* This classes gets used in conjunction with the report running with AJAX enabled
|
||||
* and is used to manage a cell that no editable grade, only possibly feedback
|
||||
*
|
||||
* @class feedbackfield
|
||||
* @constructor
|
||||
* @this {M.gradereport_grader.classes.feedbackfield}
|
||||
* @param {M.gradereport_grader.classes.report} report
|
||||
* @param {Y.Node} node
|
||||
*/
|
||||
M.gradereport_grader.classes.feedbackfield = function(report, node) {
|
||||
this.report = report;
|
||||
this.node = node;
|
||||
this.gradespan = node.one('.gradevalue');
|
||||
this.inputdiv = this.report.Y.Node.create('<div></div>');
|
||||
this.editfeedback = this.report.ajax.showquickfeedback;
|
||||
this.gradetype = 'text';
|
||||
if (this.report.ajax.showquickfeedback) {
|
||||
this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" name="ajaxfeedback" />');
|
||||
this.inputdiv.append(this.feedback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the grade for current cell (which will always be null)
|
||||
*
|
||||
* @function
|
||||
* @this {M.gradereport_grader.classes.feedbackfield}
|
||||
* @return {Mixed}
|
||||
*/
|
||||
M.gradereport_grader.classes.feedbackfield.prototype.get_grade = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Overrides the set_grade function of textfield so that it can ignore the set-grade
|
||||
* for grade cells without grades
|
||||
*
|
||||
* @function
|
||||
* @this {M.gradereport_grader.classes.feedbackfield}
|
||||
* @param {String} value
|
||||
*/
|
||||
M.gradereport_grader.classes.feedbackfield.prototype.set_grade = function() {
|
||||
return;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manually extend the feedbackfield class with the properties and methods of the
|
||||
* textfield class that have not been defined
|
||||
*/
|
||||
for (var i in M.gradereport_grader.classes.textfield.prototype) {
|
||||
if (!M.gradereport_grader.classes.feedbackfield.prototype[i]) {
|
||||
M.gradereport_grader.classes.feedbackfield.prototype[i] = M.gradereport_grader.classes.textfield.prototype[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An editable scale field
|
||||
*
|
||||
@@ -1017,11 +1170,11 @@ M.gradereport_grader.classes.scalefield = function(report, node) {
|
||||
this.gradespan = node.one('.gradevalue');
|
||||
this.inputdiv = this.report.Y.Node.create('<div></div>');
|
||||
this.editfeedback = this.report.ajax.showquickfeedback;
|
||||
this.grade = this.report.Y.Node.create('<select type="text" class="text" /><option value="-1">'+M.str.gradereport_grader.ajaxchoosescale+'</option></select>');
|
||||
this.grade = this.report.Y.Node.create('<select type="text" class="text" name="ajaxgrade" /><option value="-1">'+M.str.gradereport_grader.ajaxchoosescale+'</option></select>');
|
||||
this.gradetype = 'scale';
|
||||
this.inputdiv.append(this.grade);
|
||||
if (this.editfeedback) {
|
||||
this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" />');
|
||||
this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" name="ajaxfeedback"/>');
|
||||
this.inputdiv.append(this.feedback);
|
||||
}
|
||||
var properties = this.report.get_cell_info(node);
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
@gradereport @gradereport_grader
|
||||
Feature: Using the AJAX grading feature of Grader report to update grades and feedback
|
||||
In order to use AJAX grading
|
||||
As a teacher
|
||||
I need to be able to update and verify grades
|
||||
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category | groupmode |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | idnumber |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
|
||||
| student1 | Student | 1 | student1@example.com | s1 |
|
||||
| student2 | Student | 2 | student2@example.com | s2 |
|
||||
| student3 | Student | 3 | student3@example.com | s3 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| student3 | C1 | student |
|
||||
And the following "scales" exist:
|
||||
| name | scale |
|
||||
| Test Scale | Disappointing,Good,Very good,Excellent |
|
||||
And the following "grade categories" exist:
|
||||
| fullname | course |
|
||||
| Grade Cat | C1 |
|
||||
And the following "grade items" exist:
|
||||
| itemname | course | locked | gradetype | gradecategory |
|
||||
| Item 1 | C1 | 0 | value | Grade Cat |
|
||||
| Item VU | C1 | 0 | value | Grade Cat |
|
||||
| Item VL | C1 | 1 | value | Grade Cat |
|
||||
| Item TU | C1 | 0 | text | Grade Cat |
|
||||
| Item TL | C1 | 1 | text | Grade Cat |
|
||||
And the following "grade items" exist:
|
||||
| itemname | course | locked | gradetype | scale | gradecategory |
|
||||
| Item SU | C1 | 0 | scale | Test Scale | Grade Cat |
|
||||
| Item SL | C1 | 1 | scale | Test Scale | Grade Cat |
|
||||
And the following "grade items" exist:
|
||||
| itemname | course | locked | gradetype | gradecategory |
|
||||
| Item 3 | C1 | 0 | value | Grade Cat |
|
||||
And the following config values are set as admin:
|
||||
| grade_report_showaverages | 0 |
|
||||
| grade_report_enableajax | 1 |
|
||||
|
||||
|
||||
@javascript
|
||||
Scenario: Use the grader report without editing, with AJAX on and quick feedback off
|
||||
When the following config values are set as admin:
|
||||
| grade_overridecat | 1 |
|
||||
| grade_report_showquickfeedback | 0 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I click on student "Student 2" for grade item "Item VU"
|
||||
Then I should see a grade field for "Student 2" and grade item "Item VU"
|
||||
And I should not see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "33"
|
||||
And I click on student "Student 1" for grade item "Item VU"
|
||||
And I should not see a grade field for "Student 2" and grade item "Item VU"
|
||||
And I should not see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I click on student "Student 3" for grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "50"
|
||||
And I click on student "Student 1" for grade item "Item VU"
|
||||
And I click on student "Student 3" for grade item "Item 1"
|
||||
And I set the field "ajaxgrade" to "80"
|
||||
And I click on student "Student 1" for grade item "Item 1"
|
||||
And I click on student "Student 3" for grade item "Item SU"
|
||||
And I set the field "ajaxgrade" to "Very good"
|
||||
And I click on student "Student 1" for grade item "Item SU"
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -4- | -5- | -9- | -13- |
|
||||
| Student 2 | - | 33.00 | - | 33.00 |
|
||||
| Student 3 | 80.00 | 50.00 | Very good | 133.00 |
|
||||
And I click on student "Student 3" for grade item "Item VL"
|
||||
And I should not see a grade field for "Student 3" and grade item "Item VL"
|
||||
And I should not see a feedback field for "Student 3" and grade item "Item VL"
|
||||
And I click on student "Student 3" for grade item "Item SL"
|
||||
And I should not see a grade field for "Student 3" and grade item "Item SL"
|
||||
And I should not see a feedback field for "Student 3" and grade item "Item SL"
|
||||
And I click on student "Student 3" for grade item "Item TU"
|
||||
And I should not see a grade field for "Student 3" and grade item "Item TU"
|
||||
And I should not see a feedback field for "Student 3" and grade item "Item TU"
|
||||
And I click on student "Student 1" for grade item "Course total"
|
||||
And I should see a grade field for "Student 1" and grade item "Course total"
|
||||
And I should not see a feedback field for "Student 1" and grade item "Course total"
|
||||
And I set the field "ajaxgrade" to "90"
|
||||
And I click on student "Student 1" for grade item "Item 3"
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -13- |
|
||||
| Student 1 | 90.00 |
|
||||
And I navigate to "Grader report" node in "Grade administration"
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -4- | -5- | -9- | -13- |
|
||||
| Student 1 | - | - | - | 90.00 |
|
||||
| Student 2 | - | 33.00 | - | 33.00 |
|
||||
| Student 3 | 80.00 | 50.00 | Very good | 133.00 |
|
||||
|
||||
@javascript
|
||||
Scenario: Use the grader report without editing, with AJAX and quick feedback on
|
||||
When the following config values are set as admin:
|
||||
| grade_overridecat | 1 |
|
||||
| grade_report_showquickfeedback | 1 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I click on student "Student 2" for grade item "Item VU"
|
||||
Then I should see a grade field for "Student 2" and grade item "Item VU"
|
||||
And I should see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "33"
|
||||
And I set the field "ajaxfeedback" to "Student 2 VU feedback"
|
||||
And I click on student "Student 3" for grade item "Item VL"
|
||||
And I should not see a grade field for "Student 3" and grade item "Item VL"
|
||||
And I should not see a feedback field for "Student 3" and grade item "Item VL"
|
||||
And I click on student "Student 3" for grade item "Item TU"
|
||||
And I should not see a grade field for "Student 3" and grade item "Item TU"
|
||||
And I should see a feedback field for "Student 3" and grade item "Item TU"
|
||||
And I set the field "ajaxfeedback" to "Student 3 TU feedback"
|
||||
And I click on student "Student 2" for grade item "Item SU"
|
||||
And I set the field "ajaxgrade" to "Very good"
|
||||
And I set the field "ajaxfeedback" to "Student 2 SU feedback"
|
||||
And I click on student "Student 1" for grade item "Item SU"
|
||||
And I navigate to "Grader report" node in "Grade administration"
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -5- | -9- | -13- |
|
||||
| Student 2 | 33.00 | Very good | 36.00 |
|
||||
And I click on student "Student 3" for grade item "Item TU"
|
||||
And the field "ajaxfeedback" matches value "Student 3 TU feedback"
|
||||
And I click on student "Student 2" for grade item "Item SU"
|
||||
And the field "ajaxfeedback" matches value "Student 2 SU feedback"
|
||||
|
||||
@javascript
|
||||
Scenario: Use the grader report without editing, with AJAX and quick feedback on, without category override
|
||||
When the following config values are set as admin:
|
||||
| grade_overridecat | 0 |
|
||||
| grade_report_showquickfeedback | 1 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I click on student "Student 2" for grade item "Item VU"
|
||||
Then I should see a grade field for "Student 2" and grade item "Item VU"
|
||||
And I should see a feedback field for "Student 2" and grade item "Item VU"
|
||||
And I set the field "ajaxgrade" to "33"
|
||||
And I click on student "Student 1" for grade item "Item VU"
|
||||
And I click on student "Student 2" for grade item "Course total"
|
||||
And I should not see a grade field for "Student 3" and grade item "Course total"
|
||||
And I should not see a feedback field for "Student 3" and grade item "Course total"
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -5- | -13- |
|
||||
| Student 2 | 33.00 | 33.00 |
|
||||
|
||||
@javascript
|
||||
Scenario: Use the grader report with editing, with AJAX and quick feedback on, with category override
|
||||
When the following config values are set as admin:
|
||||
| grade_overridecat | 1 |
|
||||
| grade_report_showquickfeedback | 1 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I turn editing mode on
|
||||
Then I should not see a grade field for "Student 2" and grade item "Item VL"
|
||||
And I should not see a feedback field for "Student 2" and grade item "Item VL"
|
||||
And I should not see a grade field for "Student 2" and grade item "Item TU"
|
||||
And I should see a feedback field for "Student 2" and grade item "Item TU"
|
||||
And I should see a grade field for "Student 2" and grade item "Course total"
|
||||
And I should see a feedback field for "Student 2" and grade item "Course total"
|
||||
And I give the grade "20.00" to the user "Student 2" for the grade item "Item VU"
|
||||
And I click away from student "Student 2" and grade item "Item VU" value
|
||||
And I give the grade "30.00" to the user "Student 2" for the grade item "Item 1"
|
||||
And I give the feedback "Some feedback" to the user "Student 2" for the grade item "Item 1"
|
||||
And I click away from student "Student 2" and grade item "Item 1" feedback
|
||||
And I give the grade "Very good" to the user "Student 2" for the grade item "Item SU"
|
||||
And I click away from student "Student 2" and grade item "Item SU" value
|
||||
And the grade for "Student 2" in grade item "Grade Cat" should match "53.00"
|
||||
And the grade for "Student 2" in grade item "Course total" should match "53.00"
|
||||
And I turn editing mode off
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -4- | -5- | -9- | -12- | -13- |
|
||||
| Student 2 | 30.00 | 20.00 | Very good | 53.00 | 53.00 |
|
||||
And I click on student "Student 2" for grade item "Item 1"
|
||||
And the field "ajaxfeedback" matches value "Some feedback"
|
||||
|
||||
@javascript
|
||||
Scenario: Use the grader report with editing, with AJAX and quick feedback on, without category override
|
||||
When the following config values are set as admin:
|
||||
| grade_overridecat | 0 |
|
||||
| grade_report_showquickfeedback | 1 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I turn editing mode on
|
||||
Then I should not see a grade field for "Student 2" and grade item "Course total"
|
||||
And I should not see a feedback field for "Student 2" and grade item "Course total"
|
||||
And I give the grade "20.00" to the user "Student 2" for the grade item "Item VU"
|
||||
And I click away from student "Student 2" and grade item "Item VU" value
|
||||
And I give the grade "30.00" to the user "Student 2" for the grade item "Item 1"
|
||||
And I click away from student "Student 2" and grade item "Item 1" value
|
||||
And I give the feedback "Some feedback" to the user "Student 2" for the grade item "Item 1"
|
||||
And I click away from student "Student 2" and grade item "Item 1" feedback
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -13- |
|
||||
| Student 2 | 50.00 |
|
||||
And I turn editing mode off
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -4- | -5- | -13- |
|
||||
| Student 2 | 30.00 | 20.00 | 50.00 |
|
||||
And I click on student "Student 2" for grade item "Item 1"
|
||||
And the field "ajaxfeedback" matches value "Some feedback"
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Behat steps definitions for drag and drop onto image.
|
||||
*
|
||||
* @package gradereport_grader
|
||||
* @category test
|
||||
* @copyright 2015 Oakland University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
|
||||
|
||||
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
|
||||
|
||||
use Behat\Behat\Context\Step\Given,
|
||||
Behat\Behat\Context\Step\Then,
|
||||
Behat\Mink\Exception\ExpectationException as ExpectationException,
|
||||
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Steps definitions related with the drag and drop onto image question type.
|
||||
*
|
||||
* @copyright 2015 Oakland University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_gradereport_grader extends behat_base {
|
||||
/**
|
||||
* Click a given user grade cell.
|
||||
*
|
||||
* @Given /^I click on student "([^"]*)" for grade item "([^"]*)"$/
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return Given
|
||||
*/
|
||||
public function i_click_on_student_and_grade_item($student, $itemname) {
|
||||
$xpath = $this->get_student_and_grade_cell_selector($student, $itemname);
|
||||
|
||||
return new Given('I click on "' . $this->escape($xpath) . '" "xpath_element"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove focus for a grade value cell.
|
||||
*
|
||||
* @Given /^I click away from student "([^"]*)" and grade item "([^"]*)" value$/
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return Given
|
||||
*/
|
||||
public function i_click_away_from_student_and_grade_value($student, $itemname) {
|
||||
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
|
||||
|
||||
return new Given('I take focus off "' . $this->escape($xpath) . '" "xpath_element"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove focus for a grade value cell.
|
||||
*
|
||||
* @Given /^I click away from student "([^"]*)" and grade item "([^"]*)" feedback$/
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return Given
|
||||
*/
|
||||
public function i_click_away_from_student_and_grade_feedback($student, $itemname) {
|
||||
$xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
|
||||
|
||||
return new Given('I take focus off "' . $this->escape($xpath) . '" "xpath_element"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks grade values with or without a edit box.
|
||||
*
|
||||
* @Then /^the grade for "([^"]*)" in grade item "([^"]*)" should match "([^"]*)"$/
|
||||
* @throws Exception
|
||||
* @throws ElementNotFoundException
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @param string $value
|
||||
* @return Then
|
||||
*/
|
||||
public function the_grade_should_match($student, $itemname, $value) {
|
||||
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
|
||||
|
||||
$gradefield = $this->getSession()->getPage()->find('xpath', $xpath);
|
||||
if (!empty($gradefield)) {
|
||||
// Get the field.
|
||||
$fieldtype = behat_field_manager::guess_field_type($gradefield, $this->getSession());
|
||||
if (!$fieldtype) {
|
||||
throw new Exception('Could not get field type for grade field "' . $itemname . '"');
|
||||
}
|
||||
$field = behat_field_manager::get_field_instance($fieldtype, $gradefield, $this->getSession());
|
||||
if (!$field->matches($value)) {
|
||||
$fieldvalue = $field->get_value();
|
||||
throw new ExpectationException(
|
||||
'The "' . $student . '" and "' . $itemname . '" grade is "' . $fieldvalue . '", "' . $value . '" expected' ,
|
||||
$this->getSession()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// If there isn't a form field, just search for contents.
|
||||
$valueliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($value);
|
||||
|
||||
$xpath = $this->get_student_and_grade_cell_selector($student, $itemname);
|
||||
$xpath .= "[contains(normalize-space(.)," . $valueliteral . ")]";
|
||||
|
||||
$node = $this->getSession()->getDriver()->find($xpath);
|
||||
if (empty($node)) {
|
||||
$locatorexceptionmsg = 'Cell for "' . $student . '" and "' . $itemname . '" with value "' . $value . '"';
|
||||
throw new ElementNotFoundException($this->getSession(), $locatorexceptionmsg, null, $xpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a grade editing field.
|
||||
*
|
||||
* @Then /^I should see a grade field for "([^"]*)" and grade item "([^"]*)"$/
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return Then
|
||||
*/
|
||||
public function i_should_see_grade_field($student, $itemname) {
|
||||
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
|
||||
|
||||
return new Then('"' . $this->escape($xpath) . '" "xpath_element" should be visible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a feedback editing field.
|
||||
*
|
||||
* @Then /^I should see a feedback field for "([^"]*)" and grade item "([^"]*)"$/
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return Then
|
||||
*/
|
||||
public function i_should_see_feedback_field($student, $itemname) {
|
||||
$xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
|
||||
|
||||
return new Then('"' . $this->escape($xpath) . '" "xpath_element" should be visible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a lack of the grade editing field.
|
||||
*
|
||||
* @Then /^I should not see a grade field for "([^"]*)" and grade item "([^"]*)"$/
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return Then
|
||||
*/
|
||||
public function i_should_not_see_grade_field($student, $itemname) {
|
||||
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
|
||||
|
||||
return new Then('"' . $this->escape($xpath) . '" "xpath_element" should not exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a lack of the feedback editing field.
|
||||
*
|
||||
* @Then /^I should not see a feedback field for "([^"]*)" and grade item "([^"]*)"$/
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return Then
|
||||
*/
|
||||
public function i_should_not_see_feedback_field($student, $itemname) {
|
||||
$xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
|
||||
|
||||
return new Then('"' . $this->escape($xpath) . '" "xpath_element" should not exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user id from its name.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param string $name
|
||||
* @return int
|
||||
*/
|
||||
protected function get_user_id($name) {
|
||||
global $DB;
|
||||
$names = explode(' ', $name);
|
||||
|
||||
if (!$id = $DB->get_field('user', 'id', array('firstname' => $names[0], 'lastname' => $names[1]))) {
|
||||
throw new Exception('The specified user with username "' . $name . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the grade item id from its name.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param string $itemname
|
||||
* @return int
|
||||
*/
|
||||
protected function get_grade_item_id($itemname) {
|
||||
global $DB;
|
||||
|
||||
if ($id = $DB->get_field('grade_items', 'id', array('itemname' => $itemname))) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
// The course total is a special case.
|
||||
if ($itemname === "Course total") {
|
||||
if (!$id = $DB->get_field('grade_items', 'id', array('itemtype' => 'course'))) {
|
||||
throw new Exception('The specified grade_item with name "' . $itemname . '" does not exist');
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
// Find a category with the name.
|
||||
if ($catid = $DB->get_field('grade_categories', 'id', array('fullname' => $itemname))) {
|
||||
if ($id = $DB->get_field('grade_items', 'id', array('iteminstance' => $catid))) {
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception('The specified grade_item with name "' . $itemname . '" does not exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets unique xpath selector for a student/grade item combo.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return string
|
||||
*/
|
||||
protected function get_student_and_grade_cell_selector($student, $itemname) {
|
||||
$itemid = 'u' . $this->get_user_id($student) . 'i' . $this->get_grade_item_id($itemname);
|
||||
return "//table[@id='user-grades']//td[@id='" . $itemid . "']";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets xpath for a particular student/grade item grade value cell.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return string
|
||||
*/
|
||||
protected function get_student_and_grade_value_selector($student, $itemname) {
|
||||
$cell = $this->get_student_and_grade_cell_selector($student, $itemname);
|
||||
return $cell . "//*[contains(@id, 'grade_') or @name='ajaxgrade']";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets xpath for a particular student/grade item feedback cell.
|
||||
*
|
||||
* @throws Exception
|
||||
* @param string $student
|
||||
* @param string $itemname
|
||||
* @return string
|
||||
*/
|
||||
protected function get_student_and_grade_feedback_selector($student, $itemname) {
|
||||
$cell = $this->get_student_and_grade_cell_selector($student, $itemname);
|
||||
return $cell . "//input[contains(@id, 'feedback_') or @name='ajaxfeedback']";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -271,10 +271,10 @@ function grade_report_overview_settings_definition(&$mform) {
|
||||
GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowexhiddenitems', 'grades'),
|
||||
GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowinchiddenitems', 'grades') );
|
||||
|
||||
if (empty($CFG->grade_report_overview_showtotalsifcontainhidden)) {
|
||||
if (!array_key_exists($CFG->grade_report_overview_showtotalsifcontainhidden, $options)) {
|
||||
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
|
||||
} else {
|
||||
$options[-1] = get_string('defaultprev', 'grades', $options[1]);
|
||||
$options[-1] = get_string('defaultprev', 'grades', $options[$CFG->grade_report_overview_showtotalsifcontainhidden]);
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'report_overview_showtotalsifcontainhidden', get_string('hidetotalifhiddenitems', 'grades'), $options);
|
||||
|
||||
@@ -325,19 +325,13 @@ class grade extends tablelike implements selectable_items, filterable_items {
|
||||
|
||||
$data->$field = empty($grade) ? $null : $grade->finalgrade;
|
||||
$data->{"old$field"} = $data->$field;
|
||||
|
||||
preg_match('/_(\d+)_(\d+)/', $field, $oldoverride);
|
||||
$oldoverride = 'oldoverride' . $oldoverride[0];
|
||||
if (empty($data->$oldoverride)) {
|
||||
$data->$field = (!isset($grade->rawgrade)) ? $null : $grade->rawgrade;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $varname => $value) {
|
||||
if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
|
||||
// If we've selected override or overriding all grades.
|
||||
if (!empty($data->$matches[0]) || $filter == 'all') {
|
||||
// If we've selected overriding all grades.
|
||||
if ($filter == 'all') {
|
||||
$override = "override_{$matches[1]}_{$matches[2]}";
|
||||
$data->$override = '1';
|
||||
}
|
||||
|
||||
@@ -329,6 +329,13 @@ abstract class screen {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the user submits Exclude grade elements without the proper.
|
||||
// permissions then we should refuse to update.
|
||||
if ($matches[1] === 'exclude' && !has_capability('moodle/grade:manage', $this->context)){
|
||||
$warnings[] = get_string('nopermissions', 'error', get_string('grade:manage', 'role'));
|
||||
continue;
|
||||
}
|
||||
|
||||
$msg = $element->set($posted);
|
||||
|
||||
// Optional type.
|
||||
|
||||
@@ -141,6 +141,12 @@ abstract class tablelike extends screen {
|
||||
$html .= $this->structure->get_grade_analysis_icon($grade);
|
||||
}
|
||||
|
||||
// Singleview users without proper permissions should be presented
|
||||
// disabled checkboxes for the Exclude grade attribute.
|
||||
if ($field == 'exclude' && !has_capability('moodle/grade:manage', $this->context)){
|
||||
$html->disabled = true;
|
||||
}
|
||||
|
||||
$line[] = $html;
|
||||
}
|
||||
return $line;
|
||||
|
||||
@@ -334,29 +334,32 @@ class user extends tablelike implements selectable_items {
|
||||
}
|
||||
|
||||
$oldfinalgradefield = "oldfinalgrade_{$gradeitem->id}_{$this->itemid}";
|
||||
if (!empty($data->$oldfinalgradefield)) {
|
||||
// Bulk grade changes for all grades need to be processed and shouldn't be skipped if they had a previous grade.
|
||||
if ($gradeitem->is_course_item() || ($filter != 'all' && !empty($data->$oldfinalgradefield))) {
|
||||
if ($gradeitem->is_course_item()) {
|
||||
// The course total should not be overridden.
|
||||
unset($data->$field);
|
||||
unset($data->oldfinalgradefield);
|
||||
$oldoverride = "oldoverride_{$gradeitem->id}_{$this->itemid}";
|
||||
unset($data->$oldoverride);
|
||||
$oldfeedback = "oldfeedback_{$gradeitem->id}_{$this->itemid}";
|
||||
unset($data->$oldfeedback);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$grade = grade_grade::fetch(array(
|
||||
'itemid' => $this->itemid,
|
||||
'itemid' => $gradeitemid,
|
||||
'userid' => $userid
|
||||
));
|
||||
|
||||
$data->$field = empty($grade) ? $null : $grade->finalgrade;
|
||||
$data->{"old$field"} = $data->$field;
|
||||
|
||||
preg_match('/_(\d+)_(\d+)/', $field, $oldoverride);
|
||||
$oldoverride = 'oldoverride' . $oldoverride[0];
|
||||
if (empty($data->$oldoverride)) {
|
||||
$data->$field = (!isset($grade->rawgrade)) ? $null : $grade->rawgrade;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach ($data as $varname => $value) {
|
||||
if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
|
||||
// If we've selected override or overriding all grades.
|
||||
if (!empty($data->$matches[0]) || $filter == 'all') {
|
||||
// If we've selected overriding all grades.
|
||||
if ($filter == 'all') {
|
||||
$override = "override_{$matches[1]}_{$matches[2]}";
|
||||
$data->$override = '1';
|
||||
}
|
||||
|
||||
@@ -35,11 +35,14 @@ use grade_grade;
|
||||
* @copyright 2014 Moodle Pty Ltd (http://moodle.com)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class exclude extends grade_attribute_format implements be_checked {
|
||||
class exclude extends grade_attribute_format implements be_checked, be_disabled {
|
||||
|
||||
/** @var string $name The name of the input */
|
||||
public $name = 'exclude';
|
||||
|
||||
/** @var bool $disabled Is the checkbox disabled? */
|
||||
public $disabled = false;
|
||||
|
||||
/**
|
||||
* Is it checked?
|
||||
*
|
||||
@@ -49,6 +52,15 @@ class exclude extends grade_attribute_format implements be_checked {
|
||||
return $this->grade->is_excluded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it disabled?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_disabled() {
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the element used to render the UI
|
||||
*
|
||||
@@ -58,7 +70,8 @@ class exclude extends grade_attribute_format implements be_checked {
|
||||
return new checkbox_attribute(
|
||||
$this->get_name(),
|
||||
$this->get_label(),
|
||||
$this->is_checked()
|
||||
$this->is_checked(),
|
||||
$this->is_disabled()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,14 +47,7 @@ class finalgrade extends grade_attribute_format implements unique_value, be_disa
|
||||
public function get_value() {
|
||||
$this->label = $this->grade->grade_item->itemname;
|
||||
|
||||
$isoverridden = $this->grade->is_overridden();
|
||||
// If the grade is overridden or the grade type is not an activity then use finalgrade.
|
||||
if (!empty($isoverridden) || $this->grade->grade_item->itemtype != 'mod') {
|
||||
$val = $this->grade->finalgrade;
|
||||
} else {
|
||||
$val = $this->grade->rawgrade;
|
||||
}
|
||||
|
||||
$val = $this->grade->finalgrade;
|
||||
if ($this->grade->grade_item->scaleid) {
|
||||
return $val ? (int)$val : -1;
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ Feature: We can use Single view
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | idnumber | alternatename |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | fred |
|
||||
| teacher2 | No edit | 1 | teacher2@example.com | t2 | nick |
|
||||
| student1 | Student | 1 | student1@example.com | s1 | james |
|
||||
| student2 | Student | 2 | student1@example.com | s2 | holly |
|
||||
| student3 | Student | 3 | student1@example.com | s3 | anna |
|
||||
@@ -27,6 +28,7 @@ Feature: We can use Single view
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | teacher |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| student3 | C1 | student |
|
||||
@@ -44,6 +46,10 @@ Feature: We can use Single view
|
||||
And the following "grade items" exist:
|
||||
| itemname | course | gradetype |
|
||||
| Test grade item | C1 | Scale |
|
||||
And the following "permission overrides" exist:
|
||||
| capability | permission | role | contextlevel | reference |
|
||||
| moodle/grade:edit | Allow | teacher | Course | C1 |
|
||||
| gradereport/singleview:view | Allow | teacher | Course | C1 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Grades"
|
||||
@@ -89,6 +95,14 @@ Feature: We can use Single view
|
||||
And the following should exist in the "generaltable" table:
|
||||
| First name (Alternate name) Surname | Grade |
|
||||
| james (Student) 1 | Very good |
|
||||
And I log out
|
||||
And I log in as "teacher2"
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I click on "Single view" "option"
|
||||
And I click on "Student 4" "option"
|
||||
And the "Exclude for Test assignment one" "checkbox" should be disabled
|
||||
And the "Override for Test assignment one" "checkbox" should be enabled
|
||||
|
||||
Scenario: Single view links work on grade report.
|
||||
Given I follow "Single view for Test assignment one"
|
||||
@@ -105,7 +119,7 @@ Feature: We can use Single view
|
||||
And I set the field "Insert value" to "1.0"
|
||||
And I click on "Perform bulk insert" "checkbox"
|
||||
And I press "Update"
|
||||
Then I should see "Grades were set for 9 items"
|
||||
Then I should see "Grades were set for 8 items"
|
||||
|
||||
Scenario: Navigation works in the Single view.
|
||||
Given I follow "Single view for Student 1"
|
||||
|
||||
@@ -164,13 +164,19 @@ if (has_capability('moodle/grade:viewall', $context)) { //Teachers will see all
|
||||
}
|
||||
}
|
||||
|
||||
$event = \gradereport_user\event\grade_report_viewed::create(
|
||||
array(
|
||||
'context' => $context,
|
||||
'courseid' => $courseid,
|
||||
'relateduserid' => $userid,
|
||||
)
|
||||
);
|
||||
$event->trigger();
|
||||
// Trigger report viewed event.
|
||||
if (isset($report)) {
|
||||
$event = \gradereport_user\event\grade_report_viewed::create(
|
||||
array(
|
||||
'context' => $context,
|
||||
'courseid' => $courseid,
|
||||
'relateduserid' => $userid,
|
||||
)
|
||||
);
|
||||
$event->trigger();
|
||||
} else {
|
||||
echo html_writer::tag('div', '', array('class' => 'clearfix'));
|
||||
echo $OUTPUT->notification(get_string('nostudentsyet'));
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.path-grade-report-user #graded_users_selector {
|
||||
float: right;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.path-grade-report-user #graded_users_selector .singleselect label {
|
||||
display: inline-block;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
@core @core_grades @gradereport_user
|
||||
Feature: We can use the user report
|
||||
As a user
|
||||
I browse the User report
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
|
||||
Scenario: Verify we can view a user grade report with no users enrolled.
|
||||
Given I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I select "User report" from the "Grade report" singleselect
|
||||
And I press "Go"
|
||||
And I select "All users (0)" from the "Select all or one user" singleselect
|
||||
And I click on "Go" "button" in the "#choosegradeuser" "css_element"
|
||||
Then I should see "No students enrolled in this course yet"
|
||||
@@ -48,6 +48,23 @@ class behat_grade extends behat_base {
|
||||
return new Given('I set the field "' . $this->escape($fieldstr) . '" to "' . $grade . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters a quick feedback via the gradebook for a specific grade item and user when viewing
|
||||
* the 'Grader report' with editing mode turned on.
|
||||
*
|
||||
* @Given /^I give the feedback "(?P<grade_number>(?:[^"]|\\")*)" to the user "(?P<username_string>(?:[^"]|\\")*)" for the grade item "(?P<grade_activity_string>(?:[^"]|\\")*)"$/
|
||||
* @param string $feedback
|
||||
* @param string $userfullname the user's fullname as returned by fullname()
|
||||
* @param string $itemname
|
||||
* @return Given
|
||||
*/
|
||||
public function i_give_the_feedback($feedback, $userfullname, $itemname) {
|
||||
$gradelabel = $userfullname . ' ' . $itemname;
|
||||
$fieldstr = get_string('useractivityfeedback', 'gradereport_grader', $gradelabel);
|
||||
|
||||
return new Given('I set the field "' . $this->escape($fieldstr) . '" to "' . $this->escape($feedback) . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the settings of a grade item or category or the course.
|
||||
*
|
||||
|
||||
@@ -35,6 +35,7 @@ $string['cliansweryes'] = 'Да';
|
||||
$string['cliincorrectvalueerror'] = 'Грешка, некоректна стойност "{$a->value}" за "{$a->option}"';
|
||||
$string['cliincorrectvalueretry'] = 'Неправилна стойност. Моля опитайте отново';
|
||||
$string['clitypevalue'] = 'Тип стойност';
|
||||
$string['clitypevaluedefault'] = 'въведете стойност, натиснете Enter за да се използва стойността по подразбиране ({$a})';
|
||||
$string['cliyesnoprompt'] = 'Въведете y (означава да) или n (означава не)';
|
||||
$string['environmentrequireinstall'] = 'Трябва да бъде инсталиран и разрешен';
|
||||
$string['environmentrequireversion'] = 'Необходима е версия {$a->needed} а Вие имате {$a->current}';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user