Compare commits
552 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef7fcf4261 | |||
| fa85c3b806 | |||
| 8518f6307f | |||
| 0c799ad2e1 | |||
| 7a09d64104 | |||
| 5f7136f17c | |||
| 3278f5ca50 | |||
| 43520e3eaa | |||
| 107244cf20 | |||
| 7cb384756e | |||
| 65aceea7ed | |||
| 61d4fcc812 | |||
| 2b260526fc | |||
| 1d38f7c3eb | |||
| 348d2c66df | |||
| cdc875c25c | |||
| a3c38be96b | |||
| 2f1b88412d | |||
| a688fc9d6c | |||
| 401c241450 | |||
| 6b7850290b | |||
| ba0a487685 | |||
| 0e462101e0 | |||
| fae4570571 | |||
| 096b28dee9 | |||
| 4a4912ecbf | |||
| 8c276d003c | |||
| 23fd19658d | |||
| 2551709dbd | |||
| 9d2f8acfb4 | |||
| 6484e5ae81 | |||
| 7726160ef3 | |||
| 936f22755c | |||
| d199b23598 | |||
| f2ff9d8256 | |||
| 6b804f9e1f | |||
| 201734b465 | |||
| cb8a282e05 | |||
| 3f1a70c171 | |||
| dc9f2d2509 | |||
| acd3b26df0 | |||
| d5e880ca73 | |||
| f1e7a22672 | |||
| 2730c1a987 | |||
| 3f87850bc2 | |||
| a7e720be5f | |||
| 15f3dddd04 | |||
| b0aa3ba6af | |||
| c6f3ecb4f0 | |||
| 573158a4f3 | |||
| f8b584f09a | |||
| b33790d932 | |||
| 627fcc0ebc | |||
| 37ff88f18d | |||
| bbdd616d5c | |||
| 0e0b494d4a | |||
| b61c7fe0f9 | |||
| 044754dddb | |||
| c25ae6986c | |||
| f5e822222d | |||
| 67aa4abd73 | |||
| f3e6ed90d0 | |||
| cfd58aae3e | |||
| 822eea5638 | |||
| 9c29fcaae0 | |||
| bee5d9d27e | |||
| 695e05f1e8 | |||
| 5a407772c7 | |||
| 82f2fe02e6 | |||
| f81bc6488d | |||
| b9a6effccb | |||
| 7108bf827d | |||
| e5ce3297ba | |||
| 0f7ba5c1d2 | |||
| 45266a3611 | |||
| bc6fa0ad98 | |||
| b528e38d53 | |||
| d6d923a26a | |||
| 970fc5dfd5 | |||
| d7136fa135 | |||
| 14f98eefcf | |||
| 15f6a8977a | |||
| cd10e95fd9 | |||
| f50885a3a4 | |||
| 17f6e10e4e | |||
| 69dc0ef1cf | |||
| 3f9d4a08a5 | |||
| 5d6e97dabb | |||
| 45a7f417ce | |||
| 62d098d787 | |||
| 6cc044522a | |||
| ff09683e6c | |||
| d65a32ba29 | |||
| 4ddc2a627c | |||
| aa0d546355 | |||
| 7bbab5d400 | |||
| ae9785389a | |||
| 5f4af646ee | |||
| c228509061 | |||
| 8ec7ddb92b | |||
| af390c4495 | |||
| ded345c09a | |||
| 5ecae0a17e | |||
| 16deaaf095 | |||
| 91d3150e6a | |||
| 1de403d9b7 | |||
| 930c1a303f | |||
| aacb7a3cda | |||
| 6e43990254 | |||
| 7556d45c17 | |||
| d19433e901 | |||
| 08dae87267 | |||
| 565a9c2af5 | |||
| fefdb9ad68 | |||
| 5f0e09f367 | |||
| 5bb3ac5c9a | |||
| 384e6b03b1 | |||
| d921a644ba | |||
| 60fcd69368 | |||
| 3799c7f535 | |||
| e8425ed3af | |||
| 4fc700ec96 | |||
| c9ee7a2092 | |||
| 54a1d40758 | |||
| 92a8afc1e4 | |||
| 46a52c6ad2 | |||
| ab5e184301 | |||
| 8b8c96ff3c | |||
| ae9cf72fb6 | |||
| 42ddf9c0da | |||
| 00f23b218a | |||
| 07685e8116 | |||
| 729b244321 | |||
| 147cd2557c | |||
| 6578994426 | |||
| fbef841ee3 | |||
| 6a4f5a7d80 | |||
| 4e25ca4aad | |||
| 8fd59d9009 | |||
| eda36d1eab | |||
| 9b2400e94e | |||
| 051b9b8317 | |||
| 1f27baf216 | |||
| 28d5ee7876 | |||
| 6aadb04fe1 | |||
| fe6470b852 | |||
| 858078a374 | |||
| 317af7632a | |||
| 53fcd633e5 | |||
| 40e11ee44a | |||
| 875ee2f1d2 | |||
| 3b9d4c2cc8 | |||
| 296e9ed320 | |||
| be526048f6 | |||
| 9f2a875b0f | |||
| 8f86550c41 | |||
| 7b4f2014c3 | |||
| 579a25273d | |||
| 9ff0798864 | |||
| fd80c7a6b7 | |||
| dcee7949db | |||
| 2e96a6508e | |||
| 81e59a9ae7 | |||
| 880224da1b | |||
| 07d73cc416 | |||
| c68967af02 | |||
| 7a868e586f | |||
| c9ecca60c1 | |||
| 8031c68d12 | |||
| 41d8472136 | |||
| d2304bfbe5 | |||
| 26ec6b3f69 | |||
| a057e10630 | |||
| 47373b3d37 | |||
| b21bbfbb53 | |||
| 9dd33a7e4a | |||
| 4205f0d819 | |||
| eb7c0f7d12 | |||
| 064c3f2b4b | |||
| 17d2e4a79d | |||
| c6df8dc20b | |||
| 8f66f8807d | |||
| 94fa7da52a | |||
| c61b50e6a0 | |||
| 950e44c82f | |||
| 71b49a5b87 | |||
| e7b0985d5e | |||
| ac4b7f1193 | |||
| 30354e039a | |||
| a9c9d74fa0 | |||
| 0570323dab | |||
| fc538e946a | |||
| 17c85b704c | |||
| 340bd2726a | |||
| 0ad3e036d2 | |||
| cfcfaba862 | |||
| 582f636756 | |||
| 726d5dfc65 | |||
| de8df592c9 | |||
| 30646de80a | |||
| bf7f52d775 | |||
| b1cf84453d | |||
| e0f895ea8e | |||
| 975c9f7a82 | |||
| 32e56d6e65 | |||
| adc56c3df0 | |||
| 546ca90251 | |||
| 1d32d1d64f | |||
| 31f71d93b9 | |||
| eb8938e15b | |||
| d49c11e4ec | |||
| a0183a8251 | |||
| 6c4989dca7 | |||
| 656fc5e11b | |||
| fec01c3415 | |||
| 27bdf9b710 | |||
| e6803f9345 | |||
| 7015334d6d | |||
| b6f7ebe5e6 | |||
| 5f2879986a | |||
| b53915eabb | |||
| a4cc053dd1 | |||
| 101667dc59 | |||
| c25da3ed87 | |||
| 82e32d8012 | |||
| 73822e2790 | |||
| a6f3c28afd | |||
| ed48cfd287 | |||
| a9d19d3f20 | |||
| 4a1748a0d8 | |||
| 399ed2ef31 | |||
| 14710e4652 | |||
| 72c7ab1fcd | |||
| 1feb3e130d | |||
| 120ab864a1 | |||
| 4be8f22e59 | |||
| 63c16676ff | |||
| 66de72d17f | |||
| fc6d356416 | |||
| e2d9c997d5 | |||
| a7d8af4a9e | |||
| 901e09e890 | |||
| 1cb8980be0 | |||
| 57efb76aba | |||
| 3394914940 | |||
| 78bcf502a3 | |||
| cd2e91aa18 | |||
| f72bb528e8 | |||
| 629905943f | |||
| 1f4b80c9f1 | |||
| 9cb46e24f2 | |||
| 0db430c5a7 | |||
| b8a0eb2472 | |||
| 45978714d4 | |||
| 219694730c | |||
| b36bc055d4 | |||
| d2a001ed67 | |||
| 1f6f68ad16 | |||
| 3a0d042395 | |||
| 889c9ca8d6 | |||
| ebd36d2ac2 | |||
| 46e121473f | |||
| c1f73e72b4 | |||
| 276e963f29 | |||
| c856a7f9bb | |||
| b6aed6feaa | |||
| 7a3233a7ca | |||
| cf7eab3db1 | |||
| 75b7539e32 | |||
| 90d42dfc84 | |||
| 92d60b64a8 | |||
| dce2e6c03a | |||
| 6c364c0011 | |||
| b6c5df8181 | |||
| 120c2ee7a6 | |||
| 058394a412 | |||
| 2acb070a39 | |||
| 52bc8499aa | |||
| ef4a652abe | |||
| 5282a67615 | |||
| d44fdcf54c | |||
| 30e2579222 | |||
| ce30baea59 | |||
| c56395fba6 | |||
| dabca5c457 | |||
| 5e077b8a2b | |||
| 7984048d45 | |||
| 767c5f4512 | |||
| 73e428979d | |||
| cbb2547c32 | |||
| 2b0b259d09 | |||
| 084973f8a2 | |||
| ac0f7dd2ab | |||
| 035d4eb72a | |||
| eec766f375 | |||
| 5212bfc961 | |||
| d1e52f7086 | |||
| 330b665f82 | |||
| e89be896df | |||
| ca2acd10c9 | |||
| 863d68a344 | |||
| c816ca79fa | |||
| fc021f968a | |||
| 2477b04d12 | |||
| a6af5fd054 | |||
| 8321e49c8a | |||
| 509eef8fcf | |||
| cb9c2320b6 | |||
| b11792ac11 | |||
| dc8a751815 | |||
| 8e6cbc6e18 | |||
| 33a8c2d06a | |||
| 9c4379153f | |||
| 00821c744c | |||
| 390326fd17 | |||
| cbc2b593e7 | |||
| f420af1016 | |||
| 67150560b2 | |||
| 9fac7a8d59 | |||
| e1ca7e93d9 | |||
| 8bd6cfb4c4 | |||
| a3afe546fd | |||
| db30a623bf | |||
| 3afcb70b3d | |||
| f9b58dd719 | |||
| 9ca6f8199e | |||
| 4fec7a276d | |||
| b70a1c71e1 | |||
| 90c450839d | |||
| b7a62b4286 | |||
| 33427c7c5d | |||
| 8fa9d159bc | |||
| f8f154236d | |||
| d54cc8fbd7 | |||
| 7e6fc779c7 | |||
| 3467d87c80 | |||
| 855957180a | |||
| 8862b80c56 | |||
| 8b8142d8d9 | |||
| f0260219fa | |||
| be36ead8b0 | |||
| f270bd8c8e | |||
| 357909b23b | |||
| 84302e4a76 | |||
| 2c45443ad7 | |||
| 77d7480096 | |||
| b57b9b41cd | |||
| cfa2b28de6 | |||
| e22723680d | |||
| db2a9784ef | |||
| 06d7da192b | |||
| 586a6da356 | |||
| 2ab950039c | |||
| 3b3c246aaa | |||
| b3b476ce3c | |||
| 682ee99293 | |||
| b8069c3372 | |||
| 992d1b29b4 | |||
| ceab4136aa | |||
| 8241aa75d5 | |||
| f186074a54 | |||
| 8585fe1404 | |||
| d4dff59b82 | |||
| af4b97f57a | |||
| c3341dea30 | |||
| 2b0b250162 | |||
| dd17f6b1bf | |||
| 4e1786f27f | |||
| 247437ce25 | |||
| 8d306e640f | |||
| 17d296f1ea | |||
| b8948751f9 | |||
| c9dfaec749 | |||
| 304aaf2b7b | |||
| 34153bcc77 | |||
| 65ef2f90bf | |||
| b12ab80cf5 | |||
| 9ee975f3a9 | |||
| fb02551622 | |||
| 64db9dc75a | |||
| 9d5931b6e2 | |||
| 0369198637 | |||
| 62b9a44045 | |||
| 1251f26d67 | |||
| 77a6c383bf | |||
| 53e81c00fa | |||
| 9480706466 | |||
| bb48ca47ef | |||
| 93b2fe7d38 | |||
| 6ba4712549 | |||
| b192dc7c64 | |||
| 2d9922f388 | |||
| 639f6143e7 | |||
| 0a27966b67 | |||
| 79b89c2d42 | |||
| 591107c208 | |||
| ccfc9f7bc9 | |||
| 06bf0c89d6 | |||
| c25f0087f7 | |||
| 12830e4214 | |||
| 2e1b2cf30e | |||
| 0bfb0a58a0 | |||
| 50ba1375e8 | |||
| 5f57b10712 | |||
| ec6720a836 | |||
| e00b006d51 | |||
| 7ce3918e83 | |||
| aa1c7e0a64 | |||
| cc87756c10 | |||
| e7a53e9410 | |||
| a050d3f41b | |||
| bc103afb53 | |||
| 50d1003bb7 | |||
| 5c899c722c | |||
| 73ad678e73 | |||
| 22a5445346 | |||
| 3ca8535203 | |||
| 7697ac6538 | |||
| c97eb87506 | |||
| 66b3c79275 | |||
| 773db77081 | |||
| a07acd9464 | |||
| 12b20cbf3b | |||
| fa31950c6a | |||
| 6ef5f4ced0 | |||
| c537ed7fed | |||
| f3b2f604e2 | |||
| f987001a61 | |||
| f1ec340cca | |||
| 6998cbfad4 | |||
| 655f4d4f3f | |||
| d9e22f40b4 | |||
| df8a5aeab5 | |||
| 17cc12c381 | |||
| e76c5c764b | |||
| be45f4c47f | |||
| fa7e218546 | |||
| 5c0a5a531d | |||
| 200ec6b6ac | |||
| 0e7dc879de | |||
| 70fe644191 | |||
| 5178b349e8 | |||
| c8e335fbe3 | |||
| 567e587dbb | |||
| 37348f1c2e | |||
| e2feddadd9 | |||
| 0a62138795 | |||
| c24a16a819 | |||
| 8091ab8078 | |||
| 12b9c6051e | |||
| 92e6eb437c | |||
| 32866a1758 | |||
| 9ce415b207 | |||
| ec69ae5929 | |||
| f5545b5c0e | |||
| 7c22b71916 | |||
| b5414af214 | |||
| 686b5beb23 | |||
| af87a57052 | |||
| dec6e261e2 | |||
| 24058ace40 | |||
| d552427f61 | |||
| 0eeb391472 | |||
| 433556de1f | |||
| b24f13eca2 | |||
| 65a9b26791 | |||
| 2cc04e2b29 | |||
| 6be44e7d06 | |||
| 93b3efa5a7 | |||
| 2908ed5405 | |||
| 966c8a37cb | |||
| c8637d645e | |||
| b427f21cca | |||
| 25a0001b9f | |||
| 843cc0ee8d | |||
| 0172d13163 | |||
| 78bddace2c | |||
| 779c1687a4 | |||
| c99a83bc5c | |||
| 0b523e6031 | |||
| 4e5dee71b4 | |||
| 8cd8daa285 | |||
| 1bd007e815 | |||
| 9c35064345 | |||
| 5945a1a926 | |||
| fac91d11d8 | |||
| 9fcd6522ce | |||
| 85673c859e | |||
| 4dba8e8359 | |||
| f03c875cf3 | |||
| 9fcb5ece12 | |||
| b07833ddf4 | |||
| 333f6cb753 | |||
| 1c594e729b | |||
| d8c8f544b3 | |||
| a284ced9a1 | |||
| cb82824f6a | |||
| 613c37d54b | |||
| 53f9417795 | |||
| 407807df6b | |||
| 7c80e15392 | |||
| e95c0a90ac | |||
| 48905f6b75 | |||
| b5b562b7ee | |||
| 4a82e72b00 | |||
| ef95f5262f | |||
| e2d911c07b | |||
| 71bae0da0f | |||
| e7bfbaf7b2 | |||
| 6882d7399b | |||
| 80bf03d583 | |||
| 1039537d83 | |||
| 0214c50b18 | |||
| e29e8373e6 | |||
| 9e11009eb1 | |||
| a01ca7a600 | |||
| 6a662825f7 | |||
| 03e3219884 | |||
| 681b7f4e78 | |||
| f098f28d01 | |||
| bf17d50cf6 | |||
| 8e0f036b29 | |||
| 51c6924206 | |||
| f589a25459 | |||
| dee389dff6 | |||
| a9b83b3c01 | |||
| 0ab0a293a8 | |||
| b76093e90c | |||
| 8f0d40a39c | |||
| 3a2d8a175e | |||
| ff72ca7340 | |||
| 4e346470a5 | |||
| 13c76cc295 | |||
| 2c4426bb10 | |||
| d3a9e2935d | |||
| 2adc010b85 | |||
| c330c9276c | |||
| e9b443b3a1 | |||
| 7bb692666b | |||
| 5e924f295a | |||
| 739cbcf7e9 | |||
| 22df02089a | |||
| be2afb52c1 | |||
| 09520f8e0d | |||
| d661fbfbc1 | |||
| e29069d45b | |||
| a2dcfd5bf6 | |||
| ebe19aa434 | |||
| 898dd7bf5b | |||
| 4423581f8d | |||
| 9597baa6e8 | |||
| 04073c7e31 |
@@ -0,0 +1,95 @@
|
||||
# Generated by "grunt ignorefiles"
|
||||
*/**/yui/src/*/meta/
|
||||
*/**/build/
|
||||
node_modules/
|
||||
vendor/
|
||||
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
|
||||
auth/cas/CAS/
|
||||
cache/stores/mongodb/MongoDB/
|
||||
enrol/lti/ims-blti/
|
||||
filter/algebra/AlgParser.pm
|
||||
filter/tex/mimetex.*
|
||||
h5p/h5plib/v124/joubel/core/
|
||||
h5p/h5plib/v124/joubel/editor/
|
||||
lib/editor/atto/plugins/html/yui/src/codemirror/
|
||||
lib/editor/atto/plugins/html/yui/src/beautify/
|
||||
lib/editor/atto/yui/src/rangy/js/*.*
|
||||
lib/editor/tinymce/plugins/pdw/tinymce/
|
||||
lib/editor/tinymce/plugins/spellchecker/rpc.php
|
||||
lib/editor/tinymce/tiny_mce/
|
||||
lib/mlbackend/php/phpml/
|
||||
lib/adodb/
|
||||
lib/behat/axe/
|
||||
lib/bennu/
|
||||
lib/evalmath/
|
||||
lib/phpspreadsheet/
|
||||
lib/google/
|
||||
lib/htmlpurifier/
|
||||
lib/jabber/
|
||||
lib/minify/matthiasmullie-minify/
|
||||
lib/minify/matthiasmullie-pathconverter/
|
||||
lib/pear/HTML/Common.php
|
||||
lib/pear/HTML/QuickForm.php
|
||||
lib/pear/HTML/QuickForm/
|
||||
lib/pear/PEAR.php
|
||||
lib/phpmailer/
|
||||
lib/simplepie/
|
||||
lib/tcpdf/
|
||||
lib/typo3/
|
||||
lib/yuilib/
|
||||
lib/yuilib/gallery/
|
||||
lib/jquery/
|
||||
lib/html2text/
|
||||
lib/markdown/
|
||||
lib/xhprof/
|
||||
lib/horde/
|
||||
lib/requirejs/
|
||||
lib/amd/src/loglevel.js
|
||||
lib/mustache/
|
||||
lib/amd/src/mustache.js
|
||||
lib/graphlib.php
|
||||
lib/php-css-parser/
|
||||
lib/rtlcss/
|
||||
lib/scssphp/
|
||||
lib/spout/
|
||||
lib/amd/src/chartjs-lazy.js
|
||||
lib/maxmind/GeoIp2/
|
||||
lib/maxmind/MaxMind/
|
||||
lib/ltiprovider/
|
||||
lib/amd/src/truncate.js
|
||||
lib/fonts/
|
||||
lib/amd/src/adapter.js
|
||||
lib/validateurlsyntax.php
|
||||
lib/amd/src/popper.js
|
||||
lib/geopattern-php/
|
||||
lib/php-jwt/
|
||||
lib/babel-polyfill/
|
||||
lib/polyfills/
|
||||
lib/emoji-data/
|
||||
lib/plist/
|
||||
lib/zipstream/
|
||||
lib/php-enum/
|
||||
lib/http-message/
|
||||
media/player/videojs/amd/src/video-lazy.js
|
||||
media/player/videojs/amd/src/Youtube-lazy.js
|
||||
media/player/videojs/videojs/
|
||||
media/player/videojs/amd/src/videojs-flash-lazy.js
|
||||
media/player/videojs/videojs/video-js.swf
|
||||
mod/assign/feedback/editpdf/fpdi/
|
||||
repository/s3/S3.php
|
||||
theme/boost/scss/bootstrap/
|
||||
theme/boost/amd/src/bootstrap/alert.js
|
||||
theme/boost/amd/src/bootstrap/button.js
|
||||
theme/boost/amd/src/bootstrap/carousel.js
|
||||
theme/boost/amd/src/bootstrap/collapse.js
|
||||
theme/boost/amd/src/bootstrap/dropdown.js
|
||||
theme/boost/amd/src/bootstrap/index.js
|
||||
theme/boost/amd/src/bootstrap/modal.js
|
||||
theme/boost/amd/src/bootstrap/popover.js
|
||||
theme/boost/amd/src/bootstrap/tools/sanitizer.js
|
||||
theme/boost/amd/src/bootstrap/scrollspy.js
|
||||
theme/boost/amd/src/bootstrap/tab.js
|
||||
theme/boost/amd/src/bootstrap/toast.js
|
||||
theme/boost/amd/src/bootstrap/tooltip.js
|
||||
theme/boost/amd/src/bootstrap/util.js
|
||||
theme/boost/scss/fontawesome/
|
||||
@@ -2,7 +2,6 @@
|
||||
'plugins': [
|
||||
'babel',
|
||||
'promise',
|
||||
'jsdoc',
|
||||
],
|
||||
'env': {
|
||||
'browser': true,
|
||||
@@ -192,6 +191,7 @@
|
||||
'property': 'str',
|
||||
'message': 'Use AMD module "core/str" or M.util.get_string()'
|
||||
}],
|
||||
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@@ -200,34 +200,18 @@
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
|
||||
// === JSDocs ===
|
||||
"jsdoc/check-access": 'off',
|
||||
"jsdoc/check-alignment": 'off',
|
||||
"jsdoc/check-param-names": 'off',
|
||||
"jsdoc/check-property-names": 'off',
|
||||
"jsdoc/empty-tags": 'off',
|
||||
"jsdoc/implements-on-classes": 'off',
|
||||
"jsdoc/multiline-blocks": 'off',
|
||||
"jsdoc/require-jsdoc": 'off',
|
||||
"jsdoc/require-param": 'off',
|
||||
"jsdoc/require-param-name": 'off',
|
||||
"jsdoc/require-param-type": 'off',
|
||||
"jsdoc/require-property": 'off',
|
||||
"jsdoc/require-property-name": 'off',
|
||||
"jsdoc/require-property-type": 'off',
|
||||
'no-unused-expressions': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["**/amd/src/*.js", "**/amd/src/**/*.js", "Gruntfile.js", ".grunt/*.js", ".grunt/tasks/*.js", "jsdoc.conf.js"],
|
||||
files: ["**/amd/src/*.js", "**/amd/src/**/*.js", "Gruntfile*.js", "babel-plugin-add-module-to-define.js"],
|
||||
// We support es6 now. Woot!
|
||||
env: {
|
||||
es6: true
|
||||
},
|
||||
// We're using babel transpiling so use their parser
|
||||
// for linting.
|
||||
parser: '@babel/eslint-parser',
|
||||
parser: 'babel-eslint',
|
||||
// Check AMD with some slightly stricter rules.
|
||||
rules: {
|
||||
'no-unused-vars': 'error',
|
||||
@@ -254,54 +238,11 @@
|
||||
// === Promises ===
|
||||
// We have Promise now that we're using ES6.
|
||||
'promise/no-native': 'off',
|
||||
'promise/avoid-new': 'off',
|
||||
|
||||
// === JSDocs ===
|
||||
"jsdoc/check-access": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/check-alignment": 1, // Recommended.
|
||||
"jsdoc/check-param-names": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/check-property-names": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/empty-tags": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/implements-on-classes": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/multiline-blocks": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-jsdoc": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-param": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-param-name": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-param-type": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-property": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-property-name": [
|
||||
'error',
|
||||
],
|
||||
"jsdoc/require-property-type": [
|
||||
'error',
|
||||
],
|
||||
'promise/avoid-new': 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
'ecmaVersion': 9,
|
||||
'sourceType': 'module',
|
||||
'requireConfigFile': false,
|
||||
'sourceType': 'module'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+10
-22
@@ -1,28 +1,21 @@
|
||||
name: Core
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
- MOODLE_[0-9]+_STABLE
|
||||
tags-ignore:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
php: 8.0
|
||||
php: 7.4
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Configuring node & npm
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
shell: bash -l {0}
|
||||
run: nvm install
|
||||
|
||||
- name: Installing node stuff
|
||||
run: npm install
|
||||
@@ -54,14 +47,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
php: 7.3
|
||||
extensions:
|
||||
- os: ubuntu-18.04
|
||||
php: 7.2
|
||||
db: mysqli
|
||||
- os: ubuntu-20.04
|
||||
php: 8.0
|
||||
- os: ubuntu-18.04
|
||||
php: 7.4
|
||||
db: pgsql
|
||||
extensions: xmlrpc-beta
|
||||
|
||||
steps:
|
||||
- name: Setting up DB mysql
|
||||
@@ -91,8 +82,6 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: ${{ matrix.extensions }}
|
||||
ini-values: max_input_vars=5000
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
@@ -111,5 +100,4 @@ jobs:
|
||||
- name: Running PHPUnit tests
|
||||
env:
|
||||
dbtype: ${{ matrix.db }}
|
||||
phpunit_options: ${{ secrets.phpunit_options }}
|
||||
run: vendor/bin/phpunit $phpunit_options
|
||||
run: vendor/bin/phpunit -v
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
/cscope.*
|
||||
/.patches/
|
||||
/.idea/
|
||||
.phpstorm.*
|
||||
/nbproject/
|
||||
CVS
|
||||
.DS_Store
|
||||
@@ -47,8 +46,3 @@ composer.phar
|
||||
atlassian-ide-plugin.xml
|
||||
/node_modules/
|
||||
/.vscode/
|
||||
moodle-plugin-ci.phar
|
||||
.eslintignore
|
||||
.stylelintignore
|
||||
/jsdoc
|
||||
.phpcs.xml
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# Moodle JavaScript Documentation
|
||||
|
||||
```
|
||||
.-..-.
|
||||
_____ | || |
|
||||
/____/-.---_ .---. .---. .-.| || | .---.
|
||||
| | _ _ |/ _ \/ _ \/ _ || |/ __ \
|
||||
* | | | | | || |_| || |_| || |_| || || |___/
|
||||
|_| |_| |_|\_____/\_____/\_____||_|\_____)
|
||||
|
||||
Moodle - the world's open source learning platform
|
||||
|
||||
```
|
||||
|
||||
## About
|
||||
This generated documentation includes API documentation for JavaScript written in the AMD and ES2015 module formats within Moodle.
|
||||
|
||||
## Related information
|
||||
See [https://docs.moodle.org/dev](https://docs.moodle.org/dev) for other related Developer Documentation.
|
||||
@@ -1,131 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Helper functions for working with Moodle component names, directories, and sources.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
/* eslint-env node */
|
||||
|
||||
// Do not include any plugins as stanard.
|
||||
const plugins = [];
|
||||
|
||||
plugins.push('plugins/markdown');
|
||||
|
||||
/**
|
||||
* Get the source configuration.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
const getSource = () => {
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
const ComponentList = require(path.resolve('.grunt/components.js'));
|
||||
const thirdPartyPaths = ComponentList.getThirdPartyPaths();
|
||||
|
||||
const source = {
|
||||
include: [],
|
||||
includePattern: ".+\\.js$",
|
||||
};
|
||||
|
||||
let includeList = [];
|
||||
|
||||
ComponentList.getAmdSrcGlobList().forEach(async pattern => {
|
||||
includeList.push(...glob.sync(pattern));
|
||||
});
|
||||
|
||||
const cwdLength = process.cwd().length + 1;
|
||||
includeList.forEach(path => {
|
||||
if (source.include.indexOf(path) !== -1) {
|
||||
// Ensure no duplicates.
|
||||
return;
|
||||
}
|
||||
|
||||
const relPath = path.substring(cwdLength);
|
||||
if (thirdPartyPaths.indexOf(relPath) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
source.include.push(path);
|
||||
});
|
||||
|
||||
source.include.push('.grunt/jsdoc/README.md');
|
||||
return source;
|
||||
};
|
||||
|
||||
const tags = {
|
||||
// Allow the use of unknown tags.
|
||||
// We have a lot of legacy uses of these.
|
||||
allowUnknownTags: true,
|
||||
|
||||
// We make use of jsdoc and closure dictionaries as standard.
|
||||
dictionaries: [
|
||||
'jsdoc',
|
||||
'closure',
|
||||
],
|
||||
};
|
||||
|
||||
// Template configuraiton.
|
||||
const templates = {
|
||||
cleverLinks: false,
|
||||
monospaceLinks: false,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
opts: {
|
||||
destination: "./jsdoc/",
|
||||
template: "node_modules/docdash",
|
||||
},
|
||||
plugins,
|
||||
recurseDepth: 10,
|
||||
source: getSource(),
|
||||
sourceType: 'module',
|
||||
tags,
|
||||
templates,
|
||||
docdash: {
|
||||
collapse: true,
|
||||
search: true,
|
||||
sort: true,
|
||||
sectionOrder: [
|
||||
"Namespaces",
|
||||
"Modules",
|
||||
"Events",
|
||||
"Classes",
|
||||
"Externals",
|
||||
"Mixins",
|
||||
"Tutorials",
|
||||
"Interfaces"
|
||||
],
|
||||
"menu": {
|
||||
"Developer Docs": {
|
||||
href: "https://docs.moodle.org/dev",
|
||||
target: "_blank",
|
||||
"class": "menu-item",
|
||||
id: "devdocs"
|
||||
},
|
||||
"MDN Docs": {
|
||||
href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
|
||||
target: "_blank",
|
||||
"class": "menu-item",
|
||||
id: "mdndocs",
|
||||
},
|
||||
},
|
||||
typedefs: true,
|
||||
},
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
const files = grunt.moodleEnv.files;
|
||||
|
||||
// Project configuration.
|
||||
grunt.config.merge({
|
||||
eslint: {
|
||||
// Even though warnings dont stop the build we don't display warnings by default because
|
||||
// at this moment we've got too many core warnings.
|
||||
// To display warnings call: grunt eslint --show-lint-warnings
|
||||
// To fail on warnings call: grunt eslint --max-lint-warnings=0
|
||||
// Also --max-lint-warnings=-1 can be used to display warnings but not fail.
|
||||
options: {
|
||||
quiet: (!grunt.option('show-lint-warnings')) && (typeof grunt.option('max-lint-warnings') === 'undefined'),
|
||||
maxWarnings: ((typeof grunt.option('max-lint-warnings') !== 'undefined') ? grunt.option('max-lint-warnings') : -1)
|
||||
},
|
||||
|
||||
// Check AMD src files.
|
||||
amd: {src: files ? files : grunt.moodleEnv.amdSrc},
|
||||
|
||||
// Check YUI module source files.
|
||||
yui: {src: files ? files : grunt.moodleEnv.yuiSrc},
|
||||
},
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
|
||||
// On watch, we dynamically modify config to build only affected files. This
|
||||
// method is slightly complicated to deal with multiple changed files at once (copied
|
||||
// from the grunt-contrib-watch readme).
|
||||
let changedFiles = Object.create(null);
|
||||
const onChange = grunt.util._.debounce(function() {
|
||||
const files = Object.keys(changedFiles);
|
||||
grunt.config('eslint.amd.src', files);
|
||||
grunt.config('eslint.yui.src', files);
|
||||
changedFiles = Object.create(null);
|
||||
}, 200);
|
||||
|
||||
grunt.event.on('watch', (action, filepath) => {
|
||||
changedFiles[filepath] = action;
|
||||
onChange();
|
||||
});
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
/**
|
||||
* Get the list of feature files to pass to the gherkin linter.
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
const getGherkinLintTargets = () => {
|
||||
if (grunt.moodleEnv.files) {
|
||||
// Specific files were requested. Only check these.
|
||||
return grunt.moodleEnv.files;
|
||||
}
|
||||
|
||||
if (grunt.moodleEnv.inComponent) {
|
||||
return [`${grunt.moodleEnv.runDir}/tests/behat/*.feature`];
|
||||
}
|
||||
|
||||
return ['**/tests/behat/*.feature'];
|
||||
};
|
||||
|
||||
const handler = function() {
|
||||
const done = this.async();
|
||||
const options = grunt.config('gherkinlint.options');
|
||||
|
||||
// Grab the gherkin-lint linter and required scaffolding.
|
||||
const linter = require('gherkin-lint/dist/linter.js');
|
||||
const featureFinder = require('gherkin-lint/dist/feature-finder.js');
|
||||
const configParser = require('gherkin-lint/dist/config-parser.js');
|
||||
const formatter = require('gherkin-lint/dist/formatters/stylish.js');
|
||||
|
||||
// Run the linter.
|
||||
return linter.lint(
|
||||
featureFinder.getFeatureFiles(grunt.file.expand(options.files)),
|
||||
configParser.getConfiguration(configParser.defaultConfigFileName)
|
||||
)
|
||||
.then(results => {
|
||||
// Print the results out uncondtionally.
|
||||
formatter.printResults(results);
|
||||
|
||||
return results;
|
||||
})
|
||||
.then(results => {
|
||||
// Report on the results.
|
||||
// The done function takes a bool whereby a falsey statement causes the task to fail.
|
||||
return results.every(result => result.errors.length === 0);
|
||||
})
|
||||
.then(done); // eslint-disable-line promise/no-callback-in-promise
|
||||
};
|
||||
|
||||
grunt.registerTask('gherkinlint', 'Run gherkinlint against the current directory', handler);
|
||||
|
||||
grunt.config.set('gherkinlint', {
|
||||
options: {
|
||||
files: getGherkinLintTargets(),
|
||||
}
|
||||
});
|
||||
|
||||
grunt.config.merge({
|
||||
watch: {
|
||||
gherkinlint: {
|
||||
files: [grunt.moodleEnv.inComponent ? 'tests/behat/*.feature' : '**/tests/behat/*.feature'],
|
||||
tasks: ['gherkinlint'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return handler;
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
|
||||
/**
|
||||
* Generate the PHPCS configuration.
|
||||
*
|
||||
* @param {Object} thirdPartyPaths
|
||||
*/
|
||||
const phpcsIgnore = (thirdPartyPaths) => {
|
||||
const {toXML} = require('jstoxml');
|
||||
|
||||
const config = {
|
||||
_name: 'ruleset',
|
||||
_attrs: {
|
||||
name: "MoodleCore",
|
||||
},
|
||||
_content: [
|
||||
{
|
||||
rule: {
|
||||
_attrs: {
|
||||
ref: './.phpcs.xml.dist',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
thirdPartyPaths.forEach(library => {
|
||||
config._content.push({
|
||||
'exclude-pattern': library,
|
||||
});
|
||||
});
|
||||
|
||||
grunt.file.write('.phpcs.xml', toXML(config, {
|
||||
header: true,
|
||||
indent: ' ',
|
||||
}) + "\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate ignore files (utilising thirdpartylibs.xml data)
|
||||
*/
|
||||
const handler = function() {
|
||||
const path = require('path');
|
||||
const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
|
||||
|
||||
// An array of paths to third party directories.
|
||||
const thirdPartyPaths = ComponentList.getThirdPartyPaths();
|
||||
|
||||
// Generate .eslintignore.
|
||||
const eslintIgnores = [
|
||||
'# Generated by "grunt ignorefiles"',
|
||||
// Do not ignore the .grunt directory.
|
||||
'!/.grunt',
|
||||
|
||||
// Ignore all yui/src meta directories and build directories.
|
||||
'*/**/yui/src/*/meta/',
|
||||
'*/**/build/',
|
||||
].concat(thirdPartyPaths);
|
||||
grunt.file.write('.eslintignore', eslintIgnores.join('\n') + '\n');
|
||||
|
||||
// Generate .stylelintignore.
|
||||
const stylelintIgnores = [
|
||||
'# Generated by "grunt ignorefiles"',
|
||||
'**/yui/build/*',
|
||||
'theme/boost/style/moodle.css',
|
||||
'theme/classic/style/moodle.css',
|
||||
'jsdoc/styles/*.css',
|
||||
].concat(thirdPartyPaths);
|
||||
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n') + '\n');
|
||||
|
||||
phpcsIgnore(thirdPartyPaths);
|
||||
};
|
||||
|
||||
grunt.registerTask('ignorefiles', 'Generate ignore files for linters', handler);
|
||||
|
||||
return handler;
|
||||
};
|
||||
@@ -1,222 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function to generate the destination for the uglify task
|
||||
* (e.g. build/file.min.js). This function will be passed to
|
||||
* the rename property of files array when building dynamically:
|
||||
* http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
|
||||
*
|
||||
* @param {String} destPath the current destination
|
||||
* @param {String} srcPath the matched src path
|
||||
* @return {String} The rewritten destination path.
|
||||
*/
|
||||
|
||||
const babelRename = function(destPath, srcPath) {
|
||||
destPath = srcPath.replace('src', 'build');
|
||||
destPath = destPath.replace('.js', '.min.js');
|
||||
return destPath;
|
||||
};
|
||||
|
||||
module.exports = grunt => {
|
||||
// Load the Ignorefiles tasks.
|
||||
require('./ignorefiles')(grunt);
|
||||
|
||||
// Load the Shifter tasks.
|
||||
require('./shifter')(grunt);
|
||||
|
||||
// Load ESLint.
|
||||
require('./eslint')(grunt);
|
||||
|
||||
// Load JSDoc.
|
||||
require('./jsdoc')(grunt);
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// Register JS tasks.
|
||||
grunt.registerTask('yui', ['eslint:yui', 'shifter']);
|
||||
grunt.registerTask('amd', ['ignorefiles', 'eslint:amd', 'rollup']);
|
||||
grunt.registerTask('js', ['amd', 'yui']);
|
||||
|
||||
// Register NPM tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-rollup');
|
||||
|
||||
const babelTransform = require('@babel/core').transform;
|
||||
const babel = (options = {}) => {
|
||||
return {
|
||||
name: 'babel',
|
||||
|
||||
transform: (code, id) => {
|
||||
grunt.log.debug(`Transforming ${id}`);
|
||||
options.filename = id;
|
||||
const transformed = babelTransform(code, options);
|
||||
|
||||
return {
|
||||
code: transformed.code,
|
||||
map: transformed.map
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Note: We have to use a rate limit plugin here because rollup runs all tasks asynchronously and in parallel.
|
||||
// When we kick off a full run, if we kick off a rollup of every file this will fork-bomb the machine.
|
||||
// To work around this we use a concurrent Promise queue based on the number of available processors.
|
||||
const rateLimit = () => {
|
||||
const queue = [];
|
||||
let queueRunner;
|
||||
|
||||
const startQueue = () => {
|
||||
if (queueRunner) {
|
||||
return;
|
||||
}
|
||||
|
||||
queueRunner = setTimeout(() => {
|
||||
const limit = Math.max(1, require('os').cpus().length / 2);
|
||||
grunt.log.debug(`Starting rollup with queue size of ${limit}`);
|
||||
runQueue(limit);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// The queue runner will run the next `size` items in the queue.
|
||||
const runQueue = (size = 1) => {
|
||||
queue.splice(0, size).forEach(resolve => {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'ratelimit',
|
||||
|
||||
// The options hook is run in parallel.
|
||||
// We can return an unresolved Promise which is queued for later resolution.
|
||||
options: async() => {
|
||||
return new Promise(resolve => {
|
||||
queue.push(resolve);
|
||||
startQueue();
|
||||
});
|
||||
},
|
||||
|
||||
// When an item in the queue completes, start the next item in the queue.
|
||||
buildEnd: () => {
|
||||
runQueue();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const terser = require('rollup-plugin-terser').terser;
|
||||
grunt.config.merge({
|
||||
rollup: {
|
||||
options: {
|
||||
format: 'esm',
|
||||
dir: 'output',
|
||||
sourcemap: true,
|
||||
treeshake: false,
|
||||
context: 'window',
|
||||
plugins: [
|
||||
rateLimit(),
|
||||
babel({
|
||||
sourceMaps: true,
|
||||
comments: false,
|
||||
compact: false,
|
||||
plugins: [
|
||||
'transform-es2015-modules-amd-lazy',
|
||||
'system-import-transformer',
|
||||
// This plugin modifies the Babel transpiling for "export default"
|
||||
// so that if it's used then only the exported value is returned
|
||||
// by the generated AMD module.
|
||||
//
|
||||
// It also adds the Moodle plugin name to the AMD module definition
|
||||
// so that it can be imported as expected in other modules.
|
||||
path.resolve('.grunt/babel-plugin-add-module-to-define.js'),
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
['@babel/plugin-proposal-class-properties', {'loose': false}],
|
||||
'@babel/plugin-proposal-json-strings'
|
||||
],
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
targets: {
|
||||
browsers: [
|
||||
">0.25%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10",
|
||||
"not op_mini all",
|
||||
"not Opera > 0",
|
||||
"not dead"
|
||||
]
|
||||
},
|
||||
modules: false,
|
||||
useBuiltIns: false
|
||||
}]
|
||||
]
|
||||
}),
|
||||
|
||||
terser({
|
||||
// Do not mangle variables.
|
||||
// Makes debugging easier.
|
||||
mangle: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
src: grunt.moodleEnv.files ? grunt.moodleEnv.files : grunt.moodleEnv.amdSrc,
|
||||
rename: babelRename
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
grunt.config.merge({
|
||||
watch: {
|
||||
amd: {
|
||||
files: grunt.moodleEnv.inComponent
|
||||
? ['amd/src/*.js', 'amd/src/**/*.js']
|
||||
: ['**/amd/src/**/*.js'],
|
||||
tasks: ['amd']
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// On watch, we dynamically modify config to build only affected files. This
|
||||
// method is slightly complicated to deal with multiple changed files at once (copied
|
||||
// from the grunt-contrib-watch readme).
|
||||
let changedFiles = Object.create(null);
|
||||
const onChange = grunt.util._.debounce(function() {
|
||||
const files = Object.keys(changedFiles);
|
||||
grunt.config('rollup.dist.files', [{expand: true, src: files, rename: babelRename}]);
|
||||
changedFiles = Object.create(null);
|
||||
}, 200);
|
||||
|
||||
grunt.event.on('watch', function(action, filepath) {
|
||||
changedFiles[filepath] = action;
|
||||
onChange();
|
||||
});
|
||||
|
||||
return {
|
||||
babelRename,
|
||||
};
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
// Project configuration.
|
||||
grunt.config.merge({
|
||||
jsdoc: {
|
||||
dist: {
|
||||
options: {
|
||||
configure: ".grunt/jsdoc/jsdoc.conf.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
grunt.loadNpmTasks('grunt-sass');
|
||||
|
||||
grunt.config.merge({
|
||||
sass: {
|
||||
dist: {
|
||||
files: {
|
||||
"theme/boost/style/moodle.css": "theme/boost/scss/preset/default.scss",
|
||||
"theme/classic/style/moodle.css": "theme/classic/scss/classicgrunt.scss"
|
||||
}
|
||||
},
|
||||
options: {
|
||||
implementation: require('node-sass'),
|
||||
includePaths: ["theme/boost/scss/", "theme/classic/scss/"]
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,155 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
module.exports = grunt => {
|
||||
/**
|
||||
* Shifter task. Is configured with a path to a specific file or a directory,
|
||||
* in the case of a specific file it will work out the right module to be built.
|
||||
*
|
||||
* Note that this task runs the invidiaul shifter jobs async (becase it spawns
|
||||
* so be careful to to call done().
|
||||
*/
|
||||
const handler = function() {
|
||||
const done = this.async();
|
||||
const options = grunt.config('shifter.options');
|
||||
const async = require('async');
|
||||
const path = require('path');
|
||||
|
||||
// Run the shifter processes one at a time to avoid confusing output.
|
||||
async.eachSeries(options.paths, function(src, filedone) {
|
||||
var args = [];
|
||||
args.push(path.normalize(process.cwd() + '/node_modules/shifter/bin/shifter'));
|
||||
|
||||
// Always ignore the node_modules directory.
|
||||
args.push('--excludes', 'node_modules');
|
||||
|
||||
// Determine the most appropriate options to run with based upon the current location.
|
||||
if (grunt.file.isMatch('**/yui/**/*.js', src)) {
|
||||
// When passed a JS file, build our containing module (this happen with
|
||||
// watch).
|
||||
grunt.log.debug('Shifter passed a specific JS file');
|
||||
src = path.dirname(path.dirname(src));
|
||||
options.recursive = false;
|
||||
} else if (grunt.file.isMatch('**/yui/src', src)) {
|
||||
// When in a src directory --walk all modules.
|
||||
grunt.log.debug('In a src directory');
|
||||
args.push('--walk');
|
||||
options.recursive = false;
|
||||
} else if (grunt.file.isMatch('**/yui/src/*', src)) {
|
||||
// When in module, only build our module.
|
||||
grunt.log.debug('In a module directory');
|
||||
options.recursive = false;
|
||||
} else if (grunt.file.isMatch('**/yui/src/*/js', src)) {
|
||||
// When in module src, only build our module.
|
||||
grunt.log.debug('In a source directory');
|
||||
src = path.dirname(src);
|
||||
options.recursive = false;
|
||||
}
|
||||
|
||||
if (grunt.option('watch')) {
|
||||
grunt.fail.fatal('The --watch option has been removed, please use `grunt watch` instead');
|
||||
}
|
||||
|
||||
// Add the stderr option if appropriate
|
||||
if (grunt.option('verbose')) {
|
||||
args.push('--lint-stderr');
|
||||
}
|
||||
|
||||
if (grunt.option('no-color')) {
|
||||
args.push('--color=false');
|
||||
}
|
||||
|
||||
var execShifter = function() {
|
||||
|
||||
grunt.log.ok("Running shifter on " + src);
|
||||
grunt.util.spawn({
|
||||
cmd: "node",
|
||||
args: args,
|
||||
opts: {cwd: src, stdio: 'inherit', env: process.env}
|
||||
}, function(error, result, code) {
|
||||
if (code) {
|
||||
grunt.fail.fatal('Shifter failed with code: ' + code);
|
||||
} else {
|
||||
grunt.log.ok('Shifter build complete.');
|
||||
filedone();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Actually run shifter.
|
||||
if (!options.recursive) {
|
||||
execShifter();
|
||||
} else {
|
||||
// Check that there are yui modules otherwise shifter ends with exit code 1.
|
||||
if (grunt.file.expand({cwd: src}, '**/yui/src/**/*.js').length > 0) {
|
||||
args.push('--recursive');
|
||||
execShifter();
|
||||
} else {
|
||||
grunt.log.ok('No YUI modules to build.');
|
||||
filedone();
|
||||
}
|
||||
}
|
||||
}, done);
|
||||
};
|
||||
|
||||
// Register the shifter task.
|
||||
grunt.registerTask('shifter', 'Run Shifter against the current directory', handler);
|
||||
|
||||
// Configure it.
|
||||
grunt.config.set('shifter', {
|
||||
options: {
|
||||
recursive: true,
|
||||
// Shifter takes a relative path.
|
||||
paths: grunt.moodleEnv.files ? grunt.moodleEnv.files : [grunt.moodleEnv.runDir]
|
||||
}
|
||||
});
|
||||
|
||||
grunt.config.merge({
|
||||
watch: {
|
||||
yui: {
|
||||
files: grunt.moodleEnv.inComponent
|
||||
? ['yui/src/*.json', 'yui/src/**/*.js']
|
||||
: ['**/yui/src/**/*.js'],
|
||||
tasks: ['yui']
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// On watch, we dynamically modify config to build only affected files. This
|
||||
// method is slightly complicated to deal with multiple changed files at once (copied
|
||||
// from the grunt-contrib-watch readme).
|
||||
let changedFiles = Object.create(null);
|
||||
const onChange = grunt.util._.debounce(function() {
|
||||
const files = Object.keys(changedFiles);
|
||||
grunt.config('shifter.options.paths', files);
|
||||
changedFiles = Object.create(null);
|
||||
}, 200);
|
||||
|
||||
grunt.event.on('watch', (action, filepath) => {
|
||||
changedFiles[filepath] = action;
|
||||
onChange();
|
||||
});
|
||||
|
||||
return handler;
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
/**
|
||||
* Generate ignore files (utilising thirdpartylibs.xml data)
|
||||
*/
|
||||
const handler = function() {
|
||||
const path = require('path');
|
||||
|
||||
// Are we in a YUI directory?
|
||||
if (path.basename(path.resolve(grunt.moodleEnv.cwd, '../../')) == 'yui') {
|
||||
grunt.task.run('yui');
|
||||
// Are we in an AMD directory?
|
||||
} else if (grunt.moodleEnv.inAMD) {
|
||||
grunt.task.run('amd');
|
||||
} else {
|
||||
// Run them all!.
|
||||
grunt.task.run('css');
|
||||
grunt.task.run('js');
|
||||
grunt.task.run('gherkinlint');
|
||||
}
|
||||
};
|
||||
|
||||
// Register the startup task.
|
||||
grunt.registerTask('startup', 'Run the correct tasks for the current directory', handler);
|
||||
|
||||
return handler;
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
// Load the ignorefiles tasks.
|
||||
require('./ignorefiles')(grunt);
|
||||
|
||||
// Load the Style Lint tasks.
|
||||
require('./stylelint')(grunt);
|
||||
|
||||
// Load the SASS tasks.
|
||||
require('./sass')(grunt);
|
||||
};
|
||||
@@ -1,183 +0,0 @@
|
||||
// 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/>.
|
||||
/* jshint node: true, browser: false */
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* @copyright 2021 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = grunt => {
|
||||
|
||||
const getCssConfigForFiles = files => {
|
||||
return {
|
||||
stylelint: {
|
||||
css: {
|
||||
// Use a fully-qualified path.
|
||||
src: files,
|
||||
options: {
|
||||
configOverrides: {
|
||||
rules: {
|
||||
// These rules have to be disabled in .stylelintrc for scss compat.
|
||||
"at-rule-no-unknown": true,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const getScssConfigForFiles = files => {
|
||||
return {
|
||||
stylelint: {
|
||||
scss: {
|
||||
options: {syntax: 'scss'},
|
||||
src: files,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Register any stylelint tasks.
|
||||
*
|
||||
* @param {Object} grunt
|
||||
* @param {Array} files
|
||||
* @param {String} fullRunDir
|
||||
*/
|
||||
const registerStyleLintTasks = () => {
|
||||
const glob = require('glob');
|
||||
|
||||
// The stylelinters do not handle the case where a configuration was provided but no files were included.
|
||||
// Keep track of whether any files were found.
|
||||
let hasCss = false;
|
||||
let hasScss = false;
|
||||
|
||||
// The stylelint processors do not take a path argument. They always check all provided values.
|
||||
// As a result we must check through each glob and determine if any files match the current directory.
|
||||
const scssFiles = [];
|
||||
const cssFiles = [];
|
||||
|
||||
const requestedFiles = grunt.moodleEnv.files;
|
||||
if (requestedFiles) {
|
||||
// Grunt was called with a files argument.
|
||||
// Check whether each of the requested files matches either the CSS or SCSS source file list.
|
||||
|
||||
requestedFiles.forEach(changedFilePath => {
|
||||
let matchesGlob;
|
||||
|
||||
// Check whether this watched path matches any watched SCSS file.
|
||||
matchesGlob = grunt.moodleEnv.scssSrc.some(watchedPathGlob => {
|
||||
return glob.sync(watchedPathGlob).indexOf(changedFilePath) !== -1;
|
||||
});
|
||||
if (matchesGlob) {
|
||||
scssFiles.push(changedFilePath);
|
||||
hasScss = true;
|
||||
}
|
||||
|
||||
// Check whether this watched path matches any watched CSS file.
|
||||
matchesGlob = grunt.moodleEnv.cssSrc.some(watchedPathGlob => {
|
||||
return glob.sync(watchedPathGlob).indexOf(changedFilePath) !== -1;
|
||||
});
|
||||
if (matchesGlob) {
|
||||
cssFiles.push(changedFilePath);
|
||||
hasCss = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Grunt was called without a list of files.
|
||||
// The start directory (runDir) may be a child dir of the project.
|
||||
// Check each scssSrc file to see if it's in the start directory.
|
||||
// This means that we can lint just mod/*/styles.css if started in the mod directory.
|
||||
|
||||
grunt.moodleEnv.scssSrc.forEach(path => {
|
||||
if (path.startsWith(grunt.moodleEnv.runDir)) {
|
||||
scssFiles.push(path);
|
||||
hasScss = true;
|
||||
}
|
||||
});
|
||||
|
||||
grunt.moodleEnv.cssSrc.forEach(path => {
|
||||
if (path.startsWith(grunt.moodleEnv.runDir)) {
|
||||
cssFiles.push(path);
|
||||
hasCss = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Register the tasks.
|
||||
const scssTasks = ['sass'];
|
||||
if (hasScss) {
|
||||
grunt.config.merge(getScssConfigForFiles(scssFiles));
|
||||
scssTasks.unshift('stylelint:scss');
|
||||
}
|
||||
scssTasks.unshift('ignorefiles');
|
||||
|
||||
const cssTasks = ['ignorefiles'];
|
||||
if (hasCss) {
|
||||
grunt.config.merge(getCssConfigForFiles(cssFiles));
|
||||
cssTasks.push('stylelint:css');
|
||||
}
|
||||
|
||||
// The tasks must be registered, even if empty to ensure a consistent command list.
|
||||
// They jsut won't run anything.
|
||||
grunt.registerTask('scss', scssTasks);
|
||||
grunt.registerTask('rawcss', cssTasks);
|
||||
};
|
||||
|
||||
// Register CSS tasks.
|
||||
grunt.loadNpmTasks('grunt-stylelint');
|
||||
|
||||
// Register the style lint tasks.
|
||||
registerStyleLintTasks();
|
||||
grunt.registerTask('css', ['scss', 'rawcss']);
|
||||
|
||||
const getCoreThemeMatches = () => {
|
||||
const scssMatch = 'scss/**/*.scss';
|
||||
|
||||
if (grunt.moodleEnv.inTheme) {
|
||||
return [scssMatch];
|
||||
}
|
||||
|
||||
if (grunt.moodleEnv.runDir.startsWith('theme')) {
|
||||
return [`*/${scssMatch}`];
|
||||
}
|
||||
|
||||
return [`theme/*/${scssMatch}`];
|
||||
};
|
||||
|
||||
// Add the watch configuration for rawcss, and scss.
|
||||
grunt.config.merge({
|
||||
watch: {
|
||||
rawcss: {
|
||||
files: [
|
||||
'**/*.css',
|
||||
],
|
||||
excludes: [
|
||||
'**/moodle.css',
|
||||
'**/editor.css',
|
||||
'jsdoc/styles/*.css',
|
||||
],
|
||||
tasks: ['rawcss']
|
||||
},
|
||||
scss: {
|
||||
files: getCoreThemeMatches(),
|
||||
tasks: ['scss']
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,272 +0,0 @@
|
||||
/**
|
||||
* This is a wrapper task to handle the grunt watch command. It attempts to use
|
||||
* Watchman to monitor for file changes, if it's installed, because it's much faster.
|
||||
*
|
||||
* If Watchman isn't installed then it falls back to the grunt-contrib-watch file
|
||||
* watcher for backwards compatibility.
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
module.exports = grunt => {
|
||||
/**
|
||||
* This is a wrapper task to handle the grunt watch command. It attempts to use
|
||||
* Watchman to monitor for file changes, if it's installed, because it's much faster.
|
||||
*
|
||||
* If Watchman isn't installed then it falls back to the grunt-contrib-watch file
|
||||
* watcher for backwards compatibility.
|
||||
*/
|
||||
const watchHandler = function() {
|
||||
const async = require('async');
|
||||
const watchTaskDone = this.async();
|
||||
let watchInitialised = false;
|
||||
let watchTaskQueue = {};
|
||||
let processingQueue = false;
|
||||
|
||||
const watchman = require('fb-watchman');
|
||||
const watchmanClient = new watchman.Client();
|
||||
|
||||
// Grab the tasks and files that have been queued up and execute them.
|
||||
var processWatchTaskQueue = function() {
|
||||
if (!Object.keys(watchTaskQueue).length || processingQueue) {
|
||||
// If there is nothing in the queue or we're already processing then wait.
|
||||
return;
|
||||
}
|
||||
|
||||
processingQueue = true;
|
||||
|
||||
// Grab all tasks currently in the queue.
|
||||
var queueToProcess = watchTaskQueue;
|
||||
// Reset the queue.
|
||||
watchTaskQueue = {};
|
||||
|
||||
async.forEachSeries(
|
||||
Object.keys(queueToProcess),
|
||||
function(task, next) {
|
||||
var files = queueToProcess[task];
|
||||
var filesOption = '--files=' + files.join(',');
|
||||
grunt.log.ok('Running task ' + task + ' for files ' + filesOption);
|
||||
|
||||
// Spawn the task in a child process so that it doesn't kill this one
|
||||
// if it failed.
|
||||
grunt.util.spawn(
|
||||
{
|
||||
// Spawn with the grunt bin.
|
||||
grunt: true,
|
||||
// Run from current working dir and inherit stdio from process.
|
||||
opts: {
|
||||
cwd: grunt.moodleEnv.fullRunDir,
|
||||
stdio: 'inherit'
|
||||
},
|
||||
args: [task, filesOption]
|
||||
},
|
||||
function(err, res, code) {
|
||||
if (code !== 0) {
|
||||
// The grunt task failed.
|
||||
grunt.log.error(err);
|
||||
}
|
||||
|
||||
// Move on to the next task.
|
||||
next();
|
||||
}
|
||||
);
|
||||
},
|
||||
function() {
|
||||
// No longer processing.
|
||||
processingQueue = false;
|
||||
// Once all of the tasks are done then recurse just in case more tasks
|
||||
// were queued while we were processing.
|
||||
processWatchTaskQueue();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const originalWatchConfig = grunt.config.get(['watch']);
|
||||
const watchConfig = Object.keys(originalWatchConfig).reduce(function(carry, key) {
|
||||
if (key == 'options') {
|
||||
return carry;
|
||||
}
|
||||
|
||||
const value = originalWatchConfig[key];
|
||||
|
||||
const taskNames = value.tasks;
|
||||
const files = value.files;
|
||||
let excludes = [];
|
||||
if (value.excludes) {
|
||||
excludes = value.excludes;
|
||||
}
|
||||
|
||||
taskNames.forEach(function(taskName) {
|
||||
carry[taskName] = {
|
||||
files,
|
||||
excludes,
|
||||
};
|
||||
});
|
||||
|
||||
return carry;
|
||||
}, {});
|
||||
|
||||
watchmanClient.on('error', function(error) {
|
||||
// We have to add an error handler here and parse the error string because the
|
||||
// example way from the docs to check if Watchman is installed doesn't actually work!!
|
||||
// See: https://github.com/facebook/watchman/issues/509
|
||||
if (error.message.match('Watchman was not found')) {
|
||||
// If watchman isn't installed then we should fallback to the other watch task.
|
||||
grunt.log.ok('It is recommended that you install Watchman for better performance using the "watch" command.');
|
||||
|
||||
// Fallback to the old grunt-contrib-watch task.
|
||||
grunt.renameTask('watch-grunt', 'watch');
|
||||
grunt.task.run(['watch']);
|
||||
// This task is finished.
|
||||
watchTaskDone(0);
|
||||
} else {
|
||||
grunt.log.error(error);
|
||||
// Fatal error.
|
||||
watchTaskDone(1);
|
||||
}
|
||||
});
|
||||
|
||||
watchmanClient.on('subscription', function(resp) {
|
||||
if (resp.subscription !== 'grunt-watch') {
|
||||
return;
|
||||
}
|
||||
|
||||
resp.files.forEach(function(file) {
|
||||
grunt.log.ok('File changed: ' + file.name);
|
||||
|
||||
var fullPath = grunt.moodleEnv.fullRunDir + '/' + file.name;
|
||||
Object.keys(watchConfig).forEach(function(task) {
|
||||
|
||||
const fileGlobs = watchConfig[task].files;
|
||||
var match = fileGlobs.some(function(fileGlob) {
|
||||
return grunt.file.isMatch(`**/${fileGlob}`, fullPath);
|
||||
});
|
||||
|
||||
if (match) {
|
||||
// If we are watching a subdirectory then the file.name will be relative
|
||||
// to that directory. However the grunt tasks expect the file paths to be
|
||||
// relative to the Gruntfile.js location so let's normalise them before
|
||||
// adding them to the queue.
|
||||
var relativePath = fullPath.replace(grunt.moodleEnv.gruntFilePath + '/', '');
|
||||
if (task in watchTaskQueue) {
|
||||
if (!watchTaskQueue[task].includes(relativePath)) {
|
||||
watchTaskQueue[task] = watchTaskQueue[task].concat(relativePath);
|
||||
}
|
||||
} else {
|
||||
watchTaskQueue[task] = [relativePath];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
processWatchTaskQueue();
|
||||
});
|
||||
|
||||
process.on('SIGINT', function() {
|
||||
// Let the user know that they may need to manually stop the Watchman daemon if they
|
||||
// no longer want it running.
|
||||
if (watchInitialised) {
|
||||
grunt.log.ok('The Watchman daemon may still be running and may need to be stopped manually.');
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
|
||||
// Initiate the watch on the current directory.
|
||||
watchmanClient.command(['watch-project', grunt.moodleEnv.fullRunDir], function(watchError, watchResponse) {
|
||||
if (watchError) {
|
||||
grunt.log.error('Error initiating watch:', watchError);
|
||||
watchTaskDone(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('warning' in watchResponse) {
|
||||
grunt.log.error('warning: ', watchResponse.warning);
|
||||
}
|
||||
|
||||
var watch = watchResponse.watch;
|
||||
var relativePath = watchResponse.relative_path;
|
||||
watchInitialised = true;
|
||||
|
||||
watchmanClient.command(['clock', watch], function(clockError, clockResponse) {
|
||||
if (clockError) {
|
||||
grunt.log.error('Failed to query clock:', clockError);
|
||||
watchTaskDone(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the expression query used by watchman.
|
||||
// Documentation is limited, but see https://facebook.github.io/watchman/docs/expr/allof.html for examples.
|
||||
// We generate an expression to match any value in the files list of all of our tasks, but excluding
|
||||
// all value in the excludes list of that task.
|
||||
//
|
||||
// [anyof, [
|
||||
// [allof, [
|
||||
// [anyof, [
|
||||
// ['match', validPath, 'wholename'],
|
||||
// ['match', validPath, 'wholename'],
|
||||
// ],
|
||||
// [not,
|
||||
// [anyof, [
|
||||
// ['match', invalidPath, 'wholename'],
|
||||
// ['match', invalidPath, 'wholename'],
|
||||
// ],
|
||||
// ],
|
||||
// ],
|
||||
var matchWholeName = fileGlob => ['match', fileGlob, 'wholename'];
|
||||
var matches = Object.keys(watchConfig).map(function(task) {
|
||||
const matchAll = [];
|
||||
matchAll.push(['anyof'].concat(watchConfig[task].files.map(matchWholeName)));
|
||||
|
||||
if (watchConfig[task].excludes.length) {
|
||||
matchAll.push(['not', ['anyof'].concat(watchConfig[task].excludes.map(matchWholeName))]);
|
||||
}
|
||||
|
||||
return ['allof'].concat(matchAll);
|
||||
});
|
||||
|
||||
matches = ['anyof'].concat(matches);
|
||||
|
||||
var sub = {
|
||||
expression: matches,
|
||||
// Which fields we're interested in.
|
||||
fields: ["name", "size", "type"],
|
||||
// Add our time constraint.
|
||||
since: clockResponse.clock
|
||||
};
|
||||
|
||||
if (relativePath) {
|
||||
/* eslint-disable camelcase */
|
||||
sub.relative_root = relativePath;
|
||||
}
|
||||
|
||||
watchmanClient.command(['subscribe', watch, 'grunt-watch', sub], function(subscribeError) {
|
||||
if (subscribeError) {
|
||||
// Probably an error in the subscription criteria.
|
||||
grunt.log.error('failed to subscribe: ', subscribeError);
|
||||
watchTaskDone(1);
|
||||
return;
|
||||
}
|
||||
|
||||
grunt.log.ok('Listening for changes to files in ' + grunt.moodleEnv.fullRunDir);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Rename the grunt-contrib-watch "watch" task because we're going to wrap it.
|
||||
grunt.renameTask('watch', 'watch-grunt');
|
||||
|
||||
// Register the new watch handler.
|
||||
grunt.registerTask('watch', 'Run tasks on file changes', watchHandler);
|
||||
|
||||
grunt.config.merge({
|
||||
watch: {
|
||||
options: {
|
||||
nospawn: true // We need not to spawn so config can be changed dynamically.
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return watchHandler;
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="MoodleCore">
|
||||
<rule ref="moodle"/>
|
||||
</ruleset>
|
||||
@@ -0,0 +1,96 @@
|
||||
# Generated by "grunt ignorefiles"
|
||||
**/yui/build/*
|
||||
theme/boost/style/moodle.css
|
||||
theme/classic/style/moodle.css
|
||||
node_modules/
|
||||
vendor/
|
||||
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
|
||||
auth/cas/CAS/
|
||||
cache/stores/mongodb/MongoDB/
|
||||
enrol/lti/ims-blti/
|
||||
filter/algebra/AlgParser.pm
|
||||
filter/tex/mimetex.*
|
||||
h5p/h5plib/v124/joubel/core/
|
||||
h5p/h5plib/v124/joubel/editor/
|
||||
lib/editor/atto/plugins/html/yui/src/codemirror/
|
||||
lib/editor/atto/plugins/html/yui/src/beautify/
|
||||
lib/editor/atto/yui/src/rangy/js/*.*
|
||||
lib/editor/tinymce/plugins/pdw/tinymce/
|
||||
lib/editor/tinymce/plugins/spellchecker/rpc.php
|
||||
lib/editor/tinymce/tiny_mce/
|
||||
lib/mlbackend/php/phpml/
|
||||
lib/adodb/
|
||||
lib/behat/axe/
|
||||
lib/bennu/
|
||||
lib/evalmath/
|
||||
lib/phpspreadsheet/
|
||||
lib/google/
|
||||
lib/htmlpurifier/
|
||||
lib/jabber/
|
||||
lib/minify/matthiasmullie-minify/
|
||||
lib/minify/matthiasmullie-pathconverter/
|
||||
lib/pear/HTML/Common.php
|
||||
lib/pear/HTML/QuickForm.php
|
||||
lib/pear/HTML/QuickForm/
|
||||
lib/pear/PEAR.php
|
||||
lib/phpmailer/
|
||||
lib/simplepie/
|
||||
lib/tcpdf/
|
||||
lib/typo3/
|
||||
lib/yuilib/
|
||||
lib/yuilib/gallery/
|
||||
lib/jquery/
|
||||
lib/html2text/
|
||||
lib/markdown/
|
||||
lib/xhprof/
|
||||
lib/horde/
|
||||
lib/requirejs/
|
||||
lib/amd/src/loglevel.js
|
||||
lib/mustache/
|
||||
lib/amd/src/mustache.js
|
||||
lib/graphlib.php
|
||||
lib/php-css-parser/
|
||||
lib/rtlcss/
|
||||
lib/scssphp/
|
||||
lib/spout/
|
||||
lib/amd/src/chartjs-lazy.js
|
||||
lib/maxmind/GeoIp2/
|
||||
lib/maxmind/MaxMind/
|
||||
lib/ltiprovider/
|
||||
lib/amd/src/truncate.js
|
||||
lib/fonts/
|
||||
lib/amd/src/adapter.js
|
||||
lib/validateurlsyntax.php
|
||||
lib/amd/src/popper.js
|
||||
lib/geopattern-php/
|
||||
lib/php-jwt/
|
||||
lib/babel-polyfill/
|
||||
lib/polyfills/
|
||||
lib/emoji-data/
|
||||
lib/plist/
|
||||
lib/zipstream/
|
||||
lib/php-enum/
|
||||
lib/http-message/
|
||||
media/player/videojs/amd/src/video-lazy.js
|
||||
media/player/videojs/amd/src/Youtube-lazy.js
|
||||
media/player/videojs/videojs/
|
||||
media/player/videojs/amd/src/videojs-flash-lazy.js
|
||||
media/player/videojs/videojs/video-js.swf
|
||||
mod/assign/feedback/editpdf/fpdi/
|
||||
repository/s3/S3.php
|
||||
theme/boost/scss/bootstrap/
|
||||
theme/boost/amd/src/bootstrap/alert.js
|
||||
theme/boost/amd/src/bootstrap/button.js
|
||||
theme/boost/amd/src/bootstrap/carousel.js
|
||||
theme/boost/amd/src/bootstrap/collapse.js
|
||||
theme/boost/amd/src/bootstrap/dropdown.js
|
||||
theme/boost/amd/src/bootstrap/index.js
|
||||
theme/boost/amd/src/bootstrap/modal.js
|
||||
theme/boost/amd/src/bootstrap/popover.js
|
||||
theme/boost/amd/src/bootstrap/tools/sanitizer.js
|
||||
theme/boost/amd/src/bootstrap/scrollspy.js
|
||||
theme/boost/amd/src/bootstrap/tab.js
|
||||
theme/boost/amd/src/bootstrap/toast.js
|
||||
theme/boost/amd/src/bootstrap/tooltip.js
|
||||
theme/boost/amd/src/bootstrap/util.js
|
||||
theme/boost/scss/fontawesome/
|
||||
+17
-24
@@ -10,7 +10,7 @@ language: php
|
||||
|
||||
os: linux
|
||||
|
||||
dist: bionic
|
||||
dist: xenial
|
||||
|
||||
services:
|
||||
- mysql
|
||||
@@ -19,12 +19,6 @@ services:
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
|
||||
branches:
|
||||
except:
|
||||
- master
|
||||
- /MOODLE_[0-9]+_STABLE/
|
||||
- /^v[0-9]+\.[0-9]+\.[0-9]+.*/
|
||||
|
||||
jobs:
|
||||
# Enable fast finish.
|
||||
# This will fail the build if a single job fails (except those in allow_failures).
|
||||
@@ -32,34 +26,34 @@ jobs:
|
||||
fast_finish: true
|
||||
|
||||
include:
|
||||
# First all the lowest php ones (7.3)
|
||||
- php: 7.3
|
||||
# First all the lowest php ones (7.2)
|
||||
- php: 7.2
|
||||
env: DB=none TASK=CITEST
|
||||
- php: 7.3
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/gallium'
|
||||
- php: 7.2
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
|
||||
|
||||
- if: env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present
|
||||
php: 7.3
|
||||
php: 7.2
|
||||
env: DB=pgsql TASK=PHPUNIT
|
||||
|
||||
- if: env(MOODLE_DATABASE) = "mysqli" OR env(MOODLE_DATABASE) = "all"
|
||||
php: 7.3
|
||||
php: 7.2
|
||||
env: DB=mysqli TASK=PHPUNIT
|
||||
|
||||
# Then, conditionally, all the highest php ones (8.0)
|
||||
# Then, conditionally, all the highest php ones (7.4)
|
||||
- if: env(MOODLE_PHP) = "all"
|
||||
php: 8.0
|
||||
php: 7.4
|
||||
env: DB=none TASK=CITEST
|
||||
- if: env(MOODLE_PHP) = "all"
|
||||
php: 8.0
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/gallium'
|
||||
php: 7.4
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
|
||||
|
||||
- if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present)
|
||||
php: 8.0
|
||||
php: 7.4
|
||||
env: DB=pgsql TASK=PHPUNIT
|
||||
|
||||
- if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "mysqli" OR env(MOODLE_DATABASE) = "all")
|
||||
php: 8.0
|
||||
php: 7.4
|
||||
env: DB=mysqli TASK=PHPUNIT
|
||||
|
||||
cache:
|
||||
@@ -118,8 +112,6 @@ install:
|
||||
fi
|
||||
|
||||
before_script:
|
||||
- if [[ ${TRAVIS_PHP_VERSION:0:1} -gt 7 ]]; then pecl install xmlrpc-beta; fi
|
||||
- echo 'max_input_vars=5000' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
|
||||
- phpenv config-rm xdebug.ini
|
||||
- >
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
@@ -189,9 +181,10 @@ before_script:
|
||||
sed -i \
|
||||
-e "/require_once/i \\define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');" \
|
||||
config.php ;
|
||||
# Redis session tests
|
||||
# Redis session tests, but not for PHP 7.2 and up. See MDL-60978 for more info.
|
||||
redissession="define('TEST_SESSION_REDIS_HOST', '127.0.0.1');"
|
||||
sed -i \
|
||||
-e "/require_once/i \\define('TEST_SESSION_REDIS_HOST', '127.0.0.1');" \
|
||||
-e "/require_once/i \\${redissession}" \
|
||||
config.php ;
|
||||
|
||||
# Initialise PHPUnit for Moodle.
|
||||
@@ -221,7 +214,7 @@ before_script:
|
||||
# We need the official upstream for comparison
|
||||
git remote add upstream https://github.com/moodle/moodle.git;
|
||||
|
||||
git fetch upstream MOODLE_311_STABLE;
|
||||
git fetch upstream MOODLE_310_STABLE;
|
||||
export GIT_PREVIOUS_COMMIT="`git merge-base FETCH_HEAD $TRAVIS_COMMIT`";
|
||||
export GIT_COMMIT="$TRAVIS_COMMIT";
|
||||
export UPSTREAM_FETCH_HEAD=`git rev-parse FETCH_HEAD`
|
||||
|
||||
+762
-165
@@ -16,129 +16,147 @@
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* Grunt configuration for Moodle.
|
||||
*
|
||||
* @copyright 2014 Andrew Nicols
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
/**
|
||||
* Setup the Grunt Moodle environment.
|
||||
* Calculate the cwd, taking into consideration the `root` option (for Windows).
|
||||
*
|
||||
* @param {Grunt} grunt
|
||||
* @returns {Object}
|
||||
* @param {Object} grunt
|
||||
* @returns {String} The current directory as best we can determine
|
||||
*/
|
||||
const setupMoodleEnvironment = grunt => {
|
||||
const getCwd = grunt => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
|
||||
|
||||
const getAmdConfiguration = () => {
|
||||
// If the cwd is the amd directory in the current component then it will be empty.
|
||||
// If the cwd is a child of the component's AMD directory, the relative directory will not start with ..
|
||||
let inAMD = !path.relative(`${componentDirectory}/amd`, cwd).startsWith('..');
|
||||
let cwd = fs.realpathSync(process.env.PWD || process.cwd());
|
||||
|
||||
// Globbing pattern for matching all AMD JS source files.
|
||||
let amdSrc = [];
|
||||
if (inComponent) {
|
||||
amdSrc.push(
|
||||
componentDirectory + "/amd/src/*.js",
|
||||
componentDirectory + "/amd/src/**/*.js"
|
||||
);
|
||||
// Windows users can't run grunt in a subdirectory, so allow them to set
|
||||
// the root by passing --root=path/to/dir.
|
||||
if (grunt.option('root')) {
|
||||
const root = grunt.option('root');
|
||||
if (grunt.file.exists(__dirname, root)) {
|
||||
cwd = fs.realpathSync(path.join(__dirname, root));
|
||||
grunt.log.ok('Setting root to ' + cwd);
|
||||
} else {
|
||||
amdSrc = ComponentList.getAmdSrcGlobList();
|
||||
grunt.fail.fatal('Setting root to ' + root + ' failed - path does not exist');
|
||||
}
|
||||
}
|
||||
|
||||
return cwd;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register any stylelint tasks.
|
||||
*
|
||||
* @param {Object} grunt
|
||||
* @param {Array} files
|
||||
* @param {String} fullRunDir
|
||||
*/
|
||||
const registerStyleLintTasks = (grunt, files, fullRunDir) => {
|
||||
const getCssConfigForFiles = files => {
|
||||
return {
|
||||
inAMD,
|
||||
amdSrc,
|
||||
stylelint: {
|
||||
css: {
|
||||
// Use a fully-qualified path.
|
||||
src: files,
|
||||
options: {
|
||||
configOverrides: {
|
||||
rules: {
|
||||
// These rules have to be disabled in .stylelintrc for scss compat.
|
||||
"at-rule-no-unknown": true,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const getYuiConfiguration = () => {
|
||||
let yuiSrc = [];
|
||||
if (inComponent) {
|
||||
yuiSrc.push(componentDirectory + "/yui/src/**/*.js");
|
||||
const getScssConfigForFiles = files => {
|
||||
return {
|
||||
stylelint: {
|
||||
scss: {
|
||||
options: {syntax: 'scss'},
|
||||
src: files,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
let hasCss = true;
|
||||
let hasScss = true;
|
||||
|
||||
if (files) {
|
||||
// Specific files were passed. Just set them up.
|
||||
grunt.config.merge(getCssConfigForFiles(files));
|
||||
grunt.config.merge(getScssConfigForFiles(files));
|
||||
} else {
|
||||
// The stylelint system does not handle the case where there was no file to lint.
|
||||
// Check whether there are any files to lint in the current directory.
|
||||
const glob = require('glob');
|
||||
|
||||
const scssSrc = [];
|
||||
glob.sync(`${fullRunDir}/**/*.scss`).forEach(path => scssSrc.push(path));
|
||||
|
||||
if (scssSrc.length) {
|
||||
grunt.config.merge(getScssConfigForFiles(scssSrc));
|
||||
} else {
|
||||
yuiSrc = ComponentList.getYuiSrcGlobList(gruntFilePath + '/');
|
||||
hasScss = false;
|
||||
}
|
||||
|
||||
return {
|
||||
yuiSrc,
|
||||
};
|
||||
};
|
||||
const cssSrc = [];
|
||||
glob.sync(`${fullRunDir}/**/*.css`).forEach(path => cssSrc.push(path));
|
||||
|
||||
const getStyleConfiguration = () => {
|
||||
const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
|
||||
// Build the cssSrc and scssSrc.
|
||||
// Valid paths are:
|
||||
// [component]/styles.css; and either
|
||||
// [theme/[themename]]/scss/**/*.scss; or
|
||||
// [theme/[themename]]/style/*.css.
|
||||
//
|
||||
// If a theme has scss, then it is assumed that the style directory contains generated content.
|
||||
let cssSrc = [];
|
||||
let scssSrc = [];
|
||||
|
||||
const checkComponentDirectory = componentDirectory => {
|
||||
const isTheme = componentDirectory.startsWith('theme/');
|
||||
if (isTheme) {
|
||||
const scssDirectory = `${componentDirectory}/scss`;
|
||||
|
||||
if (fs.existsSync(scssDirectory)) {
|
||||
// This theme has an SCSS directory.
|
||||
// Include all scss files within it recursively, but do not check for css files.
|
||||
scssSrc.push(`${scssDirectory}/*.scss`);
|
||||
scssSrc.push(`${scssDirectory}/**/*.scss`);
|
||||
} else {
|
||||
// This theme has no SCSS directory.
|
||||
// Only hte CSS files in the top-level directory are checked.
|
||||
cssSrc.push(`${componentDirectory}/style/*.css`);
|
||||
}
|
||||
} else {
|
||||
// This is not a theme.
|
||||
// All other plugin types are restricted to a single styles.css in their top level.
|
||||
cssSrc.push(`${componentDirectory}/styles.css`);
|
||||
}
|
||||
};
|
||||
|
||||
if (inComponent) {
|
||||
checkComponentDirectory(componentDirectory);
|
||||
if (cssSrc.length) {
|
||||
grunt.config.merge(getCssConfigForFiles(cssSrc));
|
||||
} else {
|
||||
ComponentList.getComponentPaths(`${gruntFilePath}/`).forEach(componentPath => {
|
||||
checkComponentDirectory(componentPath);
|
||||
});
|
||||
hasCss = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cssSrc,
|
||||
scssSrc,
|
||||
};
|
||||
};
|
||||
const scssTasks = ['sass'];
|
||||
if (hasScss) {
|
||||
scssTasks.unshift('stylelint:scss');
|
||||
}
|
||||
grunt.registerTask('scss', scssTasks);
|
||||
|
||||
/**
|
||||
* Calculate the cwd, taking into consideration the `root` option (for Windows).
|
||||
*
|
||||
* @param {Object} grunt
|
||||
* @returns {String} The current directory as best we can determine
|
||||
*/
|
||||
const getCwd = grunt => {
|
||||
let cwd = fs.realpathSync(process.env.PWD || process.cwd());
|
||||
const cssTasks = [];
|
||||
if (hasCss) {
|
||||
cssTasks.push('stylelint:css');
|
||||
}
|
||||
grunt.registerTask('rawcss', cssTasks);
|
||||
|
||||
// Windows users can't run grunt in a subdirectory, so allow them to set
|
||||
// the root by passing --root=path/to/dir.
|
||||
if (grunt.option('root')) {
|
||||
const root = grunt.option('root');
|
||||
if (grunt.file.exists(__dirname, root)) {
|
||||
cwd = fs.realpathSync(path.join(__dirname, root));
|
||||
grunt.log.ok('Setting root to ' + cwd);
|
||||
} else {
|
||||
grunt.fail.fatal('Setting root to ' + root + ' failed - path does not exist');
|
||||
}
|
||||
}
|
||||
grunt.registerTask('css', ['scss', 'rawcss']);
|
||||
};
|
||||
|
||||
return cwd;
|
||||
};
|
||||
/**
|
||||
* Grunt configuration.
|
||||
*
|
||||
* @param {Object} grunt
|
||||
*/
|
||||
module.exports = function(grunt) {
|
||||
const path = require('path');
|
||||
const tasks = {};
|
||||
const async = require('async');
|
||||
const DOMParser = require('xmldom').DOMParser;
|
||||
const xpath = require('xpath');
|
||||
const semver = require('semver');
|
||||
const watchman = require('fb-watchman');
|
||||
const watchmanClient = new watchman.Client();
|
||||
const fs = require('fs');
|
||||
const ComponentList = require(path.resolve('GruntfileComponents.js'));
|
||||
const sass = require('node-sass');
|
||||
|
||||
// Verify the node version is new enough.
|
||||
var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
|
||||
var actual = semver.valid(process.version);
|
||||
if (!semver.satisfies(actual, expected)) {
|
||||
grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
|
||||
}
|
||||
|
||||
// Detect directories:
|
||||
// * gruntFilePath The real path on disk to this Gruntfile.js
|
||||
@@ -153,19 +171,8 @@ const setupMoodleEnvironment = grunt => {
|
||||
const relativeCwd = path.relative(gruntFilePath, cwd);
|
||||
const componentDirectory = ComponentList.getOwningComponentDirectory(relativeCwd);
|
||||
const inComponent = !!componentDirectory;
|
||||
const inTheme = !!componentDirectory && componentDirectory.startsWith('theme/');
|
||||
const runDir = inComponent ? componentDirectory : relativeCwd;
|
||||
const fullRunDir = fs.realpathSync(gruntFilePath + path.sep + runDir);
|
||||
const {inAMD, amdSrc} = getAmdConfiguration();
|
||||
const {yuiSrc} = getYuiConfiguration();
|
||||
const {cssSrc, scssSrc} = getStyleConfiguration();
|
||||
|
||||
let files = null;
|
||||
if (grunt.option('files')) {
|
||||
// Accept a comma separated list of files to process.
|
||||
files = grunt.option('files').split(',');
|
||||
}
|
||||
|
||||
grunt.log.debug('============================================================================');
|
||||
grunt.log.debug(`= Node version: ${process.versions.node}`);
|
||||
grunt.log.debug(`= grunt version: ${grunt.package.version}`);
|
||||
@@ -185,77 +192,667 @@ const setupMoodleEnvironment = grunt => {
|
||||
grunt.log.ok(`Running tasks for component directory ${componentDirectory}`);
|
||||
}
|
||||
|
||||
return {
|
||||
amdSrc,
|
||||
componentDirectory,
|
||||
cwd,
|
||||
cssSrc,
|
||||
files,
|
||||
fullRunDir,
|
||||
gruntFilePath,
|
||||
inAMD,
|
||||
inComponent,
|
||||
inTheme,
|
||||
relativeCwd,
|
||||
runDir,
|
||||
scssSrc,
|
||||
yuiSrc,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify tha tthe current NodeJS version matches the required version in package.json.
|
||||
*
|
||||
* @param {Grunt} grunt
|
||||
*/
|
||||
const verifyNodeVersion = grunt => {
|
||||
const semver = require('semver');
|
||||
|
||||
// Verify the node version is new enough.
|
||||
var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
|
||||
var actual = semver.valid(process.version);
|
||||
if (!semver.satisfies(actual, expected)) {
|
||||
grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
|
||||
let files = null;
|
||||
if (grunt.option('files')) {
|
||||
// Accept a comma separated list of files to process.
|
||||
files = grunt.option('files').split(',');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Grunt configuration.
|
||||
*
|
||||
* @param {Grunt} grunt
|
||||
*/
|
||||
module.exports = function(grunt) {
|
||||
// Verify that the Node version meets our requirements.
|
||||
verifyNodeVersion(grunt);
|
||||
// If the cwd is the amd directory in the current component then it will be empty.
|
||||
// If the cwd is a child of the component's AMD directory, the relative directory will not start with ..
|
||||
const inAMD = !path.relative(`${componentDirectory}/amd`, cwd).startsWith('..');
|
||||
|
||||
// Setup the Moodle environemnt within the Grunt object.
|
||||
grunt.moodleEnv = setupMoodleEnvironment(grunt);
|
||||
// Globbing pattern for matching all AMD JS source files.
|
||||
let amdSrc = [];
|
||||
if (inComponent) {
|
||||
amdSrc.push(componentDirectory + "/amd/src/*.js");
|
||||
amdSrc.push(componentDirectory + "/amd/src/**/*.js");
|
||||
} else {
|
||||
amdSrc = ComponentList.getAmdSrcGlobList();
|
||||
}
|
||||
|
||||
let yuiSrc = [];
|
||||
if (inComponent) {
|
||||
yuiSrc.push(componentDirectory + "/yui/src/**/*.js");
|
||||
} else {
|
||||
yuiSrc = ComponentList.getYuiSrcGlobList(gruntFilePath + '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the named task.
|
||||
* Function to generate the destination for the uglify task
|
||||
* (e.g. build/file.min.js). This function will be passed to
|
||||
* the rename property of files array when building dynamically:
|
||||
* http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {Grunt} grunt
|
||||
* @param {String} destPath the current destination
|
||||
* @param {String} srcPath the matched src path
|
||||
* @return {String} The rewritten destination path.
|
||||
*/
|
||||
const addTask = (name, grunt) => {
|
||||
const path = require('path');
|
||||
const taskPath = path.resolve(`./.grunt/tasks/${name}.js`);
|
||||
|
||||
grunt.log.debug(`Including tasks for ${name} from ${taskPath}`);
|
||||
|
||||
require(path.resolve(`./.grunt/tasks/${name}.js`))(grunt);
|
||||
var babelRename = function(destPath, srcPath) {
|
||||
destPath = srcPath.replace('src', 'build');
|
||||
destPath = destPath.replace('.js', '.min.js');
|
||||
return destPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find thirdpartylibs.xml and generate an array of paths contained within
|
||||
* them (used to generate ignore files and so on).
|
||||
*
|
||||
* @return {array} The list of thirdparty paths.
|
||||
*/
|
||||
var getThirdPartyPathsFromXML = function() {
|
||||
const thirdpartyfiles = ComponentList.getThirdPartyLibsList(gruntFilePath + '/');
|
||||
const libs = ['node_modules/', 'vendor/'];
|
||||
|
||||
// Add Moodle task configuration.
|
||||
addTask('gherkinlint', grunt);
|
||||
addTask('ignorefiles', grunt);
|
||||
thirdpartyfiles.forEach(function(file) {
|
||||
const dirname = path.dirname(file);
|
||||
|
||||
addTask('javascript', grunt);
|
||||
addTask('style', grunt);
|
||||
const doc = new DOMParser().parseFromString(grunt.file.read(file));
|
||||
const nodes = xpath.select("/libraries/library/location/text()", doc);
|
||||
|
||||
addTask('watch', grunt);
|
||||
addTask('startup', grunt);
|
||||
nodes.forEach(function(node) {
|
||||
let lib = path.posix.join(dirname, node.toString());
|
||||
if (grunt.file.isDir(lib)) {
|
||||
// Ensure trailing slash on dirs.
|
||||
lib = lib.replace(/\/?$/, '/');
|
||||
}
|
||||
|
||||
// Look for duplicate paths before adding to array.
|
||||
if (libs.indexOf(lib) === -1) {
|
||||
libs.push(lib);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return libs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of feature files to pass to the gherkin linter.
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
const getGherkinLintTargets = () => {
|
||||
if (files) {
|
||||
// Specific files were requested. Only check these.
|
||||
return files;
|
||||
}
|
||||
|
||||
if (inComponent) {
|
||||
return [`${runDir}/tests/behat/*.feature`];
|
||||
}
|
||||
|
||||
return ['**/tests/behat/*.feature'];
|
||||
};
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
eslint: {
|
||||
// Even though warnings dont stop the build we don't display warnings by default because
|
||||
// at this moment we've got too many core warnings.
|
||||
// To display warnings call: grunt eslint --show-lint-warnings
|
||||
// To fail on warnings call: grunt eslint --max-lint-warnings=0
|
||||
// Also --max-lint-warnings=-1 can be used to display warnings but not fail.
|
||||
options: {
|
||||
quiet: (!grunt.option('show-lint-warnings')) && (typeof grunt.option('max-lint-warnings') === 'undefined'),
|
||||
maxWarnings: ((typeof grunt.option('max-lint-warnings') !== 'undefined') ? grunt.option('max-lint-warnings') : -1)
|
||||
},
|
||||
amd: {src: files ? files : amdSrc},
|
||||
// Check YUI module source files.
|
||||
yui: {src: files ? files : yuiSrc},
|
||||
},
|
||||
babel: {
|
||||
options: {
|
||||
sourceMaps: true,
|
||||
comments: false,
|
||||
plugins: [
|
||||
'transform-es2015-modules-amd-lazy',
|
||||
'system-import-transformer',
|
||||
// This plugin modifies the Babel transpiling for "export default"
|
||||
// so that if it's used then only the exported value is returned
|
||||
// by the generated AMD module.
|
||||
//
|
||||
// It also adds the Moodle plugin name to the AMD module definition
|
||||
// so that it can be imported as expected in other modules.
|
||||
path.resolve('babel-plugin-add-module-to-define.js'),
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
['@babel/plugin-proposal-class-properties', {'loose': false}],
|
||||
'@babel/plugin-proposal-json-strings'
|
||||
],
|
||||
presets: [
|
||||
['minify', {
|
||||
// This minification plugin needs to be disabled because it breaks the
|
||||
// source map generation and causes invalid source maps to be output.
|
||||
simplify: false,
|
||||
builtIns: false
|
||||
}],
|
||||
['@babel/preset-env', {
|
||||
targets: {
|
||||
browsers: [
|
||||
">0.25%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10",
|
||||
"not op_mini all",
|
||||
"not Opera > 0",
|
||||
"not dead"
|
||||
]
|
||||
},
|
||||
modules: false,
|
||||
useBuiltIns: false
|
||||
}]
|
||||
]
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
src: files ? files : amdSrc,
|
||||
rename: babelRename
|
||||
}]
|
||||
}
|
||||
},
|
||||
sass: {
|
||||
dist: {
|
||||
files: {
|
||||
"theme/boost/style/moodle.css": "theme/boost/scss/preset/default.scss",
|
||||
"theme/classic/style/moodle.css": "theme/classic/scss/classicgrunt.scss"
|
||||
}
|
||||
},
|
||||
options: {
|
||||
implementation: sass,
|
||||
includePaths: ["theme/boost/scss/", "theme/classic/scss/"]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options: {
|
||||
nospawn: true // We need not to spawn so config can be changed dynamically.
|
||||
},
|
||||
amd: {
|
||||
files: inComponent
|
||||
? ['amd/src/*.js', 'amd/src/**/*.js']
|
||||
: ['**/amd/src/**/*.js'],
|
||||
tasks: ['amd']
|
||||
},
|
||||
boost: {
|
||||
files: [inComponent ? 'scss/**/*.scss' : 'theme/boost/scss/**/*.scss'],
|
||||
tasks: ['scss']
|
||||
},
|
||||
rawcss: {
|
||||
files: [
|
||||
'**/*.css',
|
||||
],
|
||||
excludes: [
|
||||
'**/moodle.css',
|
||||
'**/editor.css',
|
||||
],
|
||||
tasks: ['rawcss']
|
||||
},
|
||||
yui: {
|
||||
files: inComponent
|
||||
? ['yui/src/*.json', 'yui/src/**/*.js']
|
||||
: ['**/yui/src/**/*.js'],
|
||||
tasks: ['yui']
|
||||
},
|
||||
gherkinlint: {
|
||||
files: [inComponent ? 'tests/behat/*.feature' : '**/tests/behat/*.feature'],
|
||||
tasks: ['gherkinlint']
|
||||
}
|
||||
},
|
||||
shifter: {
|
||||
options: {
|
||||
recursive: true,
|
||||
// Shifter takes a relative path.
|
||||
paths: files ? files : [runDir]
|
||||
}
|
||||
},
|
||||
gherkinlint: {
|
||||
options: {
|
||||
files: getGherkinLintTargets(),
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate ignore files (utilising thirdpartylibs.xml data)
|
||||
*/
|
||||
tasks.ignorefiles = function() {
|
||||
// An array of paths to third party directories.
|
||||
const thirdPartyPaths = getThirdPartyPathsFromXML();
|
||||
// Generate .eslintignore.
|
||||
const eslintIgnores = [
|
||||
'# Generated by "grunt ignorefiles"',
|
||||
'*/**/yui/src/*/meta/',
|
||||
'*/**/build/',
|
||||
].concat(thirdPartyPaths);
|
||||
grunt.file.write('.eslintignore', eslintIgnores.join('\n'));
|
||||
|
||||
// Generate .stylelintignore.
|
||||
const stylelintIgnores = [
|
||||
'# Generated by "grunt ignorefiles"',
|
||||
'**/yui/build/*',
|
||||
'theme/boost/style/moodle.css',
|
||||
'theme/classic/style/moodle.css',
|
||||
].concat(thirdPartyPaths);
|
||||
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Shifter task. Is configured with a path to a specific file or a directory,
|
||||
* in the case of a specific file it will work out the right module to be built.
|
||||
*
|
||||
* Note that this task runs the invidiaul shifter jobs async (becase it spawns
|
||||
* so be careful to to call done().
|
||||
*/
|
||||
tasks.shifter = function() {
|
||||
var done = this.async(),
|
||||
options = grunt.config('shifter.options');
|
||||
|
||||
// Run the shifter processes one at a time to avoid confusing output.
|
||||
async.eachSeries(options.paths, function(src, filedone) {
|
||||
var args = [];
|
||||
args.push(path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
|
||||
|
||||
// Always ignore the node_modules directory.
|
||||
args.push('--excludes', 'node_modules');
|
||||
|
||||
// Determine the most appropriate options to run with based upon the current location.
|
||||
if (grunt.file.isMatch('**/yui/**/*.js', src)) {
|
||||
// When passed a JS file, build our containing module (this happen with
|
||||
// watch).
|
||||
grunt.log.debug('Shifter passed a specific JS file');
|
||||
src = path.dirname(path.dirname(src));
|
||||
options.recursive = false;
|
||||
} else if (grunt.file.isMatch('**/yui/src', src)) {
|
||||
// When in a src directory --walk all modules.
|
||||
grunt.log.debug('In a src directory');
|
||||
args.push('--walk');
|
||||
options.recursive = false;
|
||||
} else if (grunt.file.isMatch('**/yui/src/*', src)) {
|
||||
// When in module, only build our module.
|
||||
grunt.log.debug('In a module directory');
|
||||
options.recursive = false;
|
||||
} else if (grunt.file.isMatch('**/yui/src/*/js', src)) {
|
||||
// When in module src, only build our module.
|
||||
grunt.log.debug('In a source directory');
|
||||
src = path.dirname(src);
|
||||
options.recursive = false;
|
||||
}
|
||||
|
||||
if (grunt.option('watch')) {
|
||||
grunt.fail.fatal('The --watch option has been removed, please use `grunt watch` instead');
|
||||
}
|
||||
|
||||
// Add the stderr option if appropriate
|
||||
if (grunt.option('verbose')) {
|
||||
args.push('--lint-stderr');
|
||||
}
|
||||
|
||||
if (grunt.option('no-color')) {
|
||||
args.push('--color=false');
|
||||
}
|
||||
|
||||
var execShifter = function() {
|
||||
|
||||
grunt.log.ok("Running shifter on " + src);
|
||||
grunt.util.spawn({
|
||||
cmd: "node",
|
||||
args: args,
|
||||
opts: {cwd: src, stdio: 'inherit', env: process.env}
|
||||
}, function(error, result, code) {
|
||||
if (code) {
|
||||
grunt.fail.fatal('Shifter failed with code: ' + code);
|
||||
} else {
|
||||
grunt.log.ok('Shifter build complete.');
|
||||
filedone();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Actually run shifter.
|
||||
if (!options.recursive) {
|
||||
execShifter();
|
||||
} else {
|
||||
// Check that there are yui modules otherwise shifter ends with exit code 1.
|
||||
if (grunt.file.expand({cwd: src}, '**/yui/src/**/*.js').length > 0) {
|
||||
args.push('--recursive');
|
||||
execShifter();
|
||||
} else {
|
||||
grunt.log.ok('No YUI modules to build.');
|
||||
filedone();
|
||||
}
|
||||
}
|
||||
}, done);
|
||||
};
|
||||
|
||||
tasks.gherkinlint = function() {
|
||||
const done = this.async();
|
||||
const options = grunt.config('gherkinlint.options');
|
||||
|
||||
// Grab the gherkin-lint linter and required scaffolding.
|
||||
const linter = require('gherkin-lint/dist/linter.js');
|
||||
const featureFinder = require('gherkin-lint/dist/feature-finder.js');
|
||||
const configParser = require('gherkin-lint/dist/config-parser.js');
|
||||
const formatter = require('gherkin-lint/dist/formatters/stylish.js');
|
||||
|
||||
// Run the linter.
|
||||
return linter.lint(
|
||||
featureFinder.getFeatureFiles(grunt.file.expand(options.files)),
|
||||
configParser.getConfiguration(configParser.defaultConfigFileName)
|
||||
)
|
||||
.then(results => {
|
||||
// Print the results out uncondtionally.
|
||||
formatter.printResults(results);
|
||||
|
||||
return results;
|
||||
})
|
||||
.then(results => {
|
||||
// Report on the results.
|
||||
// The done function takes a bool whereby a falsey statement causes the task to fail.
|
||||
return results.every(result => result.errors.length === 0);
|
||||
})
|
||||
.then(done); // eslint-disable-line promise/no-callback-in-promise
|
||||
};
|
||||
|
||||
tasks.startup = function() {
|
||||
// Are we in a YUI directory?
|
||||
if (path.basename(path.resolve(cwd, '../../')) == 'yui') {
|
||||
grunt.task.run('yui');
|
||||
// Are we in an AMD directory?
|
||||
} else if (inAMD) {
|
||||
grunt.task.run('amd');
|
||||
} else {
|
||||
// Run them all!.
|
||||
grunt.task.run('css');
|
||||
grunt.task.run('js');
|
||||
grunt.task.run('gherkinlint');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a wrapper task to handle the grunt watch command. It attempts to use
|
||||
* Watchman to monitor for file changes, if it's installed, because it's much faster.
|
||||
*
|
||||
* If Watchman isn't installed then it falls back to the grunt-contrib-watch file
|
||||
* watcher for backwards compatibility.
|
||||
*/
|
||||
tasks.watch = function() {
|
||||
var watchTaskDone = this.async();
|
||||
var watchInitialised = false;
|
||||
var watchTaskQueue = {};
|
||||
var processingQueue = false;
|
||||
|
||||
// Grab the tasks and files that have been queued up and execute them.
|
||||
var processWatchTaskQueue = function() {
|
||||
if (!Object.keys(watchTaskQueue).length || processingQueue) {
|
||||
// If there is nothing in the queue or we're already processing then wait.
|
||||
return;
|
||||
}
|
||||
|
||||
processingQueue = true;
|
||||
|
||||
// Grab all tasks currently in the queue.
|
||||
var queueToProcess = watchTaskQueue;
|
||||
// Reset the queue.
|
||||
watchTaskQueue = {};
|
||||
|
||||
async.forEachSeries(
|
||||
Object.keys(queueToProcess),
|
||||
function(task, next) {
|
||||
var files = queueToProcess[task];
|
||||
var filesOption = '--files=' + files.join(',');
|
||||
grunt.log.ok('Running task ' + task + ' for files ' + filesOption);
|
||||
|
||||
// Spawn the task in a child process so that it doesn't kill this one
|
||||
// if it failed.
|
||||
grunt.util.spawn(
|
||||
{
|
||||
// Spawn with the grunt bin.
|
||||
grunt: true,
|
||||
// Run from current working dir and inherit stdio from process.
|
||||
opts: {
|
||||
cwd: fullRunDir,
|
||||
stdio: 'inherit'
|
||||
},
|
||||
args: [task, filesOption]
|
||||
},
|
||||
function(err, res, code) {
|
||||
if (code !== 0) {
|
||||
// The grunt task failed.
|
||||
grunt.log.error(err);
|
||||
}
|
||||
|
||||
// Move on to the next task.
|
||||
next();
|
||||
}
|
||||
);
|
||||
},
|
||||
function() {
|
||||
// No longer processing.
|
||||
processingQueue = false;
|
||||
// Once all of the tasks are done then recurse just in case more tasks
|
||||
// were queued while we were processing.
|
||||
processWatchTaskQueue();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const originalWatchConfig = grunt.config.get(['watch']);
|
||||
const watchConfig = Object.keys(originalWatchConfig).reduce(function(carry, key) {
|
||||
if (key == 'options') {
|
||||
return carry;
|
||||
}
|
||||
|
||||
const value = originalWatchConfig[key];
|
||||
|
||||
const taskNames = value.tasks;
|
||||
const files = value.files;
|
||||
let excludes = [];
|
||||
if (value.excludes) {
|
||||
excludes = value.excludes;
|
||||
}
|
||||
|
||||
taskNames.forEach(function(taskName) {
|
||||
carry[taskName] = {
|
||||
files,
|
||||
excludes,
|
||||
};
|
||||
});
|
||||
|
||||
return carry;
|
||||
}, {});
|
||||
|
||||
watchmanClient.on('error', function(error) {
|
||||
// We have to add an error handler here and parse the error string because the
|
||||
// example way from the docs to check if Watchman is installed doesn't actually work!!
|
||||
// See: https://github.com/facebook/watchman/issues/509
|
||||
if (error.message.match('Watchman was not found')) {
|
||||
// If watchman isn't installed then we should fallback to the other watch task.
|
||||
grunt.log.ok('It is recommended that you install Watchman for better performance using the "watch" command.');
|
||||
|
||||
// Fallback to the old grunt-contrib-watch task.
|
||||
grunt.renameTask('watch-grunt', 'watch');
|
||||
grunt.task.run(['watch']);
|
||||
// This task is finished.
|
||||
watchTaskDone(0);
|
||||
} else {
|
||||
grunt.log.error(error);
|
||||
// Fatal error.
|
||||
watchTaskDone(1);
|
||||
}
|
||||
});
|
||||
|
||||
watchmanClient.on('subscription', function(resp) {
|
||||
if (resp.subscription !== 'grunt-watch') {
|
||||
return;
|
||||
}
|
||||
|
||||
resp.files.forEach(function(file) {
|
||||
grunt.log.ok('File changed: ' + file.name);
|
||||
|
||||
var fullPath = fullRunDir + '/' + file.name;
|
||||
Object.keys(watchConfig).forEach(function(task) {
|
||||
|
||||
const fileGlobs = watchConfig[task].files;
|
||||
var match = fileGlobs.some(function(fileGlob) {
|
||||
return grunt.file.isMatch(`**/${fileGlob}`, fullPath);
|
||||
});
|
||||
|
||||
if (match) {
|
||||
// If we are watching a subdirectory then the file.name will be relative
|
||||
// to that directory. However the grunt tasks expect the file paths to be
|
||||
// relative to the Gruntfile.js location so let's normalise them before
|
||||
// adding them to the queue.
|
||||
var relativePath = fullPath.replace(gruntFilePath + '/', '');
|
||||
if (task in watchTaskQueue) {
|
||||
if (!watchTaskQueue[task].includes(relativePath)) {
|
||||
watchTaskQueue[task] = watchTaskQueue[task].concat(relativePath);
|
||||
}
|
||||
} else {
|
||||
watchTaskQueue[task] = [relativePath];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
processWatchTaskQueue();
|
||||
});
|
||||
|
||||
process.on('SIGINT', function() {
|
||||
// Let the user know that they may need to manually stop the Watchman daemon if they
|
||||
// no longer want it running.
|
||||
if (watchInitialised) {
|
||||
grunt.log.ok('The Watchman daemon may still be running and may need to be stopped manually.');
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
|
||||
// Initiate the watch on the current directory.
|
||||
watchmanClient.command(['watch-project', fullRunDir], function(watchError, watchResponse) {
|
||||
if (watchError) {
|
||||
grunt.log.error('Error initiating watch:', watchError);
|
||||
watchTaskDone(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('warning' in watchResponse) {
|
||||
grunt.log.error('warning: ', watchResponse.warning);
|
||||
}
|
||||
|
||||
var watch = watchResponse.watch;
|
||||
var relativePath = watchResponse.relative_path;
|
||||
watchInitialised = true;
|
||||
|
||||
watchmanClient.command(['clock', watch], function(clockError, clockResponse) {
|
||||
if (clockError) {
|
||||
grunt.log.error('Failed to query clock:', clockError);
|
||||
watchTaskDone(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the expression query used by watchman.
|
||||
// Documentation is limited, but see https://facebook.github.io/watchman/docs/expr/allof.html for examples.
|
||||
// We generate an expression to match any value in the files list of all of our tasks, but excluding
|
||||
// all value in the excludes list of that task.
|
||||
//
|
||||
// [anyof, [
|
||||
// [allof, [
|
||||
// [anyof, [
|
||||
// ['match', validPath, 'wholename'],
|
||||
// ['match', validPath, 'wholename'],
|
||||
// ],
|
||||
// [not,
|
||||
// [anyof, [
|
||||
// ['match', invalidPath, 'wholename'],
|
||||
// ['match', invalidPath, 'wholename'],
|
||||
// ],
|
||||
// ],
|
||||
// ],
|
||||
var matchWholeName = fileGlob => ['match', fileGlob, 'wholename'];
|
||||
var matches = Object.keys(watchConfig).map(function(task) {
|
||||
const matchAll = [];
|
||||
matchAll.push(['anyof'].concat(watchConfig[task].files.map(matchWholeName)));
|
||||
|
||||
if (watchConfig[task].excludes.length) {
|
||||
matchAll.push(['not', ['anyof'].concat(watchConfig[task].excludes.map(matchWholeName))]);
|
||||
}
|
||||
|
||||
return ['allof'].concat(matchAll);
|
||||
});
|
||||
|
||||
matches = ['anyof'].concat(matches);
|
||||
|
||||
var sub = {
|
||||
expression: matches,
|
||||
// Which fields we're interested in.
|
||||
fields: ["name", "size", "type"],
|
||||
// Add our time constraint.
|
||||
since: clockResponse.clock
|
||||
};
|
||||
|
||||
if (relativePath) {
|
||||
/* eslint-disable camelcase */
|
||||
sub.relative_root = relativePath;
|
||||
}
|
||||
|
||||
watchmanClient.command(['subscribe', watch, 'grunt-watch', sub], function(subscribeError) {
|
||||
if (subscribeError) {
|
||||
// Probably an error in the subscription criteria.
|
||||
grunt.log.error('failed to subscribe: ', subscribeError);
|
||||
watchTaskDone(1);
|
||||
return;
|
||||
}
|
||||
|
||||
grunt.log.ok('Listening for changes to files in ' + fullRunDir);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// On watch, we dynamically modify config to build only affected files. This
|
||||
// method is slightly complicated to deal with multiple changed files at once (copied
|
||||
// from the grunt-contrib-watch readme).
|
||||
var changedFiles = Object.create(null);
|
||||
var onChange = grunt.util._.debounce(function() {
|
||||
var files = Object.keys(changedFiles);
|
||||
grunt.config('eslint.amd.src', files);
|
||||
grunt.config('eslint.yui.src', files);
|
||||
grunt.config('shifter.options.paths', files);
|
||||
grunt.config('gherkinlint.options.files', files);
|
||||
grunt.config('babel.dist.files', [{expand: true, src: files, rename: babelRename}]);
|
||||
changedFiles = Object.create(null);
|
||||
}, 200);
|
||||
|
||||
grunt.event.on('watch', function(action, filepath) {
|
||||
changedFiles[filepath] = action;
|
||||
onChange();
|
||||
});
|
||||
|
||||
// Register NPM tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-sass');
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-stylelint');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
|
||||
// Rename the grunt-contrib-watch "watch" task because we're going to wrap it.
|
||||
grunt.renameTask('watch', 'watch-grunt');
|
||||
|
||||
// Register JS tasks.
|
||||
grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
|
||||
grunt.registerTask('gherkinlint', 'Run gherkinlint against the current directory', tasks.gherkinlint);
|
||||
grunt.registerTask('ignorefiles', 'Generate ignore files for linters', tasks.ignorefiles);
|
||||
grunt.registerTask('watch', 'Run tasks on file changes', tasks.watch);
|
||||
grunt.registerTask('yui', ['eslint:yui', 'shifter']);
|
||||
grunt.registerTask('amd', ['eslint:amd', 'babel']);
|
||||
grunt.registerTask('js', ['amd', 'yui']);
|
||||
|
||||
// Register CSS tasks.
|
||||
registerStyleLintTasks(grunt, files, fullRunDir);
|
||||
|
||||
// Register the startup task.
|
||||
grunt.registerTask('startup', 'Run the correct tasks for the current directory', tasks.startup);
|
||||
|
||||
// Register the default task.
|
||||
grunt.registerTask('default', ['startup']);
|
||||
|
||||
@@ -92,16 +92,6 @@ const fetchComponentData = () => {
|
||||
return componentData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of component paths.
|
||||
*
|
||||
* @param {string} relativeTo
|
||||
* @returns {array}
|
||||
*/
|
||||
const getComponentPaths = (relativeTo = '') => fetchComponentData().pathList.map(componentPath => {
|
||||
return componentPath.replace(relativeTo, '');
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the list of paths to build AMD sources.
|
||||
*
|
||||
@@ -150,49 +140,6 @@ const getThirdPartyLibsList = relativeTo => {
|
||||
.sort();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of thirdparty library paths.
|
||||
*
|
||||
* @returns {array}
|
||||
*/
|
||||
const getThirdPartyPaths = () => {
|
||||
const DOMParser = require('xmldom').DOMParser;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const xpath = require('xpath');
|
||||
|
||||
const thirdpartyfiles = getThirdPartyLibsList(fs.realpathSync('./'));
|
||||
const libs = ['node_modules/', 'vendor/'];
|
||||
|
||||
const addLibToList = lib => {
|
||||
if (!lib.match('\\*') && fs.statSync(lib).isDirectory()) {
|
||||
// Ensure trailing slash on dirs.
|
||||
lib = lib.replace(/\/?$/, '/');
|
||||
}
|
||||
|
||||
// Look for duplicate paths before adding to array.
|
||||
if (libs.indexOf(lib) === -1) {
|
||||
libs.push(lib);
|
||||
}
|
||||
};
|
||||
|
||||
thirdpartyfiles.forEach(function(file) {
|
||||
const dirname = path.dirname(file);
|
||||
|
||||
const xmlContent = fs.readFileSync(file, 'utf8');
|
||||
const doc = new DOMParser().parseFromString(xmlContent);
|
||||
const nodes = xpath.select("/libraries/library/location/text()", doc);
|
||||
|
||||
nodes.forEach(function(node) {
|
||||
let lib = path.posix.join(dirname, node.toString());
|
||||
addLibToList(lib);
|
||||
});
|
||||
});
|
||||
|
||||
return libs;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the name of the component matching the specified path.
|
||||
*
|
||||
@@ -235,9 +182,7 @@ const getOwningComponentDirectory = checkPath => {
|
||||
module.exports = {
|
||||
getAmdSrcGlobList,
|
||||
getComponentFromPath,
|
||||
getComponentPaths,
|
||||
getOwningComponentDirectory,
|
||||
getYuiSrcGlobList,
|
||||
getThirdPartyLibsList,
|
||||
getThirdPartyPaths,
|
||||
};
|
||||
+4
-13
@@ -44,9 +44,7 @@ switch ($action) {
|
||||
$key = array_search($auth, $authsenabled);
|
||||
if ($key !== false) {
|
||||
unset($authsenabled[$key]);
|
||||
$value = implode(',', $authsenabled);
|
||||
add_to_config_log('auth', $CFG->auth, $value, 'core');
|
||||
set_config('auth', $value);
|
||||
set_config('auth', implode(',', $authsenabled));
|
||||
}
|
||||
|
||||
if ($auth == $CFG->registerauth) {
|
||||
@@ -61,11 +59,8 @@ switch ($action) {
|
||||
if (!in_array($auth, $authsenabled)) {
|
||||
$authsenabled[] = $auth;
|
||||
$authsenabled = array_unique($authsenabled);
|
||||
$value = implode(',', $authsenabled);
|
||||
add_to_config_log('auth', $CFG->auth, $value, 'core');
|
||||
set_config('auth', $value);
|
||||
set_config('auth', implode(',', $authsenabled));
|
||||
}
|
||||
|
||||
\core\session\manager::gc(); // Remove stale sessions.
|
||||
core_plugin_manager::reset_caches();
|
||||
break;
|
||||
@@ -81,9 +76,7 @@ switch ($action) {
|
||||
$fsave = $authsenabled[$key];
|
||||
$authsenabled[$key] = $authsenabled[$key + 1];
|
||||
$authsenabled[$key + 1] = $fsave;
|
||||
$value = implode(',', $authsenabled);
|
||||
add_to_config_log('auth', $CFG->auth, $value, 'core');
|
||||
set_config('auth', $value);
|
||||
set_config('auth', implode(',', $authsenabled));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -98,9 +91,7 @@ switch ($action) {
|
||||
$fsave = $authsenabled[$key];
|
||||
$authsenabled[$key] = $authsenabled[$key - 1];
|
||||
$authsenabled[$key - 1] = $fsave;
|
||||
$value = implode(',', $authsenabled);
|
||||
add_to_config_log('auth', $CFG->auth, $value, 'core');
|
||||
set_config('auth', $value);
|
||||
set_config('auth', implode(',', $authsenabled));
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -171,6 +171,8 @@ function print_auth_lock_options($auth, $user_fields, $helptext, $retrieveopts,
|
||||
// limit for the setting name is 100.
|
||||
continue;
|
||||
}
|
||||
} elseif ($fieldname == 'url') {
|
||||
$fieldname = get_string('webpage');
|
||||
} else {
|
||||
$fieldname = get_string($fieldname);
|
||||
}
|
||||
|
||||
@@ -158,9 +158,4 @@ echo $outputhtml;
|
||||
echo html_writer::end_tag('fieldset');
|
||||
echo html_writer::end_tag('form');
|
||||
|
||||
$PAGE->requires->yui_module('moodle-core-formchangechecker', 'M.core_formchangechecker.init', [[
|
||||
'formid' => 'adminsettings'
|
||||
]]);
|
||||
$PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -126,7 +126,6 @@ class filesize extends \admin_setting {
|
||||
if (is_null($bytes)) {
|
||||
return null;
|
||||
}
|
||||
$bytes = intval($bytes);
|
||||
|
||||
return self::parse_bytes($bytes);
|
||||
}
|
||||
@@ -146,10 +145,9 @@ class filesize extends \admin_setting {
|
||||
return get_string('errorsetting', 'admin');
|
||||
}
|
||||
|
||||
// Calculate size in bytes, ensuring we don't overflow PHP_INT_MAX.
|
||||
$bytes = $data['v'] * $data['u'];
|
||||
$result = (is_int($bytes) && $this->config_write($this->name, $bytes));
|
||||
|
||||
$result = $this->config_write($this->name, $bytes);
|
||||
return ($result ? '' : get_string('errorsetting', 'admin'));
|
||||
}
|
||||
|
||||
|
||||
@@ -124,9 +124,8 @@ class task_log_table extends \table_sql {
|
||||
$sort = "ORDER BY $sort";
|
||||
}
|
||||
|
||||
// TODO Does not support custom user profile fields (MDL-70456).
|
||||
$userfieldsapi = \core_user\fields::for_identity(\context_system::instance(), false)->with_userpic();
|
||||
$userfields = $userfieldsapi->get_sql('u', false, 'user', 'userid2', false)->selects;
|
||||
$extrafields = get_extra_user_fields(\context_system::instance());
|
||||
$userfields = \user_picture::fields('u', $extrafields, 'userid2', 'user');
|
||||
|
||||
$where = '';
|
||||
if (!empty($this->sql->where)) {
|
||||
|
||||
+1
-8
@@ -120,14 +120,7 @@ if ($options['unset'] || $options['set'] !== null) {
|
||||
}
|
||||
|
||||
// Check that the variable is not hard-set in the main config.php already.
|
||||
$component = $options['component'];
|
||||
if (!empty($component)) {
|
||||
$componentsettings = $CFG->forced_plugin_settings[$component] ?? [];
|
||||
$settinginconfig = array_key_exists($options['name'], $componentsettings);
|
||||
} else {
|
||||
$settinginconfig = array_key_exists($options['name'], $CFG->config_php_settings);
|
||||
}
|
||||
if ($settinginconfig) {
|
||||
if (array_key_exists($options['name'], $CFG->config_php_settings)) {
|
||||
cli_error('The configuration variable is hard-set in the config.php, unable to change.', 4);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* CLI script to reset dashboards.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage cli
|
||||
* @copyright 2021 Brendan Heywood (brendan@catalyst-au.net)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
|
||||
require(__DIR__.'/../../config.php');
|
||||
require_once($CFG->libdir.'/clilib.php');
|
||||
require_once($CFG->dirroot.'/my/lib.php');
|
||||
|
||||
list($options, $unrecognized) = cli_get_params([
|
||||
'help' => false,
|
||||
'execute' => false,
|
||||
], [
|
||||
'h' => 'help',
|
||||
'e' => 'execute',
|
||||
]);
|
||||
|
||||
if ($unrecognized) {
|
||||
$unrecognized = implode("\n ", $unrecognized);
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized), 2);
|
||||
}
|
||||
|
||||
if (!$options['execute']) {
|
||||
$help = <<<EOF
|
||||
Resets Moodle dashboards for all users
|
||||
|
||||
Options:
|
||||
-h, --help Print out this help
|
||||
-e, --execute Actually run the reset
|
||||
|
||||
Example:
|
||||
\$ sudo -u www-data /usr/bin/php admin/cli/dashboard_reset.php -e
|
||||
|
||||
EOF;
|
||||
|
||||
echo $help;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$progressbar = new progress_bar();
|
||||
$progressbar->create();
|
||||
my_reset_page_for_all_users(MY_PAGE_PRIVATE, 'my-index', $progressbar);
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Fix orphaned calendar events that were broken by MDL-67494.
|
||||
*
|
||||
* This script will look for all the calendar events which userids
|
||||
* where broken by a wrong upgrade step, affecting to Moodle 3.9.5
|
||||
* and up.
|
||||
*
|
||||
* It performs checks to both:
|
||||
* a) Detect if the site was affected (ran the wrong upgrade step).
|
||||
* b) Look for orphaned calendar events, categorising them as:
|
||||
* - standard: site / category / course / group / user events
|
||||
* - subscription: events created via subscriptions.
|
||||
* - action: normal action events, created to show common important dates.
|
||||
* - override: user and group override events, particular, that some activities support.
|
||||
* - custom: other events, not being any of the above, common or particular.
|
||||
* By specifying it (--fix) try to recover as many broken events (missing userid) as
|
||||
* possible. Standard, subscription, action, override events in core are fully supported but
|
||||
* override or custom events should be fixed by each plugin as far as there isn't any standard
|
||||
* API (plugin-wise) to launch a rebuild of the calendar events.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2021 onwards Simey Lameze <simey@moodle.com>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir . "/clilib.php");
|
||||
require_once($CFG->libdir . '/db/upgradelib.php');
|
||||
|
||||
// Supported options.
|
||||
$long = ['fix' => false, 'help' => false];
|
||||
$short = ['f' => 'fix', 'h' => 'help'];
|
||||
|
||||
// CLI options.
|
||||
[$options, $unrecognized] = cli_get_params($long, $short);
|
||||
|
||||
if ($unrecognized) {
|
||||
$unrecognized = implode("\n ", $unrecognized);
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help']) {
|
||||
$help = <<<EOT
|
||||
Fix orphaned calendar events.
|
||||
|
||||
This script detects calendar events that have had their
|
||||
userid lost. By default it will perform various checks
|
||||
and report them, showing the site status in an easy way.
|
||||
|
||||
Also, optionally (--fix), it wil try to recover as many
|
||||
lost userids as possible from different sources. Note that
|
||||
this script aims to process well-know events in core,
|
||||
leaving custom events in 3rd part plugins mostly unmodified
|
||||
because there isn't any consistent way to regenerate them.
|
||||
|
||||
For more details: https://tracker.moodle.org/browse/MDL-71156
|
||||
|
||||
Options:
|
||||
-h, --help Print out this help.
|
||||
-f, --fix Fix the orphaned calendar events in the DB.
|
||||
If not specified only check and report problems to STDERR.
|
||||
|
||||
Usage:
|
||||
- Only report: \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php
|
||||
- Report and fix: \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php -f
|
||||
EOT;
|
||||
|
||||
cli_writeln($help);
|
||||
die;
|
||||
}
|
||||
|
||||
// Check various usual pre-requisites.
|
||||
if (empty($CFG->version)) {
|
||||
cli_error('Database is not yet installed.');
|
||||
}
|
||||
|
||||
$admin = get_admin();
|
||||
if (!$admin) {
|
||||
cli_error('Error: No admin account was found.');
|
||||
}
|
||||
|
||||
if (moodle_needs_upgrading()) {
|
||||
cli_error('Moodle upgrade pending, script execution suspended.');
|
||||
}
|
||||
|
||||
// Do everything as admin by default.
|
||||
\core\session\manager::set_user($admin);
|
||||
|
||||
// Report current site status.
|
||||
cli_heading('Checking the site status');
|
||||
$needsfix = upgrade_calendar_site_status();
|
||||
|
||||
// Report current calendar events status.
|
||||
cli_heading('Checking the calendar events status');
|
||||
$info = upgrade_calendar_events_status();
|
||||
$hasbadevents = $info['total']->bad > 0 || $info['total']->bad != $info['other']->bad;
|
||||
$needsfix = $needsfix || $hasbadevents;
|
||||
|
||||
// If, selected, fix as many calendar events as possible.
|
||||
if ($options['fix']) {
|
||||
|
||||
// If the report has told us that the fix was not needed... ask for confirmation!
|
||||
if (!$needsfix) {
|
||||
cli_writeln("This site DOES NOT NEED to run the calendar events fix.");
|
||||
$input = cli_input('Are you completely sure that you want to run the fix? (y/N)', 'N', ['y', 'Y', 'n', 'N']);
|
||||
if (strtolower($input) != 'y') {
|
||||
exit(0);
|
||||
}
|
||||
cli_writeln("");
|
||||
}
|
||||
cli_heading('Fixing as many as possible calendar events');
|
||||
upgrade_calendar_events_fix_remaining($info);
|
||||
// Report current (after fix) calendar events status.
|
||||
cli_heading('Checking the calendar events status (after fix)');
|
||||
upgrade_calendar_events_status();
|
||||
} else if ($needsfix) {
|
||||
// Fix option was not provided but problem events have been found. Notify the user and provide info how to fix these events.
|
||||
cli_writeln("This site NEEDS to run the calendar events fix!");
|
||||
cli_writeln("To fix the calendar events, re-run this script with the --fix option.");
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Generates a secure key for the current server (presuming it does not already exist).
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2020 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use \core\encryption;
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
|
||||
require(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir . '/clilib.php');
|
||||
|
||||
// Get cli options.
|
||||
[$options, $unrecognized] = cli_get_params(
|
||||
['help' => false, 'method' => null],
|
||||
['h' => 'help']);
|
||||
|
||||
if ($unrecognized) {
|
||||
$unrecognized = implode("\n ", $unrecognized);
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
// TODO: MDL-71421 - Remove the openssl alternative once sodium becomes a requirement in Moodle 4.2.
|
||||
|
||||
if ($options['help']) {
|
||||
echo "Generate secure key
|
||||
|
||||
This script manually creates a secure key within the secret data root folder (configured in
|
||||
config.php as \$CFG->secretdataroot). You must run it using an account with access to write
|
||||
to that folder.
|
||||
|
||||
In normal use Moodle automatically creates the key; this script is intended when setting up
|
||||
a new Moodle system, for cases where the secure folder is not on shared storage and the key
|
||||
may be manually installed on multiple servers.
|
||||
|
||||
Options:
|
||||
-h, --help Print out this help
|
||||
--method <method> Generate key for specified encryption method instead of default.
|
||||
* sodium
|
||||
* openssl-aes-256-ctr
|
||||
|
||||
Example:
|
||||
php admin/cli/generate_key.php
|
||||
";
|
||||
exit;
|
||||
}
|
||||
|
||||
$method = $options['method'];
|
||||
|
||||
if (encryption::key_exists($method)) {
|
||||
echo 'Key already exists: ' . encryption::get_key_file($method) . "\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Creates key with default permissions (no chmod).
|
||||
echo "Generating key...\n";
|
||||
encryption::create_key($method, false);
|
||||
|
||||
echo "\nKey created: " . encryption::get_key_file($method) . "\n\n";
|
||||
echo "If the key folder is not shared storage, then key files should be copied to all servers.\n";
|
||||
@@ -56,7 +56,6 @@ Options:
|
||||
--agree-license Indicates agreement with software license.
|
||||
--fullname=STRING Name of the site
|
||||
--shortname=STRING Name of the site
|
||||
--summary=STRING The summary to be displayed on the front page
|
||||
-h, --help Print out this help
|
||||
|
||||
Example:
|
||||
@@ -97,7 +96,6 @@ list($options, $unrecognized) = cli_get_params(
|
||||
'adminemail' => '',
|
||||
'fullname' => '',
|
||||
'shortname' => '',
|
||||
'summary' => '',
|
||||
'agree-license' => false,
|
||||
'help' => false
|
||||
),
|
||||
@@ -106,11 +104,6 @@ list($options, $unrecognized) = cli_get_params(
|
||||
)
|
||||
);
|
||||
|
||||
if ($unrecognized) {
|
||||
$unrecognized = implode("\n ", $unrecognized);
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
// We show help text even if tables are installed.
|
||||
if ($options['help']) {
|
||||
echo $help;
|
||||
|
||||
@@ -119,7 +119,7 @@ if (!empty($options['collation'])) {
|
||||
if ($dbcollation->value !== $collation || $dbcharset->value !== $charset) {
|
||||
// Try to convert the DB.
|
||||
echo "Converting database to '$collation' for $CFG->wwwroot:\n";
|
||||
$sql = "ALTER DATABASE `$CFG->dbname` DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation";
|
||||
$sql = "ALTER DATABASE $CFG->dbname DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation";
|
||||
try {
|
||||
$DB->change_database_structure($sql);
|
||||
} catch (exception $e) {
|
||||
@@ -146,7 +146,7 @@ if (!empty($options['collation'])) {
|
||||
|
||||
} else {
|
||||
try {
|
||||
$DB->change_database_structure("ALTER TABLE `$table->name` CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
$DB->change_database_structure("ALTER TABLE $table->name CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
echo "CONVERTED\n";
|
||||
$converted++;
|
||||
} catch (ddl_exception $e) {
|
||||
@@ -163,7 +163,7 @@ if (!empty($options['collation'])) {
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SHOW FULL COLUMNS FROM `$table->name` WHERE collation IS NOT NULL";
|
||||
$sql = "SHOW FULL COLUMNS FROM $table->name WHERE collation IS NOT NULL";
|
||||
$rs2 = $DB->get_recordset_sql($sql);
|
||||
foreach ($rs2 as $column) {
|
||||
$column = (object)array_change_key_case((array)$column, CASE_LOWER);
|
||||
@@ -181,7 +181,7 @@ if (!empty($options['collation'])) {
|
||||
$notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
|
||||
$default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
|
||||
// primary, unique and inc are not supported for texts
|
||||
$sql = "ALTER TABLE `$table->name`
|
||||
$sql = "ALTER TABLE $table->name
|
||||
MODIFY COLUMN $column->field $column->type
|
||||
CHARACTER SET $charset
|
||||
COLLATE $collation $notnull $default";
|
||||
@@ -192,11 +192,11 @@ if (!empty($options['collation'])) {
|
||||
$default = !is_null($column->default) ? "DEFAULT '$column->default'" : '';
|
||||
|
||||
if ($rowformat != '') {
|
||||
$sql = "ALTER TABLE `$table->name` $rowformat";
|
||||
$sql = "ALTER TABLE $table->name $rowformat";
|
||||
$DB->change_database_structure($sql);
|
||||
}
|
||||
|
||||
$sql = "ALTER TABLE `$table->name`
|
||||
$sql = "ALTER TABLE $table->name
|
||||
MODIFY COLUMN $column->field $column->type
|
||||
CHARACTER SET $charset
|
||||
COLLATE $collation $notnull $default";
|
||||
@@ -315,8 +315,8 @@ function mysql_set_row_format($tablename, $charset, $collation, $engine) {
|
||||
if ($rs->row_format == 'Compact' || $rs->row_format == 'Redundant') {
|
||||
$rowformat = $DB->get_row_format_sql($engine, $collation);
|
||||
// Try to convert to compressed format and then try updating the collation again.
|
||||
$DB->change_database_structure("ALTER TABLE `$tablename` $rowformat");
|
||||
$DB->change_database_structure("ALTER TABLE `$tablename` CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
$DB->change_database_structure("ALTER TABLE $tablename $rowformat");
|
||||
$DB->change_database_structure("ALTER TABLE $tablename CONVERT TO CHARACTER SET $charset COLLATE $collation");
|
||||
} else {
|
||||
// Row format may not be the problem. Can not diagnose problem. Send fail reply.
|
||||
return false;
|
||||
|
||||
@@ -158,7 +158,7 @@ if (!empty($options['info'])) {
|
||||
}
|
||||
|
||||
foreach ($fixtables as $table) {
|
||||
$DB->change_database_structure("ALTER TABLE `{$prefix}$table` ROW_FORMAT=Compressed");
|
||||
$DB->change_database_structure("ALTER TABLE {$prefix}$table ROW_FORMAT=Compressed");
|
||||
echo str_pad($prefix . $table, 32, ' ', STR_PAD_RIGHT) . " ... Compressed\n";
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ if (!empty($options['info'])) {
|
||||
echo "SET GLOBAL innodb_file_per_table=1;\n";
|
||||
echo "SET GLOBAL innodb_file_format=Barracuda;\n";
|
||||
foreach ($fixtables as $table) {
|
||||
echo "ALTER TABLE `{$prefix}$table` ROW_FORMAT=Compressed;\n";
|
||||
echo "ALTER TABLE {$prefix}$table ROW_FORMAT=Compressed;\n";
|
||||
}
|
||||
echo "\n";
|
||||
exit(0);
|
||||
|
||||
@@ -69,11 +69,11 @@ if ($options['help'] or (!$options['list'] and !$options['execute'])) {
|
||||
die;
|
||||
}
|
||||
|
||||
if ($options['showdebugging'] || !empty($CFG->showcrondebugging)) {
|
||||
if ($options['showdebugging']) {
|
||||
set_debugging(DEBUG_DEVELOPER, true);
|
||||
}
|
||||
|
||||
if ($options['showsql'] || !empty($CFG->showcronsql)) {
|
||||
if ($options['showsql']) {
|
||||
$DB->set_debug(true);
|
||||
}
|
||||
if ($options['list']) {
|
||||
|
||||
@@ -39,8 +39,6 @@ Options:
|
||||
--purge-missing Uninstall all missing from disk plugins.
|
||||
--plugins=<plugin name> A comma separated list of plugins to be uninstalled. E.g. mod_assign,mod_forum
|
||||
--run Execute uninstall. If this option is not set, then the script will be run in a dry mode.
|
||||
--showsql Show sql queries before they are executed.
|
||||
--showdebugging Show developer level debugging information.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -74,8 +72,6 @@ list($options, $unrecognised) = cli_get_params([
|
||||
'purge-missing' => false,
|
||||
'plugins' => false,
|
||||
'run' => false,
|
||||
'showsql' => false,
|
||||
'showdebugging' => false,
|
||||
], [
|
||||
'h' => 'help'
|
||||
]);
|
||||
@@ -90,14 +86,6 @@ if ($options['help']) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($options['showdebugging']) {
|
||||
set_debugging(DEBUG_DEVELOPER, true);
|
||||
}
|
||||
|
||||
if ($options['showsql']) {
|
||||
$DB->set_debug(true);
|
||||
}
|
||||
|
||||
$pluginman = core_plugin_manager::instance();
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
|
||||
|
||||
@@ -135,15 +135,6 @@ if (!$envstatus) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Make sure there are no files left over from previous versions.
|
||||
if (upgrade_stale_php_files_present()) {
|
||||
cli_problem(get_string('upgradestalefiles', 'admin'));
|
||||
|
||||
// Stale file info contains HTML elements which aren't suitable for CLI.
|
||||
$upgradestalefilesinfo = get_string('upgradestalefilesinfo', 'admin', get_docs_url('Upgrading'));
|
||||
cli_error(strip_tags($upgradestalefilesinfo));
|
||||
}
|
||||
|
||||
// Test plugin dependencies.
|
||||
$failed = array();
|
||||
if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed, $CFG->branch)) {
|
||||
|
||||
+1
-399
@@ -3163,11 +3163,6 @@
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_igbinary322_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="igbinary322buggyversion" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.10" requires="3.5">
|
||||
@@ -3350,11 +3345,6 @@
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_igbinary322_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="igbinary322buggyversion" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.11" requires="3.6">
|
||||
@@ -3457,7 +3447,7 @@
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="optional"/>
|
||||
<PHP_EXTENSION name="sodium" level="required"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
@@ -3537,394 +3527,6 @@
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.0" requires="3.6">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.2.29" />
|
||||
<VENDOR name="mysql" version="5.7" />
|
||||
<VENDOR name="postgres" version="10" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.3.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="optional"/>
|
||||
<PHP_EXTENSION name="exif" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="iscustomadminwarnings" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.1" requires="3.9">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.4" />
|
||||
<VENDOR name="mysql" version="5.7" />
|
||||
<VENDOR name="postgres" version="12" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="7.4.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
<PHP_EXTENSION name="sodium" level="optional"/>
|
||||
<PHP_EXTENSION name="exif" level="optional"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_max_input_vars" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_admin_dir_usage" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="iscustomadminwarnings" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -119,19 +119,10 @@ if (($form = data_submitted()) && confirm_sesskey()) {
|
||||
// Save processors enabled/disabled status.
|
||||
foreach ($allprocessors as $processor) {
|
||||
$enabled = isset($form->{$processor->name});
|
||||
if ($enabled != $processor->enabled) {
|
||||
add_to_config_log($processor->name, $processor->enabled, $enabled, 'core');
|
||||
}
|
||||
\core_message\api::update_processor_status($processor, $enabled);
|
||||
}
|
||||
|
||||
foreach ($newpreferences as $name => $value) {
|
||||
$old = isset($preferences->$name) ? $preferences->$name : '';
|
||||
|
||||
if ($old != $value) {
|
||||
add_to_config_log($name, $old, $value, 'core');
|
||||
}
|
||||
|
||||
set_config($name, $value, 'message');
|
||||
}
|
||||
$transaction->allow_commit();
|
||||
|
||||
@@ -34,12 +34,11 @@ $error = optional_param('error', '', PARAM_RAW);
|
||||
if ($error) {
|
||||
$message = optional_param('error_description', '', PARAM_RAW);
|
||||
if ($message) {
|
||||
$SESSION->loginerrormsg = $message;
|
||||
redirect(new moodle_url(get_login_url()));
|
||||
print_error($message);
|
||||
} else {
|
||||
$SESSION->loginerrormsg = $error;
|
||||
redirect(new moodle_url(get_login_url()));
|
||||
print_error($error);
|
||||
}
|
||||
die();
|
||||
}
|
||||
|
||||
// The authorization code generated by the authorization server.
|
||||
@@ -54,6 +53,5 @@ if (isset($params['sesskey']) and confirm_sesskey($params['sesskey'])) {
|
||||
$redirecturl->param('oauth2code', $code);
|
||||
redirect($redirecturl);
|
||||
} else {
|
||||
$SESSION->loginerrormsg = get_string('invalidsesskey', 'error');
|
||||
redirect(new moodle_url(get_login_url()));
|
||||
print_error('invalidsesskey');
|
||||
}
|
||||
|
||||
@@ -38,11 +38,12 @@ $form = new core_admin\form\purge_caches(null, ['returnurl' => $returnurl]);
|
||||
if ($data = $form->get_data()) {
|
||||
|
||||
// Valid request. Purge, and redirect the user back to where they came from.
|
||||
$selected = $data->purgeselectedoptions;
|
||||
purge_caches($selected);
|
||||
|
||||
if (isset($data->all)) {
|
||||
purge_caches();
|
||||
$message = get_string('purgecachesfinished', 'admin');
|
||||
} else {
|
||||
purge_caches($data->purgeselectedoptions);
|
||||
$message = get_string('purgeselectedcachesfinished', 'admin');
|
||||
}
|
||||
|
||||
@@ -51,14 +52,8 @@ if ($data = $form->get_data()) {
|
||||
$message = get_string('purgecachesfinished', 'admin');
|
||||
}
|
||||
|
||||
// Redirect and/or show notification message confirming cache(s) were purged.
|
||||
if (isset($message)) {
|
||||
if (!$PAGE->url->compare($returnurl, URL_MATCH_BASE)) {
|
||||
redirect($returnurl, $message);
|
||||
}
|
||||
|
||||
// We are already on the purge caches page, add the notification.
|
||||
\core\notification::add($message, \core\output\notification::NOTIFY_INFO);
|
||||
redirect($returnurl, $message);
|
||||
}
|
||||
|
||||
// Otherwise, show a form to actually purge the caches.
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Endpoint to get the Moodle version
|
||||
*
|
||||
* Moodle linkchecker will check this endpoint when it cannot get the Moodle version otherwise.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2022 Victor Deniz <victor@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
// Get public key.
|
||||
$key = download_file_content(HUB_STATSPUBLICKEY);
|
||||
// Check $key is a valid public key.
|
||||
$publickey = openssl_pkey_get_public($key);
|
||||
if ($publickey !== false) {
|
||||
$publickeystr = openssl_pkey_get_details($publickey)['key'];
|
||||
} else {
|
||||
die('Error getting a valid public key');
|
||||
}
|
||||
|
||||
// Encrypt data.
|
||||
$message = $CFG->release;
|
||||
$success = openssl_public_encrypt(utf8_encode($message), $encrypteddata, $publickeystr);
|
||||
|
||||
if ($success) {
|
||||
echo base64_encode($encrypteddata);
|
||||
} else {
|
||||
die ('Error encrypting the data');
|
||||
}
|
||||
+16
-27
@@ -220,7 +220,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$output .= $this->container_end();
|
||||
}
|
||||
|
||||
$button = new single_button($continueurl, get_string('upgradestart', 'admin'), 'get', true);
|
||||
$button = new single_button($continueurl, get_string('upgradestart', 'admin'), 'get');
|
||||
$button->class = 'continuebutton';
|
||||
$output .= $this->render($button);
|
||||
$output .= $this->footer();
|
||||
@@ -1616,18 +1616,12 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
|
||||
$numtotal = $numextension = $numupdatable = $numinstallable = 0;
|
||||
$numtotal = $numextension = $numupdatable = 0;
|
||||
|
||||
foreach ($plugininfo as $type => $plugins) {
|
||||
foreach ($plugins as $name => $plugin) {
|
||||
if ($res = $plugin->available_updates()) {
|
||||
if ($plugin->available_updates()) {
|
||||
$numupdatable++;
|
||||
foreach ($res as $updateinfo) {
|
||||
if ($pluginman->is_remote_plugin_installable($updateinfo->component, $updateinfo->version, $reason, false)) {
|
||||
$numinstallable++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
|
||||
continue;
|
||||
@@ -1670,13 +1664,16 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$out .= $this->output->heading(get_string('overviewext', 'core_plugin'), 3);
|
||||
}
|
||||
|
||||
if ($numinstallable) {
|
||||
$out .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('installupdatex' => 1)),
|
||||
get_string('updateavailableinstallall', 'core_admin', $numinstallable),
|
||||
'post',
|
||||
array('class' => 'singlebutton updateavailableinstallall')
|
||||
);
|
||||
if ($numupdatable) {
|
||||
$installableupdates = $pluginman->filter_installable($pluginman->available_updates());
|
||||
if ($installableupdates) {
|
||||
$out .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('installupdatex' => 1)),
|
||||
get_string('updateavailableinstallall', 'core_admin', count($installableupdates)),
|
||||
'post',
|
||||
array('class' => 'singlebutton updateavailableinstallall')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$out .= html_writer::div($infoall, 'info info-all').
|
||||
@@ -1910,7 +1907,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
'infos'
|
||||
);
|
||||
|
||||
if ($pluginman->is_remote_plugin_installable($updateinfo->component, $updateinfo->version, $reason, false)) {
|
||||
if ($pluginman->is_remote_plugin_installable($updateinfo->component, $updateinfo->version, $reason)) {
|
||||
$box .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('installupdate' => $updateinfo->component,
|
||||
'installupdateversion' => $updateinfo->version)),
|
||||
@@ -2130,16 +2127,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$output .= $this->container_start('upgradekeyreq');
|
||||
$output .= $this->heading(get_string('upgradekeyreq', 'core_admin'));
|
||||
$output .= html_writer::start_tag('form', array('method' => 'POST', 'action' => $url));
|
||||
$output .= html_writer::empty_tag('input', [
|
||||
'name' => 'upgradekey',
|
||||
'type' => 'password',
|
||||
'class' => 'form-control w-auto',
|
||||
]);
|
||||
$output .= html_writer::empty_tag('input', [
|
||||
'type' => 'submit',
|
||||
'value' => get_string('submit'),
|
||||
'class' => 'btn btn-primary mt-3',
|
||||
]);
|
||||
$output .= html_writer::empty_tag('input', array('name' => 'upgradekey', 'type' => 'password'));
|
||||
$output .= html_writer::empty_tag('input', array('value' => get_string('submit'), 'type' => 'submit'));
|
||||
$output .= html_writer::end_tag('form');
|
||||
$output .= $this->container_end();
|
||||
$output .= $this->footer();
|
||||
|
||||
@@ -271,8 +271,7 @@ if ($roleid) {
|
||||
foreach ($assignableroles as $roleid => $notused) {
|
||||
$roleusers = '';
|
||||
if (0 < $assigncounts[$roleid] && $assigncounts[$roleid] <= MAX_USERS_TO_LIST_PER_ROLE) {
|
||||
$userfieldsapi = \core_user\fields::for_name();
|
||||
$userfields = 'u.id, u.username' . $userfieldsapi->get_sql('u')->selects;
|
||||
$userfields = 'u.id, u.username, ' . get_all_user_name_fields(true, 'u');
|
||||
$roleusers = get_role_users($roleid, $context, false, $userfields);
|
||||
if (!empty($roleusers)) {
|
||||
$strroleusers = array();
|
||||
|
||||
@@ -163,8 +163,7 @@ if (!empty($capabilities)) {
|
||||
echo html_writer::start_tag('div', array('class'=>'submit_buttons'));
|
||||
$attrs = array('type'=>'submit', 'name'=>'savechanges', 'value'=>get_string('savechanges'), 'class'=>'btn btn-primary');
|
||||
echo html_writer::empty_tag('input', $attrs);
|
||||
$attrs = array('type' => 'submit', 'name' => 'cancel', 'value' => get_string('cancel'),
|
||||
'class' => 'btn btn-secondary ml-1');
|
||||
$attrs = array('type'=>'submit', 'name'=>'cancel', 'value'=>get_string('cancel'), 'class' => 'btn btn-secondary');
|
||||
echo html_writer::empty_tag('input', $attrs);
|
||||
echo html_writer::end_tag('div');
|
||||
echo html_writer::end_tag('div');
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_role;
|
||||
|
||||
use core_role_preset;
|
||||
|
||||
/**
|
||||
* Role XML presets test case.
|
||||
*
|
||||
* @package core_role
|
||||
* @category test
|
||||
* @category phpunit
|
||||
* @copyright 2013 Petr Skoda {@link http://skodak.org}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class preset_test extends \advanced_testcase {
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
class core_role_preset_testcase extends advanced_testcase {
|
||||
public function test_xml() {
|
||||
global $DB;
|
||||
|
||||
@@ -61,7 +61,7 @@ class preset_test extends \advanced_testcase {
|
||||
FROM {role_capabilities}
|
||||
WHERE contextid = :syscontext AND roleid = :roleid
|
||||
ORDER BY capability ASC",
|
||||
array('syscontext' => \context_system::instance()->id, 'roleid' => $role->id));
|
||||
array('syscontext'=>context_system::instance()->id, 'roleid'=>$role->id));
|
||||
|
||||
foreach ($capabilities as $cap) {
|
||||
$this->assertEquals($cap->permission, $info['permissions'][$cap->capability]);
|
||||
|
||||
@@ -22,17 +22,16 @@
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_role\privacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_role\privacy\provider;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\writer;
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\transform;
|
||||
use tool_cohortroles\api;
|
||||
use core_privacy\local\request\approved_userlist;
|
||||
use \core_role\privacy\provider;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\tests\provider_testcase;
|
||||
use \core_privacy\local\request\transform;
|
||||
use \tool_cohortroles\api;
|
||||
use \core_privacy\local\request\approved_userlist;
|
||||
|
||||
/**
|
||||
* Privacy test for core_role
|
||||
@@ -40,7 +39,7 @@ use core_privacy\local\request\approved_userlist;
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider_test extends provider_testcase {
|
||||
class core_role_privacy_testcase extends provider_testcase {
|
||||
/**
|
||||
* Test to check export_user_preferences.
|
||||
* returns user preferences data.
|
||||
@@ -26,7 +26,7 @@ require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
admin_externalpage_setup('searchareas');
|
||||
|
||||
$areaid = optional_param('areaid', null, PARAM_ALPHANUMEXT);
|
||||
$areaid = optional_param('areaid', null, PARAM_ALPHAEXT);
|
||||
$action = optional_param('action', null, PARAM_ALPHA);
|
||||
$indexingenabled = \core_search\manager::is_indexing_enabled(); // This restricts many of the actions on this page.
|
||||
|
||||
|
||||
@@ -144,20 +144,5 @@ if ($hassiteconfig && \core_analytics\manager::is_analytics_enabled()) {
|
||||
$settings->add(new admin_setting_configduration('analytics/modeltimelimit', new lang_string('modeltimelimit', 'analytics'),
|
||||
new lang_string('modeltimelimitinfo', 'analytics'), 20 * MINSECS));
|
||||
|
||||
$options = array(
|
||||
0 => new lang_string('neverdelete', 'analytics'),
|
||||
1000 => new lang_string('numdays', '', 1000),
|
||||
365 => new lang_string('numdays', '', 365),
|
||||
180 => new lang_string('numdays', '', 180),
|
||||
150 => new lang_string('numdays', '', 150),
|
||||
120 => new lang_string('numdays', '', 120),
|
||||
90 => new lang_string('numdays', '', 90),
|
||||
60 => new lang_string('numdays', '', 60),
|
||||
35 => new lang_string('numdays', '', 35));
|
||||
$settings->add(new admin_setting_configselect('analytics/calclifetime',
|
||||
new lang_string('calclifetime', 'analytics'),
|
||||
new lang_string('configlcalclifetime', 'analytics'), 35, $options));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +269,7 @@ preferences,moodle|/user/preferences.php|t/preferences',
|
||||
$setting = new admin_setting_configcheckbox('cachejs', new lang_string('cachejs', 'admin'), new lang_string('cachejs_help', 'admin'), 1);
|
||||
$setting->set_updatedcallback('js_reset_all_caches');
|
||||
$temp->add($setting);
|
||||
$temp->add(new admin_setting_configcheckbox('modchooserdefault', new lang_string('modchooserdefault', 'admin'), new lang_string('configmodchooserdefault', 'admin'), 1));
|
||||
$ADMIN->add('appearance', $temp);
|
||||
|
||||
// Link to tag management interface.
|
||||
|
||||
@@ -57,14 +57,20 @@ if (($hassiteconfig || has_any_capability(array(
|
||||
new lang_string('badgesalt_desc', 'badges'),
|
||||
'badges' . $SITE->timecreated, PARAM_ALPHANUM));
|
||||
|
||||
$backpacks = badges_get_site_backpacks();
|
||||
$choices = array();
|
||||
$defaultchoice = 0;
|
||||
foreach ($backpacks as $backpack) {
|
||||
$choices[$backpack->id] = $backpack->backpackweburl;
|
||||
if ($backpack->backpackweburl == BADGRIO_BACKPACKWEBURL) {
|
||||
$defaultchoice = $backpack->id;
|
||||
}
|
||||
}
|
||||
|
||||
$globalsettings->add(new admin_setting_configcheckbox('badges_allowcoursebadges',
|
||||
new lang_string('allowcoursebadges', 'badges'),
|
||||
new lang_string('allowcoursebadges_desc', 'badges'), 1));
|
||||
|
||||
$globalsettings->add(new admin_setting_configcheckbox('badges_allowexternalbackpack',
|
||||
new lang_string('allowexternalbackpack', 'badges'),
|
||||
new lang_string('allowexternalbackpack_desc', 'badges'), 1));
|
||||
|
||||
$ADMIN->add('badges', $globalsettings);
|
||||
|
||||
$ADMIN->add('badges',
|
||||
@@ -91,6 +97,19 @@ if (($hassiteconfig || has_any_capability(array(
|
||||
array('moodle/badges:createbadge'), empty($CFG->enablebadges)
|
||||
)
|
||||
);
|
||||
$backpacksettings = new admin_settingpage('backpacksettings', new lang_string('backpacksettings', 'badges'),
|
||||
array('moodle/badges:manageglobalsettings'), empty($CFG->enablebadges));
|
||||
|
||||
$backpacksettings->add(new admin_setting_configcheckbox('badges_allowexternalbackpack',
|
||||
new lang_string('allowexternalbackpack', 'badges'),
|
||||
new lang_string('allowexternalbackpack_desc', 'badges'), 1));
|
||||
|
||||
$backpacksettings->add(new admin_setting_configselect('badges_site_backpack',
|
||||
new lang_string('sitebackpack', 'badges'),
|
||||
new lang_string('sitebackpack_help', 'badges'),
|
||||
$defaultchoice, $choices));
|
||||
|
||||
$ADMIN->add('badges', $backpacksettings);
|
||||
|
||||
$ADMIN->add('badges',
|
||||
new admin_externalpage('managebackpacks',
|
||||
|
||||
@@ -139,13 +139,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
new lang_string('coursehelpshowgrades'), 1, array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/showreports', new lang_string('showreports'), '', 0,
|
||||
array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/showactivitydates',
|
||||
new lang_string('showactivitydates'),
|
||||
new lang_string('showactivitydates_help'), 1, [
|
||||
0 => new lang_string('no'),
|
||||
1 => new lang_string('yes')
|
||||
]
|
||||
));
|
||||
|
||||
// Files and uploads.
|
||||
$temp->add(new admin_setting_heading('filesanduploadshdr', new lang_string('filesanduploads'), ''));
|
||||
@@ -170,15 +163,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/enablecompletion', new lang_string('completion', 'completion'),
|
||||
new lang_string('enablecompletion_help', 'completion'), 1, array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
|
||||
|
||||
// Display completion conditions.
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/showcompletionconditions',
|
||||
new lang_string('showcompletionconditions', 'completion'),
|
||||
new lang_string('showcompletionconditions_help', 'completion'), 1, [
|
||||
0 => new lang_string('no'),
|
||||
1 => new lang_string('yes')
|
||||
]
|
||||
));
|
||||
|
||||
// Groups.
|
||||
$temp->add(new admin_setting_heading('groups', new lang_string('groups', 'group'), ''));
|
||||
$choices = array();
|
||||
@@ -343,11 +327,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
|
||||
// Import defaults section.
|
||||
$temp->add(new admin_setting_heading('importsettings', new lang_string('importsettings', 'backup'), ''));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock(
|
||||
'backup/backup_import_permissions',
|
||||
new lang_string('generalpermissions', 'backup'),
|
||||
new lang_string('configgeneralpermissions', 'backup'),
|
||||
array('value' => 0, 'locked' => 0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), array('value'=>1, 'locked'=>0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_blocks', new lang_string('generalblocks','backup'), new lang_string('configgeneralblocks','backup'), array('value'=>1, 'locked'=>0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_filters', new lang_string('generalfilters','backup'), new lang_string('configgeneralfilters','backup'), array('value'=>1, 'locked'=>0)));
|
||||
@@ -522,9 +501,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_role_assignments',
|
||||
new lang_string('generalroleassignments', 'backup'),
|
||||
new lang_string('configrestoreroleassignments', 'backup'), array('value' => 1, 'locked' => 0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_permissions',
|
||||
new lang_string('generalpermissions', 'backup'),
|
||||
new lang_string('configrestorepermissions', 'backup'), array('value' => 1, 'locked' => 0)));
|
||||
$temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_activities',
|
||||
new lang_string('generalactivities', 'backup'),
|
||||
new lang_string('configrestoreactivities', 'backup'), array('value' => 1, 'locked' => 0)));
|
||||
|
||||
@@ -38,8 +38,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
// "debugging" settingpage
|
||||
$temp = new admin_settingpage('debugging', new lang_string('debugging', 'admin'));
|
||||
$temp->add(new admin_setting_special_debug());
|
||||
$temp->add(new admin_setting_configcheckbox('debugdisplay', new lang_string('debugdisplay', 'admin'),
|
||||
new lang_string('configdebugdisplay', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('debugdisplay', new lang_string('debugdisplay', 'admin'), new lang_string('configdebugdisplay', 'admin'), ini_get_bool('display_errors')));
|
||||
$temp->add(new admin_setting_configcheckbox('perfdebug', new lang_string('perfdebug', 'admin'), new lang_string('configperfdebug', 'admin'), '7', '15', '7'));
|
||||
$temp->add(new admin_setting_configcheckbox('debugstringids', new lang_string('debugstringids', 'admin'), new lang_string('debugstringids_desc', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configselect('debugsqltrace',
|
||||
|
||||
+50
-14
@@ -231,9 +231,6 @@ if ($hassiteconfig) {
|
||||
array('0' => new lang_string('none'), '1' => new lang_string('allfiles'), '2' => new lang_string('htmlfilesonly')));
|
||||
$items[] = new admin_setting_configcheckbox('filtermatchoneperpage', new lang_string('filtermatchoneperpage', 'admin'), new lang_string('configfiltermatchoneperpage', 'admin'), 0);
|
||||
$items[] = new admin_setting_configcheckbox('filtermatchonepertext', new lang_string('filtermatchonepertext', 'admin'), new lang_string('configfiltermatchonepertext', 'admin'), 0);
|
||||
$items[] = new admin_setting_configcheckbox('filternavigationwithsystemcontext',
|
||||
new lang_string('filternavigationwithsystemcontext', 'admin'),
|
||||
new lang_string('configfilternavigationwithsystemcontext', 'admin'), 0);
|
||||
foreach ($items as $item) {
|
||||
$item->set_updatedcallback('reset_text_filters_cache');
|
||||
$temp->add($item);
|
||||
@@ -308,13 +305,6 @@ if ($hassiteconfig) {
|
||||
$temp->add(new admin_setting_managedataformats());
|
||||
$ADMIN->add('dataformatsettings', $temp);
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
|
||||
core_collator::asort_objects_by_property($plugins, 'displayname');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\dataformat $plugin */
|
||||
$plugin->load_settings($ADMIN, 'dataformatsettings', $hassiteconfig);
|
||||
}
|
||||
|
||||
//== Portfolio settings ==
|
||||
require_once($CFG->libdir. '/portfoliolib.php');
|
||||
$catname = new lang_string('portfolios', 'portfolio');
|
||||
@@ -411,6 +401,55 @@ if ($hassiteconfig) {
|
||||
/** @var \core\plugininfo\repository $plugin */
|
||||
$plugin->load_settings($ADMIN, 'repositorysettings', $hassiteconfig);
|
||||
}
|
||||
|
||||
/// Web services
|
||||
$ADMIN->add('modules', new admin_category('webservicesettings', new lang_string('webservices', 'webservice')));
|
||||
|
||||
/// overview page
|
||||
$temp = new admin_settingpage('webservicesoverview', new lang_string('webservicesoverview', 'webservice'));
|
||||
$temp->add(new admin_setting_webservicesoverview());
|
||||
$ADMIN->add('webservicesettings', $temp);
|
||||
//API documentation
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('webservicedocumentation', new lang_string('wsdocapi', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/documentation.php", 'moodle/site:config', false));
|
||||
/// manage service
|
||||
$temp = new admin_settingpage('externalservices', new lang_string('externalservices', 'webservice'));
|
||||
$temp->add(new admin_setting_heading('manageserviceshelpexplaination', new lang_string('information', 'webservice'), new lang_string('servicehelpexplanation', 'webservice')));
|
||||
$temp->add(new admin_setting_manageexternalservices());
|
||||
$ADMIN->add('webservicesettings', $temp);
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalservice', new lang_string('editaservice', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/service.php", 'moodle/site:config', true));
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalservicefunctions', new lang_string('externalservicefunctions', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/service_functions.php", 'moodle/site:config', true));
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalserviceusers', new lang_string('externalserviceusers', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/service_users.php", 'moodle/site:config', true));
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalserviceusersettings', new lang_string('serviceusersettings', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/service_user_settings.php", 'moodle/site:config', true));
|
||||
/// manage protocol page link
|
||||
$temp = new admin_settingpage('webserviceprotocols', new lang_string('manageprotocols', 'webservice'));
|
||||
$temp->add(new admin_setting_managewebserviceprotocols());
|
||||
if (empty($CFG->enablewebservices)) {
|
||||
$temp->add(new admin_setting_heading('webservicesaredisabled', '', new lang_string('disabledwarning', 'webservice')));
|
||||
}
|
||||
|
||||
// We cannot use $OUTPUT this early, doing so means that we lose the ability
|
||||
// to set the page layout on all admin pages.
|
||||
// $wsdoclink = $OUTPUT->doc_link('How_to_get_a_security_key');
|
||||
$url = new moodle_url(get_docs_url('How_to_get_a_security_key'));
|
||||
$wsdoclink = html_writer::tag('a', new lang_string('supplyinfo', 'webservice'), array('href'=>$url));
|
||||
$temp->add(new admin_setting_configcheckbox('enablewsdocumentation', new lang_string('enablewsdocumentation',
|
||||
'admin'), new lang_string('configenablewsdocumentation', 'admin', $wsdoclink), false));
|
||||
$ADMIN->add('webservicesettings', $temp);
|
||||
/// links to protocol pages
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('webservice');
|
||||
core_collator::asort_objects_by_property($plugins, 'displayname');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\webservice $plugin */
|
||||
$plugin->load_settings($ADMIN, 'webservicesettings', $hassiteconfig);
|
||||
}
|
||||
/// manage token page link
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('addwebservicetoken', new lang_string('managetokens', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/tokens.php", 'moodle/site:config', true));
|
||||
$temp = new admin_settingpage('webservicetokens', new lang_string('managetokens', 'webservice'));
|
||||
$temp->add(new admin_setting_managewebservicetokens());
|
||||
if (empty($CFG->enablewebservices)) {
|
||||
$temp->add(new admin_setting_heading('webservicesaredisabled', '', new lang_string('disabledwarning', 'webservice')));
|
||||
}
|
||||
$ADMIN->add('webservicesettings', $temp);
|
||||
}
|
||||
|
||||
// Question type settings
|
||||
@@ -492,10 +531,7 @@ if ($hassiteconfig && !empty($CFG->enableplagiarism)) {
|
||||
$plugin->load_settings($ADMIN, 'plagiarism', $hassiteconfig);
|
||||
}
|
||||
}
|
||||
|
||||
// Comments report, note this page is really just a means to delete comments so check that.
|
||||
$ADMIN->add('reports', new admin_externalpage('comments', new lang_string('comments'), $CFG->wwwroot . '/comment/index.php',
|
||||
'moodle/comment:delete'));
|
||||
$ADMIN->add('reports', new admin_externalpage('comments', new lang_string('comments'), $CFG->wwwroot.'/comment/', 'moodle/site:viewreports'));
|
||||
|
||||
// Course reports settings
|
||||
if ($hassiteconfig) {
|
||||
|
||||
@@ -38,8 +38,6 @@ if ($hassiteconfig) {
|
||||
new lang_string('pathtodot_help', 'admin'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('pathtogs', new lang_string('pathtogs', 'admin'),
|
||||
new lang_string('pathtogs_help', 'admin'), '/usr/bin/gs'));
|
||||
$temp->add(new admin_setting_configexecutable('pathtopdftoppm', new lang_string('pathtopdftoppm', 'admin'),
|
||||
new lang_string('pathtopdftoppm_help', 'admin'), ''));
|
||||
$temp->add(new admin_setting_configexecutable('pathtopython', new lang_string('pathtopython', 'admin'),
|
||||
new lang_string('pathtopythondesc', 'admin'), ''));
|
||||
$ADMIN->add('server', $temp);
|
||||
@@ -75,22 +73,6 @@ if ($hassiteconfig) {
|
||||
$temp->add(new admin_setting_configduration('sessiontimeout', new lang_string('sessiontimeout', 'admin'),
|
||||
new lang_string('configsessiontimeout', 'admin'), 8 * 60 * 60));
|
||||
|
||||
$sessiontimeoutwarning = new admin_setting_configduration('sessiontimeoutwarning',
|
||||
new lang_string('sessiontimeoutwarning', 'admin'),
|
||||
new lang_string('configsessiontimeoutwarning', 'admin'), 20 * 60);
|
||||
|
||||
$sessiontimeoutwarning->set_validate_function(function(int $value): string {
|
||||
global $CFG;
|
||||
// Check sessiontimeoutwarning is less than sessiontimeout.
|
||||
if ($CFG->sessiontimeout <= $value) {
|
||||
return get_string('configsessiontimeoutwarningcheck', 'admin');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
$temp->add($sessiontimeoutwarning);
|
||||
|
||||
$temp->add(new admin_setting_configtext('sessioncookie', new lang_string('sessioncookie', 'admin'),
|
||||
new lang_string('configsessioncookie', 'admin'), '', PARAM_ALPHANUM));
|
||||
$temp->add(new admin_setting_configtext('sessioncookiepath', new lang_string('sessioncookiepath', 'admin'),
|
||||
@@ -546,67 +528,4 @@ if ($hassiteconfig) {
|
||||
new lang_string('updatenotifybuilds_desc', 'core_admin'), 0));
|
||||
$ADMIN->add('server', $temp);
|
||||
}
|
||||
|
||||
// Web services.
|
||||
$ADMIN->add('server', new admin_category('webservicesettings', new lang_string('webservices', 'webservice')));
|
||||
|
||||
// Web services > Overview.
|
||||
$temp = new admin_settingpage('webservicesoverview', new lang_string('webservicesoverview', 'webservice'));
|
||||
$temp->add(new admin_setting_webservicesoverview());
|
||||
$ADMIN->add('webservicesettings', $temp);
|
||||
|
||||
// Web services > API documentation.
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('webservicedocumentation', new lang_string('wsdocapi', 'webservice'),
|
||||
"{$CFG->wwwroot}/{$CFG->admin}/webservice/documentation.php", 'moodle/site:config', false));
|
||||
|
||||
// Web services > External services.
|
||||
$temp = new admin_settingpage('externalservices', new lang_string('externalservices', 'webservice'));
|
||||
|
||||
$temp->add(new admin_setting_heading('manageserviceshelpexplaination', new lang_string('information', 'webservice'),
|
||||
new lang_string('servicehelpexplanation', 'webservice')));
|
||||
|
||||
$temp->add(new admin_setting_manageexternalservices());
|
||||
|
||||
$ADMIN->add('webservicesettings', $temp);
|
||||
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalservice', new lang_string('editaservice', 'webservice'),
|
||||
"{$CFG->wwwroot}/{$CFG->admin}/webservice/service.php", 'moodle/site:config', true));
|
||||
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalservicefunctions',
|
||||
new lang_string('externalservicefunctions', 'webservice'), "{$CFG->wwwroot}/{$CFG->admin}/webservice/service_functions.php",
|
||||
'moodle/site:config', true));
|
||||
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalserviceusers',
|
||||
new lang_string('externalserviceusers', 'webservice'), "{$CFG->wwwroot}/{$CFG->admin}/webservice/service_users.php",
|
||||
'moodle/site:config', true));
|
||||
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('externalserviceusersettings',
|
||||
new lang_string('serviceusersettings', 'webservice'), "{$CFG->wwwroot}/{$CFG->admin}/webservice/service_user_settings.php",
|
||||
'moodle/site:config', true));
|
||||
|
||||
// Web services > Manage protocols.
|
||||
$temp = new admin_settingpage('webserviceprotocols', new lang_string('manageprotocols', 'webservice'));
|
||||
$temp->add(new admin_setting_managewebserviceprotocols());
|
||||
if (empty($CFG->enablewebservices)) {
|
||||
$temp->add(new admin_setting_heading('webservicesaredisabled', '', new lang_string('disabledwarning', 'webservice')));
|
||||
}
|
||||
|
||||
// We cannot use $OUTPUT->doc_link() this early, we would lose the ability to set the page layout on all admin pages.
|
||||
$url = new moodle_url(get_docs_url('How_to_get_a_security_key'));
|
||||
$wsdoclink = html_writer::link($url, new lang_string('supplyinfo', 'webservice'), ['target' => '_blank']);
|
||||
$temp->add(new admin_setting_configcheckbox('enablewsdocumentation', new lang_string('enablewsdocumentation', 'admin'),
|
||||
new lang_string('configenablewsdocumentation', 'admin', $wsdoclink), false));
|
||||
|
||||
$ADMIN->add('webservicesettings', $temp);
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('webservice');
|
||||
core_collator::asort_objects_by_property($plugins, 'displayname');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\webservice $plugin */
|
||||
$plugin->load_settings($ADMIN, 'webservicesettings', $hassiteconfig);
|
||||
}
|
||||
|
||||
// Web services > Manage tokens.
|
||||
$ADMIN->add('webservicesettings', new admin_externalpage('webservicetokens', new lang_string('managetokens', 'webservice'),
|
||||
new moodle_url('/admin/webservice/tokens.php')));
|
||||
}
|
||||
|
||||
+19
-40
@@ -87,14 +87,6 @@ if ($hassiteconfig
|
||||
$choices['1'] = new lang_string('trackforumsyes');
|
||||
$temp->add(new admin_setting_configselect('defaultpreference_trackforums', new lang_string('trackforums'),
|
||||
'', 0, $choices));
|
||||
|
||||
$choices = [];
|
||||
$choices[\core_contentbank\content::VISIBILITY_PUBLIC] = new lang_string('visibilitychoicepublic', 'core_contentbank');
|
||||
$choices[\core_contentbank\content::VISIBILITY_UNLISTED] = new lang_string('visibilitychoiceunlisted', 'core_contentbank');
|
||||
$temp->add(new admin_setting_configselect('defaultpreference_core_contentbank_visibility',
|
||||
new lang_string('visibilitypref', 'core_contentbank'),
|
||||
new lang_string('visibilitypref_help', 'core_contentbank'),
|
||||
\core_contentbank\content::VISIBILITY_PUBLIC, $choices));
|
||||
}
|
||||
$ADMIN->add('accounts', $temp);
|
||||
|
||||
@@ -196,6 +188,12 @@ if ($hassiteconfig
|
||||
'country' => new lang_string('country'),
|
||||
'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'),
|
||||
'timezone' => new lang_string('timezone'),
|
||||
'webpage' => new lang_string('webpage'),
|
||||
'icqnumber' => new lang_string('icqnumber'),
|
||||
'skypeid' => new lang_string('skypeid'),
|
||||
'yahooid' => new lang_string('yahooid'),
|
||||
'aimid' => new lang_string('aimid'),
|
||||
'msnid' => new lang_string('msnid'),
|
||||
'firstaccess' => new lang_string('firstaccess'),
|
||||
'lastaccess' => new lang_string('lastaccess'),
|
||||
'lastip' => new lang_string('lastip'),
|
||||
@@ -208,40 +206,21 @@ if ($hassiteconfig
|
||||
// with moodle/site:viewuseridentity).
|
||||
// Options include fields from the user table that might be helpful to
|
||||
// distinguish when adding or listing users ('I want to add the John
|
||||
// Smith from Science faculty') and any custom profile fields.
|
||||
// Smith from Science faculty').
|
||||
// Custom user profile fields are not currently supported.
|
||||
$temp->add(new admin_setting_configmulticheckbox('showuseridentity',
|
||||
new lang_string('showuseridentity', 'admin'),
|
||||
new lang_string('showuseridentity_desc', 'admin'), ['email' => 1],
|
||||
function() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/user/profile/lib.php');
|
||||
|
||||
// Basic fields available in user table.
|
||||
$fields = [
|
||||
'username' => new lang_string('username'),
|
||||
'idnumber' => new lang_string('idnumber'),
|
||||
'email' => new lang_string('email'),
|
||||
'phone1' => new lang_string('phone1'),
|
||||
'phone2' => new lang_string('phone2'),
|
||||
'department' => new lang_string('department'),
|
||||
'institution' => new lang_string('institution'),
|
||||
'city' => new lang_string('city'),
|
||||
'country' => new lang_string('country'),
|
||||
];
|
||||
|
||||
// Custom profile fields.
|
||||
$profilefields = profile_get_custom_fields();
|
||||
foreach ($profilefields as $field) {
|
||||
// Only reasonable-length text fields can be used as identity fields.
|
||||
if ($field->param2 > 255 || $field->datatype != 'text') {
|
||||
continue;
|
||||
}
|
||||
$fields['profile_field_' . $field->shortname] = format_string($field->name, true,
|
||||
['context' => context_system::instance()]) . ' *';
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}));
|
||||
new lang_string('showuseridentity_desc', 'admin'), array('email' => 1), array(
|
||||
'username' => new lang_string('username'),
|
||||
'idnumber' => new lang_string('idnumber'),
|
||||
'email' => new lang_string('email'),
|
||||
'phone1' => new lang_string('phone1'),
|
||||
'phone2' => new lang_string('phone2'),
|
||||
'department' => new lang_string('department'),
|
||||
'institution' => new lang_string('institution'),
|
||||
'city' => new lang_string('city'),
|
||||
'country' => new lang_string('country'),
|
||||
)));
|
||||
$setting = new admin_setting_configtext('fullnamedisplay', new lang_string('fullnamedisplay', 'admin'),
|
||||
new lang_string('configfullnamedisplay', 'admin'), 'language', PARAM_TEXT, 50);
|
||||
$setting->set_force_ltr(true);
|
||||
|
||||
@@ -48,6 +48,6 @@
|
||||
</div>
|
||||
<input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
|
||||
{{#haspreviewconfig}}
|
||||
<input type="button" id="{{id}}_preview" value="{{#cleanstr}}preview{{/cleanstr}}" class="admin_colourpicker_preview">
|
||||
<input type="button" id="{{id}}_preview" value={{#quote}}{{#str}}preview{{/str}}{{/quote}} class="admin_colourpicker_preview">
|
||||
{{/haspreviewconfig}}
|
||||
</div>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_admin/admin_setting_encryptedpassword
|
||||
|
||||
Admin encrypted password template.
|
||||
|
||||
Context variables required for this template:
|
||||
* name - form element name
|
||||
* set - whether it is set or empty
|
||||
* id - element id
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "test",
|
||||
"id": "test0",
|
||||
"set": true
|
||||
}
|
||||
}}
|
||||
<div class="core_admin_encryptedpassword" data-encryptedpasswordid="{{ id }}"
|
||||
{{#novalue}}data-novalue="y"{{/novalue}}>
|
||||
{{#set}}
|
||||
<span>{{# str }} encryptedpassword_set, admin {{/ str }}</span>
|
||||
{{/set}}
|
||||
{{^set}}
|
||||
<a href="#" title="{{# str }} encryptedpassword_edit, admin {{/ str }}">
|
||||
<span>{{# str }} novalueclicktoset, form {{/ str }}</span>
|
||||
{{# pix }} t/passwordunmask-edit, core, {{# str }} passwordunmaskedithint, form {{/ str }}{{/ pix }}
|
||||
</a>
|
||||
{{/set}}
|
||||
<input style="display: none" type="password" name="{{name}}" disabled>
|
||||
{{!
|
||||
Using buttons instead of links here allows them to be connected to the label, so the button
|
||||
works if you click the label.
|
||||
}}
|
||||
{{#set}}
|
||||
<button type="button" id="{{id}}" title="{{# str }} encryptedpassword_edit, admin {{/ str }}" class="btn btn-link" data-editbutton>
|
||||
{{# pix }} t/passwordunmask-edit, core, {{/ pix }}
|
||||
</button>
|
||||
{{/set}}
|
||||
<button type="button" style="display: none" title="{{# str }} cancel {{/ str }}" class="btn btn-link" data-cancelbutton>
|
||||
<i class="icon fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{#js}}
|
||||
require(['core_form/encryptedpassword'], function(encryptedpassword) {
|
||||
new encryptedpassword.EncryptedPassword("{{ id }}");
|
||||
});
|
||||
{{/js}}
|
||||
@@ -53,8 +53,11 @@ class behat_admin extends behat_base {
|
||||
}
|
||||
|
||||
foreach ($data as $label => $value) {
|
||||
// Navigate straight to the search results fo rthis label.
|
||||
$this->execute('behat_general::i_visit', ["/admin/search.php?query=" . urlencode($label)]);
|
||||
$this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]);
|
||||
|
||||
// Search by label.
|
||||
$this->execute('behat_forms::i_set_the_field_to', [get_string('search'), $label]);
|
||||
$this->execute("behat_forms::press_button", get_string('search', 'admin'));
|
||||
|
||||
// Admin settings does not use the same DOM structure than other moodle forms
|
||||
// but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
|
||||
@@ -86,7 +89,7 @@ class behat_admin extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified site settings. A table with | config | value | (optional)plugin | (optional)encrypted | is expected.
|
||||
* Sets the specified site settings. A table with | config | value | (optional)plugin | is expected.
|
||||
*
|
||||
* @Given /^the following config values are set as admin:$/
|
||||
* @param TableNode $table
|
||||
@@ -100,20 +103,11 @@ class behat_admin extends behat_base {
|
||||
foreach ($data as $config => $value) {
|
||||
// Default plugin value is null.
|
||||
$plugin = null;
|
||||
$encrypted = false;
|
||||
|
||||
if (is_array($value)) {
|
||||
$plugin = $value[1];
|
||||
if (array_key_exists(2, $value)) {
|
||||
$encrypted = $value[2] === 'encrypted';
|
||||
}
|
||||
$value = $value[0];
|
||||
}
|
||||
|
||||
if ($encrypted) {
|
||||
$value = \core\encryption::encrypt($value);
|
||||
}
|
||||
|
||||
set_config($config, $value, $plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
@core @core_admin
|
||||
Feature: An administrator can browse user accounts
|
||||
In order to find the user accounts I am looking for
|
||||
As an admin
|
||||
I can browse users and see their basic information
|
||||
|
||||
Background:
|
||||
Given the following "custom profile fields" exist:
|
||||
| datatype | shortname | name |
|
||||
| text | frog | Favourite frog |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | department | profile_field_frog | firstnamephonetic |
|
||||
| user1 | User | One | one@example.com | Attack | Kermit | Yewzer |
|
||||
| user2 | User | Two | two@example.com | Defence | Tree | Yoozare |
|
||||
And I log in as "admin"
|
||||
|
||||
Scenario: User accounts display default fields
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
# Name field always present, email field is default for showidentity.
|
||||
Then the following should exist in the "users" table:
|
||||
| First name / Surname | Email address |
|
||||
| User One | one@example.com |
|
||||
| User Two | two@example.com |
|
||||
# Should not see other identity fields or non-default name fields.
|
||||
And I should not see "Department" in the "table" "css_element"
|
||||
And I should not see "Attack"
|
||||
And I should not see "Favourite frog" in the "table" "css_element"
|
||||
And I should not see "Kermit"
|
||||
And I should not see "First name - phonetic" in the "table" "css_element"
|
||||
And I should not see "Yoozare"
|
||||
|
||||
Scenario: User accounts with extra name fields
|
||||
Given the following config values are set as admin:
|
||||
| alternativefullnameformat | firstnamephonetic lastname |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "users" table:
|
||||
| First name - phonetic / Surname | Email address |
|
||||
| Yewzer One | one@example.com |
|
||||
| Yoozare Two | two@example.com |
|
||||
|
||||
Scenario: User accounts with specified identity fields
|
||||
Given the following config values are set as admin:
|
||||
| showuseridentity | department,profile_field_frog |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
Then the following should exist in the "users" table:
|
||||
| First name / Surname | Favourite frog | Department |
|
||||
| User One | Kermit | Attack |
|
||||
| User Two | Tree | Defence |
|
||||
And I should not see "Email address" in the "table" "css_element"
|
||||
And I should not see "one@example.com"
|
||||
|
||||
Scenario: Sort user accounts by custom profile field
|
||||
Given the following config values are set as admin:
|
||||
| showuseridentity | profile_field_frog |
|
||||
When I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
And I follow "Favourite frog"
|
||||
Then "Kermit" "text" should appear before "Tree" "text"
|
||||
And I follow "Favourite frog"
|
||||
Then "Tree" "text" should appear before "Kermi" "text"
|
||||
@@ -5,23 +5,15 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
|
||||
I need to filter the users account list using different filter
|
||||
|
||||
Background:
|
||||
Given the following "custom profile fields" exist:
|
||||
| datatype | shortname | name |
|
||||
| text | frog | Favourite frog |
|
||||
| text | undead | Type of undead |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | auth | confirmed | lastip | institution | department | profile_field_frog | profile_field_undead |
|
||||
| user1 | User | One | one@example.com | manual | 0 | 127.0.1.1 | moodle | red | Kermit | |
|
||||
| user2 | User | Two | two@example.com | ldap | 1 | 0.0.0.0 | moodle | blue | Mr Toad | Zombie |
|
||||
| user3 | User | Three | three@example.com | manual | 1 | 0.0.0.0 | | | | |
|
||||
| user4 | User | Four | four@example.com | ldap | 0 | 127.0.1.2 | | | | |
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email | auth | confirmed | lastip | institution | department |
|
||||
| user1 | User | One | one@example.com | manual | 0 | 127.0.1.1 | moodle | red |
|
||||
| user2 | User | Two | two@example.com | ldap | 1 | 0.0.0.0 | moodle | blue |
|
||||
| user3 | User | Three | three@example.com | manual | 1 | 0.0.0.0 | | |
|
||||
| user4 | User | Four | four@example.com | ldap | 0 | 127.0.1.2 | | |
|
||||
And the following "cohorts" exist:
|
||||
| name | idnumber |
|
||||
| Cohort 1 | CH1 |
|
||||
And the following "cohort members" exist:
|
||||
| user | cohort |
|
||||
| user2 | CH1 |
|
||||
| user3 | CH1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
@@ -31,6 +23,8 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
|
||||
| user2 | C1 | student |
|
||||
| user3 | C1 | student |
|
||||
And I log in as "admin"
|
||||
And I add "User Two (two@example.com)" user to "CH1" cohort members
|
||||
And I add "User Three (three@example.com)" user to "CH1" cohort members
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
|
||||
Scenario: Filter user accounts by role and cohort
|
||||
@@ -122,20 +116,3 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
|
||||
And I press "Add filter"
|
||||
And I should see "User One"
|
||||
And I should not see "User Two"
|
||||
|
||||
Scenario: Filter users by custom profile field (specific or any)
|
||||
When I set the field "id_profile_fld" to "Favourite frog"
|
||||
And I set the field "id_profile" to "Kermit"
|
||||
And I press "Add filter"
|
||||
Then I should see "User One"
|
||||
And I should not see "User Two"
|
||||
And I should not see "User Three"
|
||||
And I should not see "User Four"
|
||||
And I press "Remove all filters"
|
||||
And I set the field "id_profile_fld" to "any field"
|
||||
And I set the field "id_profile" to "Zombie"
|
||||
And I press "Add filter"
|
||||
And I should see "User Two"
|
||||
And I should not see "User One"
|
||||
And I should not see "User Three"
|
||||
And I should not see "User Four"
|
||||
|
||||
@@ -1,76 +1,27 @@
|
||||
@core @core_admin
|
||||
Feature: Manage external services tokens
|
||||
In order to manage external service usage
|
||||
Feature: Manage tokens
|
||||
In order to manage webservice usage
|
||||
As an admin
|
||||
I need to be able to create, filter and delete tokens
|
||||
I need to be able to create and delete tokens
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | password | firstname | lastname |
|
||||
| user1 | user1 | Firstname1 | Lastname1 |
|
||||
| user2 | user2 | Firstname2 | Lastname2 |
|
||||
| user3 | user3 | Firstname3 | Lastname3 |
|
||||
| user4 | user4 | Firstname4 | Lastname4 |
|
||||
| username | password | firstname | lastname |
|
||||
| testuser | testuser | Joe | Bloggs |
|
||||
| testuser2 | testuser2 | TestFirstname | TestLastname |
|
||||
And I change window size to "small"
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
|
||||
@javascript
|
||||
Scenario: Add a token to user identified by name and then delete that token
|
||||
Given I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I navigate to "Server > Web services > Manage tokens" in site administration
|
||||
And I press "Create token"
|
||||
And I set the field "User" to "Firstname1 Lastname1"
|
||||
And I set the field "Service" to "Moodle mobile web service"
|
||||
Scenario: Add & delete a token
|
||||
Given I navigate to "Plugins > Web services > Manage tokens" in site administration
|
||||
And I follow "Add"
|
||||
And I set the field "User" to "Joe Bloggs"
|
||||
And I set the field "IP restriction" to "127.0.0.1"
|
||||
When I press "Save changes"
|
||||
Then I should see "Moodle mobile web service" in the "Firstname1 Lastname1" "table_row"
|
||||
And I should see "127.0.0.1" in the "Firstname1 Lastname1" "table_row"
|
||||
And I click on "Delete" "link" in the "Firstname1 Lastname1" "table_row"
|
||||
And I should see "Do you really want to delete this web service token for Firstname1 Lastname1 on the service Moodle mobile web service?"
|
||||
Then I should see "Joe Bloggs"
|
||||
And I should see "127.0.0.1"
|
||||
And I follow "Delete"
|
||||
And I press "Delete"
|
||||
And "Firstname1 Lastname1" "table_row" should not exist
|
||||
|
||||
@javascript @skip_chrome_zerosize
|
||||
Scenario: Tokens can be filtered by user and by service
|
||||
Given the following "core_webservice > Service" exists:
|
||||
| name | Site information |
|
||||
| shortname | siteinfo |
|
||||
| enabled | 1 |
|
||||
And the following "core_webservice > Service function" exists:
|
||||
| service | siteinfo |
|
||||
| functions | core_webservice_get_site_info |
|
||||
And the following "core_webservice > Tokens" exist:
|
||||
| user | service |
|
||||
| user2 | siteinfo |
|
||||
| user3 | moodle_mobile_app |
|
||||
| user4 | siteinfo |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Server > Web services > Manage tokens" in site administration
|
||||
|
||||
# All created tokens are shown by default.
|
||||
And "Firstname1 Lastname1" "table_row" should not exist
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row"
|
||||
And I should see "Site information" in the "Firstname4 Lastname4" "table_row"
|
||||
|
||||
# Filter tokens by user (note we can select the user by the identity field here).
|
||||
When I click on "Tokens filter" "link"
|
||||
And I set the field "User" to "user2@example.com"
|
||||
And I press "Show only matching tokens"
|
||||
Then "Firstname3 Lastname3" "table_row" should not exist
|
||||
And "Firstname4 Lastname4" "table_row" should not exist
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
|
||||
# Reset the filter.
|
||||
And I press "Show all tokens"
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Moodle mobile web service" in the "Firstname3 Lastname3" "table_row"
|
||||
And I should see "Site information" in the "Firstname4 Lastname4" "table_row"
|
||||
|
||||
# Filter tokens by service.
|
||||
And I click on "Tokens filter" "link"
|
||||
And I set the field "Service" to "Site information"
|
||||
And I press "Show only matching tokens"
|
||||
And I should see "Site information" in the "Firstname2 Lastname2" "table_row"
|
||||
And I should see "Site information" in the "Firstname4 Lastname4" "table_row"
|
||||
And "Firstname3 Lastname3" "table_row" should not exist
|
||||
And I should not see "Joe Bloggs"
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
@core @core_admin
|
||||
Feature: Web service user settings
|
||||
In order to configure authorised users for a web service
|
||||
As an admin
|
||||
I need to use the page that lets you do that
|
||||
|
||||
Background:
|
||||
# Include a custom profile field so we can check it gets displayed
|
||||
Given the following "custom profile fields" exist:
|
||||
| datatype | shortname | name | param2 |
|
||||
| text | frog | Favourite frog | 100 |
|
||||
And the following config values are set as admin:
|
||||
| showuseridentity | email,profile_field_frog |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | profile_field_frog |
|
||||
| user1 | User | One | 1@example.org | Kermit |
|
||||
And the following "core_webservice > Service" exists:
|
||||
| name | Silly service |
|
||||
| shortname | silly |
|
||||
| restrictedusers | 1 |
|
||||
| enabled | 1 |
|
||||
|
||||
Scenario: Add a user to a web service
|
||||
When I log in as "admin"
|
||||
And I navigate to "Server > Web services > External services" in site administration
|
||||
And I click on "Authorised users" "link" in the "Silly service" "table_row"
|
||||
And I set the field "Not authorised users" to "User One"
|
||||
And I press "Add"
|
||||
Then I should see "User One" in the ".alloweduserlist" "css_element"
|
||||
And I should see "1@example.org" in the ".alloweduserlist" "css_element"
|
||||
And I should see "Kermit" in the ".alloweduserlist" "css_element"
|
||||
+2
-10
@@ -1,10 +1,2 @@
|
||||
/**
|
||||
* Shows a dialogue with info about this logs.
|
||||
*
|
||||
* @module tool_analytics/log_info
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("tool_analytics/log_info",["jquery","core/str","core/modal_factory","core/notification"],(function($,str,ModalFactory,Notification){return{loadInfo:function(id,info){var link=$('[data-model-log-id="'+id+'"]');str.get_string("loginfo","tool_analytics").then((function(langString){var bodyInfo=$("<ul>");return info.forEach((function(item){bodyInfo.append("<li>"+item+"</li>")})),bodyInfo.append("</ul>"),ModalFactory.create({title:langString,body:bodyInfo.html(),large:!0},link)})).catch(Notification.exception)}}}));
|
||||
|
||||
//# sourceMappingURL=log_info.min.js.map
|
||||
define ("tool_analytics/log_info",["jquery","core/str","core/modal_factory","core/notification"],function(a,b,c,d){return{loadInfo:function loadInfo(e,f){var g=a("[data-model-log-id=\""+e+"\"]");b.get_string("loginfo","tool_analytics").then(function(b){var d=a("<ul>");f.forEach(function(a){d.append("<li>"+a+"</li>")});d.append("</ul>");return c.create({title:b,body:d.html(),large:!0},g)}).catch(d.exception)}}});
|
||||
//# sourceMappingURL=log_info.min.js.map
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"log_info.min.js","sources":["../src/log_info.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Shows a dialogue with info about this logs.\n *\n * @module tool_analytics/log_info\n * @copyright 2017 David Monllao {@link http://www.davidmonllao.com}\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/str', 'core/modal_factory', 'core/notification'], function($, str, ModalFactory, Notification) {\n\n return /** @alias module:tool_analytics/log_info */ {\n\n /**\n * Prepares a modal info for a log's results.\n *\n * @method loadInfo\n * @param {int} id\n * @param {string[]} info\n */\n loadInfo: function(id, info) {\n\n var link = $('[data-model-log-id=\"' + id + '\"]');\n str.get_string('loginfo', 'tool_analytics').then(function(langString) {\n\n var bodyInfo = $(\"<ul>\");\n info.forEach(function(item) {\n bodyInfo.append('<li>' + item + '</li>');\n });\n bodyInfo.append(\"</ul>\");\n\n return ModalFactory.create({\n title: langString,\n body: bodyInfo.html(),\n large: true,\n }, link);\n\n }).catch(Notification.exception);\n }\n };\n});\n"],"names":["define","$","str","ModalFactory","Notification","loadInfo","id","info","link","get_string","then","langString","bodyInfo","forEach","item","append","create","title","body","html","large","catch","exception"],"mappings":";;;;;;;AAsBAA,iCAAO,CAAC,SAAU,WAAY,qBAAsB,sBAAsB,SAASC,EAAGC,IAAKC,aAAcC,oBAEjD,CAShDC,SAAU,SAASC,GAAIC,UAEfC,KAAOP,EAAE,uBAAyBK,GAAK,MAC3CJ,IAAIO,WAAW,UAAW,kBAAkBC,MAAK,SAASC,gBAElDC,SAAWX,EAAE,eACjBM,KAAKM,SAAQ,SAASC,MAClBF,SAASG,OAAO,OAASD,KAAO,YAEpCF,SAASG,OAAO,SAETZ,aAAaa,OAAO,CACvBC,MAAON,WACPO,KAAMN,SAASO,OACfC,OAAO,GACRZ,SAEJa,MAAMjB,aAAakB"}
|
||||
{"version":3,"sources":["../src/log_info.js"],"names":["define","$","str","ModalFactory","Notification","loadInfo","id","info","link","get_string","then","langString","bodyInfo","forEach","item","append","create","title","body","html","large","catch","exception"],"mappings":"AAwBAA,OAAM,2BAAC,CAAC,QAAD,CAAW,UAAX,CAAuB,oBAAvB,CAA6C,mBAA7C,CAAD,CAAoE,SAASC,CAAT,CAAYC,CAAZ,CAAiBC,CAAjB,CAA+BC,CAA/B,CAA6C,CAEnH,MAAoD,CAShDC,QAAQ,CAAE,kBAASC,CAAT,CAAaC,CAAb,CAAmB,CAEzB,GAAIC,CAAAA,CAAI,CAAGP,CAAC,CAAC,wBAAyBK,CAAzB,CAA8B,KAA/B,CAAZ,CACAJ,CAAG,CAACO,UAAJ,CAAe,SAAf,CAA0B,gBAA1B,EAA4CC,IAA5C,CAAiD,SAASC,CAAT,CAAqB,CAElE,GAAIC,CAAAA,CAAQ,CAAGX,CAAC,CAAC,MAAD,CAAhB,CACAM,CAAI,CAACM,OAAL,CAAa,SAASC,CAAT,CAAe,CACxBF,CAAQ,CAACG,MAAT,CAAgB,OAASD,CAAT,CAAgB,OAAhC,CACH,CAFD,EAGAF,CAAQ,CAACG,MAAT,CAAgB,OAAhB,EAEA,MAAOZ,CAAAA,CAAY,CAACa,MAAb,CAAoB,CACvBC,KAAK,CAAEN,CADgB,CAEvBO,IAAI,CAAEN,CAAQ,CAACO,IAAT,EAFiB,CAGvBC,KAAK,GAHkB,CAApB,CAIJZ,CAJI,CAMV,CAdD,EAcGa,KAdH,CAcSjB,CAAY,CAACkB,SAdtB,CAeH,CA3B+C,CA6BvD,CA/BK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Shows a dialogue with info about this logs.\n *\n * @module tool_analytics/log_info\n * @class log_info\n * @package tool_analytics\n * @copyright 2017 David Monllao {@link http://www.davidmonllao.com}\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/str', 'core/modal_factory', 'core/notification'], function($, str, ModalFactory, Notification) {\n\n return /** @alias module:tool_analytics/log_info */ {\n\n /**\n * Prepares a modal info for a log's results.\n *\n * @method loadInfo\n * @param {int} id\n * @param {string[]} info\n */\n loadInfo: function(id, info) {\n\n var link = $('[data-model-log-id=\"' + id + '\"]');\n str.get_string('loginfo', 'tool_analytics').then(function(langString) {\n\n var bodyInfo = $(\"<ul>\");\n info.forEach(function(item) {\n bodyInfo.append('<li>' + item + '</li>');\n });\n bodyInfo.append(\"</ul>\");\n\n return ModalFactory.create({\n title: langString,\n body: bodyInfo.html(),\n large: true,\n }, link);\n\n }).catch(Notification.exception);\n }\n };\n});\n"],"file":"log_info.min.js"}
|
||||
+2
-10
@@ -1,10 +1,2 @@
|
||||
/**
|
||||
* AMD module for model actions confirmation.
|
||||
*
|
||||
* @module tool_analytics/model
|
||||
* @copyright 2017 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("tool_analytics/model",["jquery","core/str","core/log","core/notification","core/modal_factory","core/modal_events","core/templates"],(function($,Str,log,Notification,ModalFactory,ModalEvents,Templates){var actionsList={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}},delete:{title:{key:"delete",component:"tool_analytics"},body:{key:"deletemodelconfirmation",component:"tool_analytics"}}};return{confirmAction:function(actionId,actionType){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(void 0!==actionsList[actionType]){var wrap,reqStrings=[actionsList[actionType].title,actionsList[actionType].body];reqStrings[1].param=(wrap=$(a).closest("[data-model-name]")).length?wrap.attr("data-model-name"):(log.error("Unexpected DOM error - unable to obtain the model name"),"");var stringsPromise=Str.get_strings(reqStrings),modalPromise=ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setBody(strings[1]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}else log.error('Action "'+actionType+'" is not allowed.')}))},selectEvaluationOptions:function(actionId,trainedOnlyExternally){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget),timeSplittingMethods=$(this).attr("data-timesplitting-methods"),stringsPromise=Str.get_strings([{key:"evaluatemodel",component:"tool_analytics"},{key:"evaluate",component:"tool_analytics"}]),modalPromise=ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL}),bodyPromise=Templates.render("tool_analytics/evaluation_options",{trainedexternally:trainedOnlyExternally,timesplittingmethods:JSON.parse(timeSplittingMethods)});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.getRoot().on(ModalEvents.hidden,modal.destroy.bind(modal)),modal.setTitle(strings[0]),modal.setSaveButtonText(strings[1]),modal.setBody(bodyPromise),modal.getRoot().on(ModalEvents.save,(function(){"trainedmodel"==$("input[name='evaluationmode']:checked").val()&&a.attr("href",a.attr("href")+"&mode=trainedmodel");var timeSplittingMethod=$("#id-evaluation-timesplitting").val();a.attr("href",a.attr("href")+"×plitting="+timeSplittingMethod),window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}))},selectExportOptions:function(actionId,isTrained){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(!isTrained)return a.attr("href",a.attr("href")+"&action=exportmodel&includeweights=0"),void(window.location.href=a.attr("href"));var stringsPromise=Str.get_strings([{key:"export",component:"tool_analytics"}]),modalPromise=ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL}),bodyPromise=Templates.render("tool_analytics/export_options",{});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.getRoot().on(ModalEvents.hidden,modal.destroy.bind(modal)),modal.setTitle(strings[0]),modal.setSaveButtonText(strings[0]),modal.setBody(bodyPromise),modal.getRoot().on(ModalEvents.save,(function(){"exportdata"==$("input[name='exportoption']:checked").val()?a.attr("href",a.attr("href")+"&action=exportdata"):(a.attr("href",a.attr("href")+"&action=exportmodel"),$("#id-includeweights").is(":checked")?a.attr("href",a.attr("href")+"&includeweights=1"):a.attr("href",a.attr("href")+"&includeweights=0")),window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}))}}}));
|
||||
|
||||
//# sourceMappingURL=model.min.js.map
|
||||
define ("tool_analytics/model",["jquery","core/str","core/log","core/notification","core/modal_factory","core/modal_events","core/templates"],function(b,c,d,e,f,g,h){var i={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}},delete:{title:{key:"delete",component:"tool_analytics"},body:{key:"deletemodelconfirmation",component:"tool_analytics"}}},j=function(a){var c=b(a).closest("[data-model-name]");if(c.length){return c.attr("data-model-name")}else{d.error("Unexpected DOM error - unable to obtain the model name");return""}};return{confirmAction:function confirmAction(a,h){b("[data-action-id=\""+a+"\"]").on("click",function(k){k.preventDefault();var l=b(k.currentTarget);if("undefined"==typeof i[h]){d.error("Action \""+h+"\" is not allowed.");return}var a=[i[h].title,i[h].body];a[1].param=j(l);var m=c.get_strings(a),n=f.create({type:f.types.SAVE_CANCEL});b.when(m,n).then(function(a,b){b.setTitle(a[0]);b.setBody(a[1]);b.setSaveButtonText(a[0]);b.getRoot().on(g.save,function(){window.location.href=l.attr("href")});b.show();return b}).fail(e.exception)})},selectEvaluationOptions:function selectEvaluationOptions(a,d){b("[data-action-id=\""+a+"\"]").on("click",function(i){i.preventDefault();var j=b(i.currentTarget),a=b(this).attr("data-timesplitting-methods"),k=c.get_strings([{key:"evaluatemodel",component:"tool_analytics"},{key:"evaluate",component:"tool_analytics"}]),l=f.create({type:f.types.SAVE_CANCEL}),m=h.render("tool_analytics/evaluation_options",{trainedexternally:d,timesplittingmethods:JSON.parse(a)});b.when(k,l).then(function(a,c){c.getRoot().on(g.hidden,c.destroy.bind(c));c.setTitle(a[0]);c.setSaveButtonText(a[1]);c.setBody(m);c.getRoot().on(g.save,function(){var a=b("input[name='evaluationmode']:checked").val();if("trainedmodel"==a){j.attr("href",j.attr("href")+"&mode=trainedmodel")}var c=b("#id-evaluation-timesplitting").val();j.attr("href",j.attr("href")+"×plitting="+c);window.location.href=j.attr("href")});c.show();return c}).fail(e.exception)})},selectExportOptions:function selectExportOptions(a,d){b("[data-action-id=\""+a+"\"]").on("click",function(i){i.preventDefault();var j=b(i.currentTarget);if(!d){j.attr("href",j.attr("href")+"&action=exportmodel&includeweights=0");window.location.href=j.attr("href");return}var a=c.get_strings([{key:"export",component:"tool_analytics"}]),k=f.create({type:f.types.SAVE_CANCEL}),l=h.render("tool_analytics/export_options",{});b.when(a,k).then(function(a,c){c.getRoot().on(g.hidden,c.destroy.bind(c));c.setTitle(a[0]);c.setSaveButtonText(a[0]);c.setBody(l);c.getRoot().on(g.save,function(){var a=b("input[name='exportoption']:checked").val();if("exportdata"==a){j.attr("href",j.attr("href")+"&action=exportdata")}else{j.attr("href",j.attr("href")+"&action=exportmodel");if(b("#id-includeweights").is(":checked")){j.attr("href",j.attr("href")+"&includeweights=1")}else{j.attr("href",j.attr("href")+"&includeweights=0")}}window.location.href=j.attr("href")});c.show();return c}).fail(e.exception)})}}});
|
||||
//# sourceMappingURL=model.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+2
-10
@@ -1,10 +1,2 @@
|
||||
/**
|
||||
* Potential contexts selector module.
|
||||
*
|
||||
* @module tool_analytics/potential-contexts
|
||||
* @copyright 2019 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("tool_analytics/potential-contexts",["jquery","core/ajax"],(function($,Ajax){return{processResults:function(selector,results){var contexts=[];return $.isArray(results)?($.each(results,(function(index,context){contexts.push({value:context.id,label:context.name})})),contexts):results},transport:function(selector,query,success,failure){var modelid=$(selector).attr("modelid")||null;Ajax.call([{methodname:"tool_analytics_potential_contexts",args:{query:query,modelid:modelid}}])[0].then(success).fail(failure)}}}));
|
||||
|
||||
//# sourceMappingURL=potential-contexts.min.js.map
|
||||
define ("tool_analytics/potential-contexts",["jquery","core/ajax"],function(a,b){return{processResults:function processResults(b,c){var d=[];if(a.isArray(c)){a.each(c,function(a,b){d.push({value:b.id,label:b.name})});return d}else{return c}},transport:function transport(c,d,e,f){var g,h=a(c).attr("modelid")||null;g=b.call([{methodname:"tool_analytics_potential_contexts",args:{query:d,modelid:h}}]);g[0].then(e).fail(f)}}});
|
||||
//# sourceMappingURL=potential-contexts.min.js.map
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"potential-contexts.min.js","sources":["../src/potential-contexts.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Potential contexts selector module.\n *\n * @module tool_analytics/potential-contexts\n * @copyright 2019 David Monllao\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/ajax'], function($, Ajax) {\n\n return /** @alias module:tool_analytics/potential-contexts */ {\n\n processResults: function(selector, results) {\n var contexts = [];\n if ($.isArray(results)) {\n $.each(results, function(index, context) {\n contexts.push({\n value: context.id,\n label: context.name\n });\n });\n return contexts;\n\n } else {\n return results;\n }\n },\n\n transport: function(selector, query, success, failure) {\n var promise;\n\n let modelid = $(selector).attr('modelid') || null;\n promise = Ajax.call([{\n methodname: 'tool_analytics_potential_contexts',\n args: {\n query: query,\n modelid: modelid\n }\n }]);\n\n promise[0].then(success).fail(failure);\n }\n\n };\n\n});\n"],"names":["define","$","Ajax","processResults","selector","results","contexts","isArray","each","index","context","push","value","id","label","name","transport","query","success","failure","modelid","attr","call","methodname","args","then","fail"],"mappings":";;;;;;;AAuBAA,2CAAO,CAAC,SAAU,cAAc,SAASC,EAAGC,YAEsB,CAE1DC,eAAgB,SAASC,SAAUC,aAC3BC,SAAW,UACXL,EAAEM,QAAQF,UACVJ,EAAEO,KAAKH,SAAS,SAASI,MAAOC,SAC5BJ,SAASK,KAAK,CACVC,MAAOF,QAAQG,GACfC,MAAOJ,QAAQK,UAGhBT,UAGAD,SAIfW,UAAW,SAASZ,SAAUa,MAAOC,QAASC,aAGtCC,QAAUnB,EAAEG,UAAUiB,KAAK,YAAc,KACnCnB,KAAKoB,KAAK,CAAC,CACjBC,WAAY,oCACZC,KAAM,CACFP,MAAOA,MACPG,QAASA,YAIT,GAAGK,KAAKP,SAASQ,KAAKP"}
|
||||
{"version":3,"sources":["../src/potential-contexts.js"],"names":["define","$","Ajax","processResults","selector","results","contexts","isArray","each","index","context","push","value","id","label","name","transport","query","success","failure","promise","modelid","attr","call","methodname","args","then","fail"],"mappings":"AAyBAA,OAAM,qCAAC,CAAC,QAAD,CAAW,WAAX,CAAD,CAA0B,SAASC,CAAT,CAAYC,CAAZ,CAAkB,CAE9C,MAA8D,CAE1DC,cAAc,CAAE,wBAASC,CAAT,CAAmBC,CAAnB,CAA4B,CACxC,GAAIC,CAAAA,CAAQ,CAAG,EAAf,CACA,GAAIL,CAAC,CAACM,OAAF,CAAUF,CAAV,CAAJ,CAAwB,CACpBJ,CAAC,CAACO,IAAF,CAAOH,CAAP,CAAgB,SAASI,CAAT,CAAgBC,CAAhB,CAAyB,CACrCJ,CAAQ,CAACK,IAAT,CAAc,CACVC,KAAK,CAAEF,CAAO,CAACG,EADL,CAEVC,KAAK,CAAEJ,CAAO,CAACK,IAFL,CAAd,CAIH,CALD,EAMA,MAAOT,CAAAA,CAEV,CATD,IASO,CACH,MAAOD,CAAAA,CACV,CACJ,CAhByD,CAkB1DW,SAAS,CAAE,mBAASZ,CAAT,CAAmBa,CAAnB,CAA0BC,CAA1B,CAAmCC,CAAnC,CAA4C,IAC/CC,CAAAA,CAD+C,CAG/CC,CAAO,CAAGpB,CAAC,CAACG,CAAD,CAAD,CAAYkB,IAAZ,CAAiB,SAAjB,GAA+B,IAHM,CAInDF,CAAO,CAAGlB,CAAI,CAACqB,IAAL,CAAU,CAAC,CACjBC,UAAU,CAAE,mCADK,CAEjBC,IAAI,CAAE,CACFR,KAAK,CAAEA,CADL,CAEFI,OAAO,CAAEA,CAFP,CAFW,CAAD,CAAV,CAAV,CAQAD,CAAO,CAAC,CAAD,CAAP,CAAWM,IAAX,CAAgBR,CAAhB,EAAyBS,IAAzB,CAA8BR,CAA9B,CACH,CA/ByD,CAmCjE,CArCK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Potential contexts selector module.\n *\n * @module tool_analytics/potential-contexts\n * @class potential-contexts\n * @package tool_analytics\n * @copyright 2019 David Monllao\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/ajax'], function($, Ajax) {\n\n return /** @alias module:tool_analytics/potential-contexts */ {\n\n processResults: function(selector, results) {\n var contexts = [];\n if ($.isArray(results)) {\n $.each(results, function(index, context) {\n contexts.push({\n value: context.id,\n label: context.name\n });\n });\n return contexts;\n\n } else {\n return results;\n }\n },\n\n transport: function(selector, query, success, failure) {\n var promise;\n\n let modelid = $(selector).attr('modelid') || null;\n promise = Ajax.call([{\n methodname: 'tool_analytics_potential_contexts',\n args: {\n query: query,\n modelid: modelid\n }\n }]);\n\n promise[0].then(success).fail(failure);\n }\n\n };\n\n});\n"],"file":"potential-contexts.min.js"}
|
||||
@@ -17,6 +17,8 @@
|
||||
* Shows a dialogue with info about this logs.
|
||||
*
|
||||
* @module tool_analytics/log_info
|
||||
* @class log_info
|
||||
* @package tool_analytics
|
||||
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* Potential contexts selector module.
|
||||
*
|
||||
* @module tool_analytics/potential-contexts
|
||||
* @class potential-contexts
|
||||
* @package tool_analytics
|
||||
* @copyright 2019 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
@@ -137,7 +137,7 @@ if (!empty($validdatasets) && !$model->is_enabled() && $options['non-interactive
|
||||
}
|
||||
|
||||
// Refresh the instance to prevent unexpected issues.
|
||||
$model = new \core_analytics\model($options['modelid']);
|
||||
$model = new \core_analytics\model($modelobj);
|
||||
|
||||
// Set the time splitting method file and enable it.
|
||||
$model->enable($input);
|
||||
|
||||
+4
-8
@@ -24,17 +24,13 @@
|
||||
* @since Moodle 3.8
|
||||
*/
|
||||
|
||||
namespace tool_analytics\external;
|
||||
|
||||
use externallib_advanced_testcase;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
require_once($CFG->dirroot . '/analytics/tests/fixtures/test_indicator_max.php');
|
||||
require_once($CFG->dirroot . '/analytics/tests/fixtures/test_target_course_level_shortname.php');
|
||||
require_once(__DIR__ . '/../../../../analytics/tests/fixtures/test_indicator_max.php');
|
||||
require_once(__DIR__ . '/../../../../analytics/tests/fixtures/test_target_course_level_shortname.php');
|
||||
|
||||
/**
|
||||
* Tool analytics external functions tests
|
||||
@@ -45,7 +41,7 @@ require_once($CFG->dirroot . '/analytics/tests/fixtures/test_target_course_level
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 3.8
|
||||
*/
|
||||
class external_test extends externallib_advanced_testcase {
|
||||
class tool_analytics_external_testcase extends externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* test_potential_contexts description
|
||||
@@ -77,7 +73,7 @@ class external_test extends externallib_advanced_testcase {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
|
||||
$this->expectException(\required_capability_exception::class);
|
||||
$this->expectException(required_capability_exception::class);
|
||||
$this->assertCount(2, \tool_analytics\external::potential_contexts());
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2021051700; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2021051100; // Requires this Moodle version.
|
||||
$plugin->version = 2020110900; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2020110300; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2021051700;
|
||||
$plugin->requires = 2021051100;
|
||||
$plugin->version = 2020110900;
|
||||
$plugin->requires = 2020110300;
|
||||
$plugin->component = 'tool_availabilityconditions';
|
||||
|
||||
@@ -45,7 +45,7 @@ list($options, $unrecognised) = cli_get_params(
|
||||
array(
|
||||
'stop-on-failure' => 0,
|
||||
'verbose' => false,
|
||||
'replace' => '',
|
||||
'replace' => false,
|
||||
'help' => false,
|
||||
'tags' => '',
|
||||
'profile' => '',
|
||||
@@ -70,7 +70,7 @@ $help = "
|
||||
Behat utilities to run behat tests in parallel
|
||||
|
||||
Usage:
|
||||
php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace=\"{run}\"] [--fromrun=value --torun=value] [--help]
|
||||
php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace] [--fromrun=value --torun=value] [--help]
|
||||
|
||||
Options:
|
||||
--BEHAT_OPTION Any combination of behat option specified in http://behat.readthedocs.org/en/v2.5/guides/6.cli.html
|
||||
@@ -144,10 +144,9 @@ $extraopts = $unrecognised;
|
||||
if ($options['profile']) {
|
||||
$profile = $options['profile'];
|
||||
|
||||
// If profile passed is not set, then exit (note we skip if the 'replace' option is found within the 'profile' value).
|
||||
// If profile passed is not set, then exit.
|
||||
if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile]) &&
|
||||
!($options['replace'] && (strpos($profile, (string) $options['replace']) !== false))) {
|
||||
|
||||
!(isset($options['replace']) && (strpos($options['profile'], $options['replace']) >= 0 ))) {
|
||||
echo "Invalid profile passed: " . $profile . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@ Feature: Transform date time string arguments
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test assignment name"
|
||||
And I should see "##yesterday##%d %B %Y##"
|
||||
And I should see "##yesterday##%A, %d %B %Y##"
|
||||
And I log out
|
||||
|
||||
@@ -49,14 +49,10 @@ Feature: Edit capabilities
|
||||
|
||||
Scenario: Module capabilities overrides
|
||||
Given I log in as "teacher1"
|
||||
And the following "activity" exists:
|
||||
| activity | forum |
|
||||
| course | C1 |
|
||||
| idnumber | 00001 |
|
||||
| name | I'm the name |
|
||||
| intro | I'm the introduction |
|
||||
| section | 1 |
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | I'm the name |
|
||||
| Description | I'm the introduction |
|
||||
And I follow "I'm the name"
|
||||
And I navigate to "Permissions" in current page administration
|
||||
And I override the system permissions of "Student" role with:
|
||||
|
||||
@@ -48,7 +48,7 @@ Feature: Verify that all form fields values can be get and set
|
||||
And the "Unenrol users" select box should not contain "Baker"
|
||||
And the "Unenrol users" select box should not contain "President, Baker"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I follow "Test this one"
|
||||
And I press "Create page"
|
||||
# Text (textarea & editor) & Select (multi-select) - Checking "I set the following fields to these values".
|
||||
When I set the following fields to these values:
|
||||
@@ -61,7 +61,8 @@ Feature: Verify that all form fields values can be get and set
|
||||
# Checkbox - Checking "I set the field" and "The field matches value" ticked.
|
||||
And I set the field "Force format" to "1"
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And the field "Force format" matches value "1"
|
||||
@@ -69,7 +70,8 @@ Feature: Verify that all form fields values can be get and set
|
||||
# Checkbox - Checking "I set the field" and "The field matches value" unticked.
|
||||
And I set the field "Force format" to ""
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And the field "Force format" matches value ""
|
||||
@@ -78,7 +80,8 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I set the following fields to these values:
|
||||
| Force format | 1 |
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And the following fields match these values:
|
||||
@@ -89,7 +92,8 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I set the following fields to these values:
|
||||
| Force format | |
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test this one" "wiki activity" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And the following fields match these values:
|
||||
@@ -111,24 +115,16 @@ Feature: Verify that all form fields values can be get and set
|
||||
| Wiki name | Test this one baby |
|
||||
| Default format | HTML |
|
||||
And I press "Cancel"
|
||||
And I am on "Course 1" course homepage
|
||||
# Radio - Checking "I set the field" and "the field matches value".
|
||||
And the following "activity" exists:
|
||||
| activity | choice |
|
||||
| course | C1 |
|
||||
| idnumber | choice1 |
|
||||
| intro | Test choice description |
|
||||
| name | Test choice name |
|
||||
| choice1 | Option 1, Option 2, Option 3 |
|
||||
| section | 1 |
|
||||
| allowupdate | 1 |
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "Test choice name" "choice activity editing" page
|
||||
And I set the field "Option 1" to "one"
|
||||
And I set the field "Option 2" to "two"
|
||||
And I set the field "Option 3" to "three"
|
||||
And I press "Save and return to course"
|
||||
And I am on "Course 1" course homepage
|
||||
And I am on the "Test choice name" "choice activity" page
|
||||
And I add a "Choice" to section "1" and I fill the form with:
|
||||
| Choice name | Test choice name |
|
||||
| Description | Test choice description |
|
||||
| Allow choice to be updated | Yes |
|
||||
| Option 1 | one |
|
||||
| Option 2 | two |
|
||||
| Option 3 | three |
|
||||
And I follow "Test choice name"
|
||||
And I set the field "one" to "1"
|
||||
And I press "Save my choice"
|
||||
And the field "one" matches value "1"
|
||||
@@ -178,23 +174,31 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I press "Cancel"
|
||||
# Advanced checkbox requires real browser to allow uncheck to work. MDL-58681. MDL-55386.
|
||||
# Advanced checkbox - Checking "I set the field" and "The field matches value" ticked.
|
||||
And I am on the "Test choice name" "choice activity editing" page
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the field "Display description on course page" to "1"
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test choice name" "choice activity editing" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And the field "Display description on course page" matches value "1"
|
||||
And the field "Display description on course page" does not match value ""
|
||||
# Advanced checkbox - Checking "I set the field" and "The field matches value" unticked.
|
||||
And I set the field "Display description on course page" to ""
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test choice name" "choice activity editing" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And the field "Display description on course page" matches value ""
|
||||
And the field "Display description on course page" does not match value "1"
|
||||
# Advanced checkbox - Checking "I set the following fields to these values:" and "The following fields match these values" ticked.
|
||||
And I set the following fields to these values:
|
||||
| Display description on course page | 1 |
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test choice name" "choice activity editing" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And the following fields match these values:
|
||||
| Display description on course page | 1 |
|
||||
And the following fields do not match these values:
|
||||
@@ -203,7 +207,9 @@ Feature: Verify that all form fields values can be get and set
|
||||
And I set the following fields to these values:
|
||||
| Display description on course page | |
|
||||
And I press "Save and return to course"
|
||||
And I am on the "Test choice name" "choice activity editing" page
|
||||
And I should see "Test this one"
|
||||
And I follow "Test this one"
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And the following fields match these values:
|
||||
| Display description on course page | |
|
||||
And the following fields do not match these values:
|
||||
|
||||
@@ -7,8 +7,8 @@ Feature: Behat steps for interacting with form work
|
||||
@javascript
|
||||
Scenario: Test fields in containers
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | summary | summaryformat |
|
||||
| Course 1 | C1 | Red | 1 |
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
When I log in as "admin"
|
||||
And I am on "Course 1" course homepage
|
||||
# Just get to any form.
|
||||
@@ -17,15 +17,11 @@ Feature: Behat steps for interacting with form work
|
||||
And I set the following fields in the "Appearance" "fieldset" to these values:
|
||||
| Show activity reports | Yes |
|
||||
| Number of announcements | 1 |
|
||||
And I set the following fields in the "Description" "fieldset" to these values:
|
||||
| Course summary | Green |
|
||||
Then the field "Show activity reports" in the "Appearance" "fieldset" matches value "Yes"
|
||||
And the field "Show activity reports" in the "Appearance" "fieldset" does not match value "No"
|
||||
And the following fields in the "region-main" "region" match these values:
|
||||
| Course full name | Frog |
|
||||
| Number of announcements | 1 |
|
||||
| Course summary | Green |
|
||||
And the following fields in the "region-main" "region" do not match these values:
|
||||
| Course full name | Course 1 |
|
||||
| Number of announcements | 5 |
|
||||
| Course summary | Red |
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
@tool @tool_behat
|
||||
Feature: Use core page resolvers for the I am on the page steps
|
||||
In order to write tests correctly
|
||||
As a developer
|
||||
I need to have steps which take me straight to a page
|
||||
|
||||
Scenario Outline: When I am on an instance
|
||||
Given the following "categories" exist:
|
||||
| name | category | idnumber |
|
||||
| Cat 1 | 0 | CAT1 |
|
||||
And the following "course" exists:
|
||||
| fullname | Economics 101 |
|
||||
| shortname | ECON101 |
|
||||
| idnumber | 2021-econ101 |
|
||||
And the following "activity" exists:
|
||||
| course | ECON101 |
|
||||
| activity | forum |
|
||||
| name | Fundamentals of Economics |
|
||||
| idnumber | fundamentalsofeconomics |
|
||||
And I log in as "admin"
|
||||
When I am on the <identifier> <type> page
|
||||
Then I should see "<shouldsee>"
|
||||
|
||||
Examples:
|
||||
| description | identifier | type | shouldsee |
|
||||
| Course Category idnumber | CAT1 | category | Add a new course |
|
||||
| Course Category name | "Cat 1" | Category | Add a new course |
|
||||
| Course Full name | "Economics 101" | course | Fundamentals of Economics |
|
||||
| Course Short name | ECON101 | COURSE | Fundamentals of Economics |
|
||||
| Course idnumber | "2021-econ101" | Course | Fundamentals of Economics |
|
||||
| Forum idnumber | fundamentalsofeconomics | Activity | Add a new discussion |
|
||||
| Generic activity editing | fundamentalsofeconomics | "Activity editing" | Updating: Forum |
|
||||
| Forum name | "Fundamentals of Economics" | "Forum activity" | Add a new discussion |
|
||||
| Forum name editing | "Fundamentals of Economics" | "Forum activity editing" | Updating: Forum |
|
||||
| Forum name permissions | "Fundamentals of Economics" | "Forum activity permissions" | Permissions in Forum: Fun |
|
||||
| Forum name roles | "Fundamentals of Economics" | "Forum activity roles" | Assign roles in Forum: Fun |
|
||||
|
||||
Scenario Outline: When I am on an instance logged in as
|
||||
Given the following "categories" exist:
|
||||
| name | category | idnumber |
|
||||
| Cat 1 | 0 | CAT1 |
|
||||
And the following "course" exists:
|
||||
| fullname | Economics 101 |
|
||||
| shortname | ECON101 |
|
||||
| idnumber | 2021-econ101 |
|
||||
And the following "activity" exists:
|
||||
| course | ECON101 |
|
||||
| activity | forum |
|
||||
| name | Fundamentals of Economics |
|
||||
| idnumber | fundamentalsofeconomics |
|
||||
When I am on the <identifier> <type> page logged in as admin
|
||||
Then I should see "<shouldsee>"
|
||||
|
||||
Examples:
|
||||
| description | identifier | type | shouldsee |
|
||||
| Course Category idnumber | CAT1 | category | Add a new course |
|
||||
| Course Category name | "Cat 1" | Category | Add a new course |
|
||||
| Course Full name | "Economics 101" | course | Fundamentals of Economics |
|
||||
| Course Short name | ECON101 | COURSE | Fundamentals of Economics |
|
||||
| Course idnumber | "2021-econ101" | Course | Fundamentals of Economics |
|
||||
| Forum idnumber | fundamentalsofeconomics | Activity | Add a new discussion |
|
||||
| Generic activity editing | fundamentalsofeconomics | "Activity editing" | Updating: Forum |
|
||||
| Forum name | "Fundamentals of Economics" | "Forum activity" | Add a new discussion |
|
||||
| Forum name editing | "Fundamentals of Economics" | "Forum activity editing" | Updating: Forum |
|
||||
| Forum name permissions | "Fundamentals of Economics" | "Forum activity permissions" | Permissions in Forum: Fun |
|
||||
| Forum name roles | "Fundamentals of Economics" | "Forum activity roles" | Assign roles in Forum: Fun |
|
||||
|
||||
Scenario Outline: When I am on a named page
|
||||
Given I log in as "admin"
|
||||
When I am on the <identifier> page
|
||||
Then I should see "<shouldsee>"
|
||||
|
||||
Examples:
|
||||
| description | identifier | shouldsee |
|
||||
| Admin page | "Admin notifications" | Check for available updates |
|
||||
| Home page | Homepage | Recently accessed courses |
|
||||
|
||||
Scenario Outline: When I am on a named page logged in as
|
||||
When I am on the <identifier> page logged in as admin
|
||||
Then I should see "<shouldsee>"
|
||||
|
||||
Examples:
|
||||
| description | identifier | shouldsee |
|
||||
| Admin page | "Admin notifications" | Check for available updates |
|
||||
| Home page | Homepage | Recently accessed courses |
|
||||
@@ -1,110 +0,0 @@
|
||||
@tool_behat
|
||||
Feature: Verify that the behat login and logout steps work as expected
|
||||
In order to use behat login and log out steps
|
||||
As a test writer
|
||||
I need to verify that login and logout happen when the steps are used
|
||||
|
||||
Scenario: Log in as a user using the step
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
When I log in as "traverst1"
|
||||
Then I should see "Thomas Travers"
|
||||
|
||||
@javascript
|
||||
Scenario: Log in as a user using the step (javascript)
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
When I log in as "traverst1"
|
||||
Then I should see "Thomas Travers"
|
||||
|
||||
Scenario: Log out using the log out step
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
And I am logged in as traverst1
|
||||
When I log out
|
||||
Then I should not see "Thomas Travers"
|
||||
And I should see "You are not logged in"
|
||||
|
||||
@javascript
|
||||
Scenario: Log out using the log out step (javascript)
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
And I am logged in as traverst1
|
||||
When I log out
|
||||
Then I should not see "Thomas Travers"
|
||||
And I should see "You are not logged in"
|
||||
|
||||
Scenario: Log in step should automatically log user out if already logged in
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
| emeryj | Jane | Emery |
|
||||
And I am logged in as traverst1
|
||||
When I log in as "emeryj"
|
||||
Then I should not see "Thomas Travers"
|
||||
And I should see "Jane Emery"
|
||||
|
||||
@javascript
|
||||
Scenario: Log in step should automatically log user out if already logged in (javascript)
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
| emeryj | Jane | Emery |
|
||||
And I am logged in as traverst1
|
||||
When I log in as "emeryj"
|
||||
Then I should not see "Thomas Travers"
|
||||
And I should see "Jane Emery"
|
||||
|
||||
Scenario: I am on page logged in as should redirect to correct page
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
And the following "course" exists:
|
||||
| fullname | Life, the Universe, and Everything |
|
||||
| shortname | hhgttg |
|
||||
When I am on the hhgttg Course page logged in as traverst1
|
||||
Then I should see "Thomas Travers"
|
||||
And I should see "Life, the Universe, and Everything"
|
||||
|
||||
@javascript
|
||||
Scenario: I am on page logged in as should redirect to correct page (javascript)
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
And the following "course" exists:
|
||||
| fullname | Life, the Universe, and Everything |
|
||||
| shortname | hhgttg |
|
||||
When I am on the hhgttg Course page logged in as traverst1
|
||||
Then I should see "Thomas Travers"
|
||||
And I should see "Life, the Universe, and Everything"
|
||||
|
||||
Scenario: I am on page logged in as should redirect to correct page when automatically logging a user out
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
| emeryj | Jane | Emery |
|
||||
And the following "course" exists:
|
||||
| fullname | Life, the Universe, and Everything |
|
||||
| shortname | hhgttg |
|
||||
And I am logged in as emeryj
|
||||
When I am on the hhgttg Course page logged in as traverst1
|
||||
Then I should see "Thomas Travers"
|
||||
And I should see "Life, the Universe, and Everything"
|
||||
|
||||
@javascript
|
||||
Scenario: I am on page logged in as should redirect to correct page when automatically logging a user out (javacript)
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| traverst1 | Thomas | Travers |
|
||||
| emeryj | Jane | Emery |
|
||||
And the following "course" exists:
|
||||
| fullname | Life, the Universe, and Everything |
|
||||
| shortname | hhgttg |
|
||||
And I am logged in as emeryj
|
||||
When I am on the hhgttg Course page logged in as traverst1
|
||||
Then I should see "Thomas Travers"
|
||||
And I should see "Life, the Universe, and Everything"
|
||||
@@ -20,10 +20,9 @@ Feature: Forms manipulation
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "activities" exist:
|
||||
| activity | course | section | name |
|
||||
| quiz | C1 | 1 | Quiz 1 |
|
||||
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
|
||||
And I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Quiz" to section "1"
|
||||
When I expand all fieldsets
|
||||
Then I should see "Close the quiz"
|
||||
And I should see "Group mode"
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tests for behat_form_text class
|
||||
*
|
||||
* @copyright 2022 onwards Eloy Lafuente (stronk7) {@link https://stronk7.com}
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_behat;
|
||||
|
||||
use behat_form_text;
|
||||
use Behat\Mink\Session;
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use core_string_manager_standard;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/behat/classes/behat_session_interface.php');
|
||||
require_once($CFG->libdir . '/behat/classes/behat_session_trait.php');
|
||||
require_once($CFG->libdir . '/behat/form_field/behat_form_text.php');
|
||||
|
||||
/**
|
||||
* Tests for the behat_form_text class
|
||||
*
|
||||
* @package tool_behat
|
||||
* @category test
|
||||
* @copyright 2022 onwards Eloy Lafuente (stronk7) {@link https://stronk7.com}
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*
|
||||
* @covers \behat_form_text
|
||||
* @covers \behat_form_field
|
||||
*/
|
||||
class behat_form_text_test extends \basic_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for the test_set_get_value() method.
|
||||
*
|
||||
* @return array of value and expectation pairs to be tested.
|
||||
*/
|
||||
public function provider_test_set_get_value() {
|
||||
return [
|
||||
'null' => [null, null],
|
||||
'int' => [3, 3],
|
||||
'float' => [3.14, 3.14],
|
||||
'string' => ['hello', 'hello'],
|
||||
'utf8' => ['你好', '你好'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the set_value() and get_value() methods.
|
||||
*
|
||||
* @param mixed $value value to be set.
|
||||
* @param mixed $expectation value to be checked.
|
||||
* @dataProvider provider_test_set_get_value()
|
||||
*/
|
||||
public function test_set_get_value($value, $expectation) {
|
||||
$session = $this->createMock(Session::class);
|
||||
$node = $this->createMock(NodeElement::class);
|
||||
$node->method('getValue')->willReturn($value);
|
||||
$field = new behat_form_text($session, $node);
|
||||
|
||||
$field->set_value($value);
|
||||
$this->assertEquals($expectation, $field->get_value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_text_matches() method.
|
||||
*
|
||||
* @return array of decsep, value, match and result pairs to be tested.
|
||||
*/
|
||||
public function provider_test_matches() {
|
||||
return [
|
||||
'lazy true' => ['.', 'hello', 'hello', true],
|
||||
'lazy false' => ['.', 'hello', 'bye', false],
|
||||
'float true' => ['.', '3.14', '3.1400', true],
|
||||
'float false' => ['.', '3.14', '3.1401', false],
|
||||
'float and float string true' => ['.', 3.14, '3.1400', true],
|
||||
'float and unrelated string false' => ['.', 3.14, 'hello', false],
|
||||
'float hash decsep true' => ['#', '3#14', '3#1400', true],
|
||||
'float hash decsep false' => ['#', '3#14', '3#1401', false],
|
||||
'float and float string hash decsep true' => ['#', 3.14, '3.1400', true],
|
||||
'float and unrelated string hash decsep false' => ['#', 3.14, 'hello', false],
|
||||
'float custom-default decsep mix1 true' => ['#', '3#14', '3.1400', true],
|
||||
'float custom-default decsep mix2 true' => ['#', '3.14', '3#1400', true],
|
||||
'float 2-custom decsep mix1 false' => ['#', '3#14', '3,1400', false],
|
||||
'float 2-custom decsep mix2 false' => [',', '3#14', '3,1400', false],
|
||||
'float default-custom decsep mix1 false' => ['.', '3#14', '3.1400', false],
|
||||
'float default-custom decsep mix2 false' => ['.', '3.14', '3#1400', false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the matches() method.
|
||||
*
|
||||
* @param string $decsep decimal separator to use.
|
||||
* @param mixed $value value to be set.
|
||||
* @param mixed $match value to be matched.
|
||||
* @param bool $result expected return status of the function.
|
||||
* @dataProvider provider_test_matches()
|
||||
*/
|
||||
public function test_matches($decsep, $value, $match, $result) {
|
||||
global $CFG;
|
||||
|
||||
// Switch of string manager to avoid having to (slow) customise the lang file.
|
||||
$origcustom = $CFG->config_php_settings['customstringmanager'] ?? null;
|
||||
$CFG->config_php_settings['customstringmanager'] = '\tool_behat\phpunit_string_manager';
|
||||
$manager = get_string_manager(true);
|
||||
$manager->set_string('decsep', 'langconfig', $decsep);
|
||||
|
||||
$session = $this->createMock(Session::class);
|
||||
$node = $this->createMock(NodeElement::class);
|
||||
$node->method('getValue')->willReturn($value);
|
||||
|
||||
$field = new behat_form_text($session, $node);
|
||||
|
||||
$field->set_value($value);
|
||||
$this->assertSame($result, $field->matches($match));
|
||||
|
||||
// Switch back to the original string manager.
|
||||
if (is_null($origcustom)) {
|
||||
unset($CFG->config_php_settings['customstringmanager']);
|
||||
} else {
|
||||
$CFG->config_php_settings['customstringmanager'] = $origcustom;
|
||||
}
|
||||
$manager = get_string_manager(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customised values that will be used instead of standard manager one.
|
||||
*
|
||||
* If an existing component/identifier is found, return it instead of the real
|
||||
* one from language files. Note this doesn't support place holders or another niceties.
|
||||
*
|
||||
* @package tool_behat
|
||||
* @category test
|
||||
* @copyright 2022 onwards Eloy Lafuente (stronk7) {@link https://stronk7.com}
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class phpunit_string_manager extends core_string_manager_standard {
|
||||
|
||||
/** @var array language customisations provided by the manager without asking for real contents */
|
||||
protected $customstrings = [];
|
||||
|
||||
/**
|
||||
* Get String returns a requested string
|
||||
*
|
||||
* @param string $identifier The identifier of the string to search for
|
||||
* @param string $component The module the string is associated with
|
||||
* @param string|object|array $a An object, string or number that can be used
|
||||
* within translation strings
|
||||
* @param string $lang moodle translation language, null means use current
|
||||
* @return string The String !
|
||||
*/
|
||||
public function get_string($identifier, $component = '', $a = null, $lang = null) {
|
||||
$key = trim($component) . '/' . trim($identifier);
|
||||
if (isset($this->customstrings[$key])) {
|
||||
return $this->customstrings[$key];
|
||||
}
|
||||
return parent::get_string($identifier, $component, $a, $lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom string to be returned by the string manager instead of the language file one.
|
||||
*
|
||||
* @param string $identifier The identifier of the string to search for
|
||||
* @param string $component The module the string is associated with
|
||||
* @param string $value the contents of the language string to be returned by get_string()
|
||||
*/
|
||||
public function set_string($identifier, $component, $value) {
|
||||
$key = trim($component) . '/' . trim($identifier);
|
||||
$this->customstrings[$key] = $value;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
// @codingStandardsIgnoreFile
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
@@ -149,7 +149,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
public function test_get_config_file_contents_with_single_run() {
|
||||
|
||||
$mockbuilder = $this->getMockBuilder('behat_config_util');
|
||||
$mockbuilder->onlyMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
|
||||
$behatconfigutil = $mockbuilder->getMock();
|
||||
|
||||
@@ -188,7 +188,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
public function test_get_config_file_contents_with_single_run_no_theme() {
|
||||
|
||||
$mockbuilder = $this->getMockBuilder('behat_config_util');
|
||||
$mockbuilder->onlyMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
|
||||
$behatconfigutil = $mockbuilder->getMock();
|
||||
|
||||
@@ -242,7 +242,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
public function test_get_config_file_contents_with_parallel_run() {
|
||||
|
||||
$mockbuilder = $this->getMockBuilder('behat_config_util');
|
||||
$mockbuilder->onlyMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
|
||||
$behatconfigutil = $mockbuilder->getMock();
|
||||
|
||||
@@ -345,7 +345,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
public function test_get_config_file_contents_with_parallel_run_optimize_tags() {
|
||||
|
||||
$mockbuilder = $this->getMockBuilder('behat_config_util');
|
||||
$mockbuilder->onlyMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
|
||||
$behatconfigutil = $mockbuilder->getMock();
|
||||
|
||||
@@ -489,7 +489,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
public function test_get_config_file_contents_with_blacklisted_tags() {
|
||||
|
||||
$mockbuilder = $this->getMockBuilder('behat_config_util');
|
||||
$mockbuilder->onlyMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
|
||||
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
|
||||
'get_default_theme', 'get_theme_config'));
|
||||
|
||||
$behatconfigutil = $mockbuilder->getMock();
|
||||
@@ -553,7 +553,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
public function test_get_config_file_contents_with_blacklisted_features_contexts() {
|
||||
|
||||
$mockbuilder = $this->getMockBuilder('behat_config_util');
|
||||
$mockbuilder->onlyMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
|
||||
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
|
||||
'get_default_theme', 'get_theme_config'));
|
||||
|
||||
$behatconfigutil = $mockbuilder->getMock();
|
||||
@@ -632,7 +632,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
|
||||
public function test_core_features_to_include_in_specified_theme() {
|
||||
|
||||
$mockbuilder = $this->getMockBuilder('behat_config_util');
|
||||
$mockbuilder->onlyMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
$mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
|
||||
|
||||
$behatconfigutil = $mockbuilder->getMock();
|
||||
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2021051700; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2021051100; // Requires this Moodle version.
|
||||
$plugin->version = 2020110900; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2020110300; // Requires this Moodle version
|
||||
$plugin->component = 'tool_behat'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
PLEASE NOTE:
|
||||
|
||||
There are included libraries in the directory quail/common/services which might have different (but compatible) licenses than the one covered in this file. Current licenses include:
|
||||
*The current BSD:
|
||||
*services/readability/php-text-statistics
|
||||
*The MIT License:
|
||||
*common/css.php The function parseSelector is from the PHPQuery project:
|
||||
*http://code.google.com/p/phpquery
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
TERMS AND CONDITIONS
|
||||
0. Definitions.
|
||||
|
||||
ÒThis LicenseÓ refers to version 3 of the GNU General Public License.
|
||||
|
||||
ÒCopyrightÓ also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
|
||||
|
||||
ÒThe ProgramÓ refers to any copyrightable work licensed under this License. Each licensee is addressed as ÒyouÓ. ÒLicenseesÓ and ÒrecipientsÓ may be individuals or organizations.
|
||||
|
||||
To ÒmodifyÓ a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a Òmodified versionÓ of the earlier work or a work Òbased onÓ the earlier work.
|
||||
|
||||
A Òcovered workÓ means either the unmodified Program or a work based on the Program.
|
||||
|
||||
To ÒpropagateÓ a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
|
||||
|
||||
To ÒconveyÓ a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays ÒAppropriate Legal NoticesÓ to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
|
||||
1. Source Code.
|
||||
|
||||
The Òsource codeÓ for a work means the preferred form of the work for making modifications to it. ÒObject codeÓ means any non-source form of a work.
|
||||
|
||||
A ÒStandard InterfaceÓ means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
|
||||
|
||||
The ÒSystem LibrariesÓ of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A ÒMajor ComponentÓ, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The ÒCorresponding SourceÓ for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same work.
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
* a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
|
||||
* b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to Òkeep intact all noticesÓ.
|
||||
* c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
|
||||
* d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an ÒaggregateÓ if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
|
||||
|
||||
* a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
|
||||
* b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
|
||||
* c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
|
||||
* d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
|
||||
* e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
|
||||
|
||||
A ÒUser ProductÓ is either (1) a Òconsumer productÓ, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, Ònormally usedÓ refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
|
||||
|
||||
ÒInstallation InformationÓ for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
|
||||
7. Additional Terms.
|
||||
|
||||
ÒAdditional permissionsÓ are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
|
||||
|
||||
* a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
|
||||
* b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
|
||||
* c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
|
||||
* d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
|
||||
* e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
|
||||
* f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered Òfurther restrictionsÓ within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
|
||||
|
||||
An Òentity transactionÓ is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
|
||||
11. Patents.
|
||||
|
||||
A ÒcontributorÓ is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's Òcontributor versionÓ.
|
||||
|
||||
A contributor's Òessential patent claimsÓ are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, ÒcontrolÓ includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a Òpatent licenseÓ is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To ÒgrantÓ such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. ÒKnowingly relyingÓ means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
|
||||
|
||||
A patent license is ÒdiscriminatoryÓ if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License Òor any later versionÓ applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM ÒAS ISÓ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS FOR QUAIL LIBRARY
|
||||
|
||||
|
||||
THE FOLLOWING LICENSE COVERS PORTIONS OF THE FILE LOCATED AT common/css.php, NAMELY THE FUNCTION parseSelector:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,23 +0,0 @@
|
||||
# Brickfield accessibility tool plugin #
|
||||
|
||||
Copyright (C) 2021 [Brickfield Education Labs](https://www.brickfield.ie/)
|
||||
|
||||
The Accessibility admin tool plugin provides a simple means of checking content for any accessibility issues.
|
||||
|
||||
Created by an enthusiastic team from Brickfield Education Labs, including:
|
||||
* Karen Holland
|
||||
* Gavin Henrick
|
||||
* Mike Churchward
|
||||
* Renée Mackey
|
||||
* Laia Joana Canet
|
||||
* Jay Churchward
|
||||
* Paul Ffrench
|
||||
* Holly Lynn
|
||||
* Michael Pound
|
||||
* Max Larkin
|
||||
|
||||
with a little help from our friends.
|
||||
|
||||
Portions of the htmlchecker files were derived from the ["Quail PHP Accessibility Library"](https://code.google.com/archive/p/quail-lib/).
|
||||
The Quail library (Copyright (C) 2009 Kevin Miller) is licensed under [GNU GPL v3](http://www.gnu.org/licenses/gpl.html).
|
||||
See the LICENSE_QUAIL.txt file in this directory for original Quail license information.
|
||||
@@ -1,576 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield;
|
||||
|
||||
use context_system;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
use tool_brickfield\local\tool\filter;
|
||||
|
||||
/**
|
||||
* Provides the Brickfield Accessibility toolkit API.
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 onward Brickfield Education Labs Ltd, https://www.brickfield.ie
|
||||
* @author Mike Churchward (mike@brickfieldlabs.ie)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class accessibility {
|
||||
|
||||
/** @var string The component sub path */
|
||||
private static $pluginpath = 'tool/brickfield';
|
||||
|
||||
/** @var string Supported format of topics */
|
||||
const TOOL_BRICKFIELD_FORMAT_TOPIC = 'topics';
|
||||
|
||||
/** @var string Supported format of weeks */
|
||||
const TOOL_BRICKFIELD_FORMAT_WEEKLY = 'weeks';
|
||||
|
||||
/**
|
||||
* Return the state of the site enable condition.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_accessibility_enabled(): bool {
|
||||
global $CFG;
|
||||
|
||||
if (isset($CFG->enableaccessibilitytools)) {
|
||||
return $CFG->enableaccessibilitytools;
|
||||
}
|
||||
|
||||
// Enabled by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error if the toolkit is not enabled.
|
||||
* @return bool
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function require_accessibility_enabled(): bool {
|
||||
if (!static::is_accessibility_enabled()) {
|
||||
throw new moodle_exception('accessibilitydisabled', manager::PLUGINNAME);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL for a page within the plugin.
|
||||
*
|
||||
* This takes into account the value of the admin config value.
|
||||
*
|
||||
* @param string $url The URL within the plugin
|
||||
* @return moodle_url
|
||||
*/
|
||||
public static function get_plugin_url(string $url = ''): moodle_url {
|
||||
$url = ($url == '') ? 'index.php' : $url;
|
||||
$pluginpath = self::$pluginpath;
|
||||
return new moodle_url("/admin/{$pluginpath}/{$url}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file path for a file within the plugin.
|
||||
*
|
||||
* This takes into account the value of the admin config value.
|
||||
*
|
||||
* @param string $path The path within the plugin
|
||||
* @return string
|
||||
*/
|
||||
public static function get_file_path(string $path): string {
|
||||
global $CFG;
|
||||
|
||||
return implode(DIRECTORY_SEPARATOR, [$CFG->dirroot, $CFG->admin, self::$pluginpath, $path, ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canonicalised name of a capability.
|
||||
*
|
||||
* @param string $capability
|
||||
* @return string
|
||||
*/
|
||||
public static function get_capability_name(string $capability): string {
|
||||
return self::$pluginpath . ':' . $capability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relevant title.
|
||||
* @param filter $filter
|
||||
* @param int $countdata
|
||||
* @return string
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public static function get_title(filter $filter, int $countdata): string {
|
||||
global $DB;
|
||||
|
||||
$tmp = new \stdClass();
|
||||
$tmp->count = $countdata;
|
||||
$langstr = 'title' . $filter->tab . 'partial';
|
||||
|
||||
if ($filter->courseid != 0) {
|
||||
$thiscourse = get_fast_modinfo($filter->courseid)->get_course();
|
||||
$tmp->name = $thiscourse->fullname;
|
||||
} else {
|
||||
$langstr = 'title' . $filter->tab . 'all';
|
||||
}
|
||||
return get_string($langstr, manager::PLUGINNAME, $tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to be run periodically according to the scheduled task.
|
||||
* Return true if a process was completed. False if no process executed.
|
||||
* Finds all unprocessed courses for bulk batch processing and completes them.
|
||||
* @param int $batch
|
||||
* @return bool
|
||||
* @throws \ReflectionException
|
||||
* @throws \coding_exception
|
||||
* @throws \ddl_exception
|
||||
* @throws \ddl_table_missing_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function bulk_process_courses_cron(int $batch = 0): bool {
|
||||
global $PAGE;
|
||||
|
||||
// Run a registration check.
|
||||
if (!(new registration())->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (analysis::is_enabled()) {
|
||||
$PAGE->set_context(context_system::instance());
|
||||
mtrace("Starting cron for bulk_process_courses");
|
||||
// Do regular processing. True if full deployment type isn't selected as well.
|
||||
static::bulk_processing($batch);
|
||||
mtrace("Ending cron for bulk_process_courses");
|
||||
return true;
|
||||
} else {
|
||||
mtrace('Content analysis is currently disabled in settings.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk processing.
|
||||
* @param int $batch
|
||||
* @return bool
|
||||
*/
|
||||
protected static function bulk_processing(int $batch = 0): bool {
|
||||
manager::check_course_updates();
|
||||
mtrace("check_course_updates completed at " . time());
|
||||
$recordsprocessed = manager::check_scheduled_areas($batch);
|
||||
mtrace("check_scheduled_areas completed at " . time());
|
||||
manager::check_scheduled_deletions();
|
||||
mtrace("check_scheduled_deletions completed at " . time());
|
||||
manager::delete_historical_data();
|
||||
mtrace("delete_historical_data completed at " . time());
|
||||
return $recordsprocessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to be run periodically according to the scheduled task.
|
||||
* Finds all unprocessed courses for cache processing and completes them.
|
||||
*/
|
||||
public static function bulk_process_caches_cron() {
|
||||
global $DB;
|
||||
|
||||
// Run a registration check.
|
||||
if (!(new registration())->validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (analysis::is_enabled()) {
|
||||
mtrace("Starting cron for bulk_process_caches");
|
||||
// Monitor ongoing caching requests.
|
||||
$fields = 'DISTINCT courseid';
|
||||
$reruns = $DB->get_records(manager::DB_PROCESS, ['item' => 'cache'], '', $fields);
|
||||
foreach ($reruns as $rerun) {
|
||||
mtrace("Running rerun caching for Courseid " . $rerun->courseid);
|
||||
manager::store_result_summary($rerun->courseid);
|
||||
mtrace("rerun cache completed at " . time());
|
||||
$DB->delete_records(manager::DB_PROCESS, ['courseid' => $rerun->courseid, 'item' => 'cache']);
|
||||
}
|
||||
mtrace("Ending cron for bulk_process_caches at " . time());
|
||||
} else {
|
||||
mtrace('Content analysis is currently disabled in settings.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs the checks on the html item
|
||||
*
|
||||
* @param string $html The html string to be analysed; might be NULL.
|
||||
* @param int $contentid The content area ID
|
||||
* @param int $processingtime
|
||||
* @param int $resultstime
|
||||
*/
|
||||
public static function run_check(string $html, int $contentid, int &$processingtime, int &$resultstime) {
|
||||
global $DB;
|
||||
|
||||
// Change the limit if 10,000 is not appropriate.
|
||||
$bulkrecordlimit = manager::BULKRECORDLIMIT;
|
||||
$bulkrecordcount = 0;
|
||||
|
||||
$checkids = static::checkids();
|
||||
$checknameids = array_flip($checkids);
|
||||
|
||||
$testname = 'brickfield';
|
||||
|
||||
$stime = time();
|
||||
|
||||
// Swapping in new library.
|
||||
$htmlchecker = new local\htmlchecker\brickfield_accessibility($html, $testname, 'string');
|
||||
$htmlchecker->run_check();
|
||||
$tests = $htmlchecker->guideline->get_tests();
|
||||
$report = $htmlchecker->get_report();
|
||||
$processingtime += (time() - $stime);
|
||||
|
||||
$records = [];
|
||||
foreach ($tests as $test) {
|
||||
$records[$test]['count'] = 0;
|
||||
$records[$test]['errors'] = [];
|
||||
}
|
||||
|
||||
foreach ($report['report'] as $a) {
|
||||
if (!isset($a['type'])) {
|
||||
continue;
|
||||
}
|
||||
$type = $a['type'];
|
||||
$records[$type]['errors'][] = $a;
|
||||
if (!isset($records[$type]['count'])) {
|
||||
$records[$type]['count'] = 0;
|
||||
}
|
||||
$records[$type]['count']++;
|
||||
}
|
||||
|
||||
$stime = time();
|
||||
$returnchecks = [];
|
||||
$errors = [];
|
||||
|
||||
// Build up records for inserting.
|
||||
foreach ($records as $key => $rec) {
|
||||
$recordres = new stdClass();
|
||||
// Handling if checkid is unknown.
|
||||
$checkid = (isset($checknameids[$key])) ? $checknameids[$key] : 0;
|
||||
$recordres->contentid = $contentid;
|
||||
$recordres->checkid = $checkid;
|
||||
$recordres->errorcount = $rec['count'];
|
||||
|
||||
// Build error inserts if needed.
|
||||
if ($rec['count'] > 0) {
|
||||
foreach ($rec['errors'] as $tmp) {
|
||||
$error = new stdClass();
|
||||
$error->resultid = 0;
|
||||
$error->linenumber = $tmp['lineNo'];
|
||||
$error->htmlcode = $tmp['html'];
|
||||
$error->errordescription = $tmp['title'];
|
||||
// Add contentid and checkid so that we can query for the results record id later.
|
||||
$error->contentid = $contentid;
|
||||
$error->checkid = $checkid;
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
$returnchecks[] = $recordres;
|
||||
$bulkrecordcount++;
|
||||
|
||||
// If we've hit the bulk limit, write the results records and reset.
|
||||
if ($bulkrecordcount > $bulkrecordlimit) {
|
||||
$DB->insert_records(manager::DB_RESULTS, $returnchecks);
|
||||
$bulkrecordcount = 0;
|
||||
$returnchecks = [];
|
||||
// Get the results id value for each error record and write the errors.
|
||||
foreach ($errors as $key2 => $error) {
|
||||
$errors[$key2]->resultid = $DB->get_field(manager::DB_RESULTS, 'id',
|
||||
['contentid' => $error->contentid, 'checkid' => $error->checkid]);
|
||||
unset($errors[$key2]->contentid);
|
||||
unset($errors[$key2]->checkid);
|
||||
}
|
||||
$DB->insert_records(manager::DB_ERRORS, $errors);
|
||||
$errors = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Write any leftover records.
|
||||
if ($bulkrecordcount > 0) {
|
||||
$DB->insert_records(manager::DB_RESULTS, $returnchecks);
|
||||
// Get the results id value for each error record and write the errors.
|
||||
foreach ($errors as $key => $error) {
|
||||
$errors[$key]->resultid = $DB->get_field(manager::DB_RESULTS, 'id',
|
||||
['contentid' => $error->contentid, 'checkid' => $error->checkid]);
|
||||
unset($errors[$key]->contentid);
|
||||
unset($errors[$key]->checkid);
|
||||
}
|
||||
$DB->insert_records(manager::DB_ERRORS, $errors);
|
||||
}
|
||||
|
||||
$resultstime += (time() - $stime);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs one specified check on the html item
|
||||
*
|
||||
* @param string $html The html string to be analysed; might be NULL.
|
||||
* @param int $contentid The content area ID
|
||||
* @param int $errid The error ID
|
||||
* @param string $check The check name to run
|
||||
* @param int $processingtime
|
||||
* @param int $resultstime
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function run_one_check(
|
||||
string $html = null,
|
||||
int $contentid,
|
||||
int $errid,
|
||||
string $check,
|
||||
int &$processingtime,
|
||||
int &$resultstime
|
||||
) {
|
||||
global $DB;
|
||||
|
||||
$stime = time();
|
||||
|
||||
$checkdata = $DB->get_record(manager::DB_CHECKS, ['shortname' => $check], 'id,shortname,severity');
|
||||
|
||||
$testname = 'brickfield';
|
||||
|
||||
// Swapping in new library.
|
||||
$htmlchecker = new local\htmlchecker\brickfield_accessibility($html, $testname, 'string');
|
||||
$htmlchecker->run_check();
|
||||
$report = $htmlchecker->get_test($check);
|
||||
$processingtime += (time() - $stime);
|
||||
|
||||
$record = [];
|
||||
$record['count'] = 0;
|
||||
$record['errors'] = [];
|
||||
|
||||
foreach ($report as $a) {
|
||||
$a->html = $a->get_html();
|
||||
$record['errors'][] = $a;
|
||||
$record['count']++;
|
||||
}
|
||||
|
||||
// Build up record for inserting.
|
||||
$recordres = new stdClass();
|
||||
// Handling if checkid is unknown.
|
||||
$checkid = (isset($checkdata->id)) ? $checkdata->id : 0;
|
||||
$recordres->contentid = $contentid;
|
||||
$recordres->checkid = $checkid;
|
||||
$recordres->errorcount = $record['count'];
|
||||
if ($exists = $DB->get_record(manager::DB_RESULTS, ['contentid' => $contentid, 'checkid' => $checkid])) {
|
||||
$resultid = $exists->id;
|
||||
$DB->set_field(manager::DB_RESULTS, 'errorcount', $record['count'], ['id' => $resultid]);
|
||||
// Remove old error records for specific resultid, if existing.
|
||||
$DB->delete_records(manager::DB_ERRORS, ['id' => $errid]);
|
||||
} else {
|
||||
$resultid = $DB->insert_record(manager::DB_RESULTS, $recordres);
|
||||
}
|
||||
$errors = [];
|
||||
|
||||
// Build error inserts if needed.
|
||||
if ($record['count'] > 0) {
|
||||
// Reporting all found errors for this check, so need to ignore existing other error records.
|
||||
foreach ($record['errors'] as $tmp) {
|
||||
// Confirm if error is reported separately.
|
||||
if ($DB->record_exists_select(manager::DB_ERRORS,
|
||||
'resultid = ? AND ' . $DB->sql_compare_text('htmlcode', 255) . ' = ' . $DB->sql_compare_text('?', 255),
|
||||
[$resultid, html_entity_decode($tmp->html)])) {
|
||||
continue;
|
||||
}
|
||||
$error = new stdClass();
|
||||
$error->resultid = $resultid;
|
||||
$error->linenumber = $tmp->line;
|
||||
$error->htmlcode = html_entity_decode($tmp->html);
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
$DB->insert_records(manager::DB_ERRORS, $errors);
|
||||
}
|
||||
|
||||
$resultstime += (time() - $stime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the id's and shortnames of all of the checks.
|
||||
* @param int $status
|
||||
* @return array
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function checkids(int $status = 1): array {
|
||||
global $DB;
|
||||
|
||||
$checks = $DB->get_records_menu(manager::DB_CHECKS, ['status' => $status], 'id ASC', 'id,shortname');
|
||||
return $checks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of translations from htmlchecker of all of the checks, and their descriptions.
|
||||
* @return array
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function get_translations(): array {
|
||||
global $DB;
|
||||
|
||||
$htmlchecker = new local\htmlchecker\brickfield_accessibility('test', 'brickfield', 'string');
|
||||
$htmlchecker->run_check();
|
||||
ksort($htmlchecker->guideline->translations);
|
||||
|
||||
// Need to limit to active checks.
|
||||
$activechecks = $DB->get_fieldset_select(manager::DB_CHECKS, 'shortname', 'status = :status', ['status' => 1]);
|
||||
|
||||
$translations = [];
|
||||
foreach ($htmlchecker->guideline->translations as $key => $trans) {
|
||||
if (in_array($key, $activechecks)) {
|
||||
$translations[$key] = $trans;
|
||||
}
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all of the course id's for a given category.
|
||||
* @param int $categoryid
|
||||
* @return array|null
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function get_category_courseids(int $categoryid): ?array {
|
||||
global $DB;
|
||||
|
||||
if (!$DB->record_exists('course_categories', ['id' => $categoryid])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sql = "SELECT {course}.id
|
||||
FROM {course}, {course_categories}
|
||||
WHERE {course}.category = {course_categories}.id
|
||||
AND (
|
||||
" . $DB->sql_like('path', ':categoryid1') . "
|
||||
OR " . $DB->sql_like('path', ':categoryid2') . "
|
||||
)";
|
||||
$params = ['categoryid1' => "%/$categoryid/%", 'categoryid2' => "%/$categoryid"];
|
||||
$courseids = $DB->get_fieldset_sql($sql, $params);
|
||||
|
||||
return $courseids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get summary data for this site.
|
||||
* @param int $id
|
||||
* @return \stdClass
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function get_summary_data(int $id): \stdClass {
|
||||
global $CFG, $DB;
|
||||
|
||||
$summarydata = new \stdClass();
|
||||
$summarydata->siteurl = (substr($CFG->wwwroot, -1) !== '/') ? $CFG->wwwroot . '/' : $CFG->wwwroot;
|
||||
$summarydata->moodlerelease = (preg_match('/^(\d+\.\d.*?)[. ]/', $CFG->release, $matches)) ? $matches[1] : $CFG->release;
|
||||
$summarydata->numcourses = $DB->count_records('course') - 1;
|
||||
$summarydata->numusers = $DB->count_records('user', array('deleted' => 0));
|
||||
$summarydata->numfiles = $DB->count_records('files');
|
||||
$summarydata->numfactivities = $DB->count_records('course_modules');
|
||||
$summarydata->mobileservice = (int)$CFG->enablemobilewebservice === 1 ? true : false;
|
||||
$summarydata->usersmobileregistered = $DB->count_records('user_devices');
|
||||
$summarydata->contenttyperesults = static::get_contenttyperesults($id);
|
||||
$summarydata->contenttypeerrors = static::get_contenttypeerrors();
|
||||
$summarydata->percheckerrors = static::get_percheckerrors();
|
||||
return $summarydata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content type results.
|
||||
* @param int $id
|
||||
* @return \stdClass
|
||||
*/
|
||||
private static function get_contenttyperesults(int $id): \stdClass {
|
||||
global $DB;
|
||||
$sql = 'SELECT component, COUNT(id) AS count
|
||||
FROM {' . manager::DB_AREAS . '}
|
||||
GROUP BY component';
|
||||
$components = $DB->get_recordset_sql($sql);
|
||||
$contenttyperesults = new \stdClass();
|
||||
$contenttyperesults->id = $id;
|
||||
$contenttyperesults->contenttype = new \stdClass();
|
||||
foreach ($components as $component) {
|
||||
$componentname = $component->component;
|
||||
$contenttyperesults->contenttype->$componentname = $component->count;
|
||||
}
|
||||
$components->close();
|
||||
$contenttyperesults->summarydatastorage = static::get_summary_data_storage();
|
||||
$contenttyperesults->datachecked = time();
|
||||
return $contenttyperesults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get per check errors.
|
||||
* @return stdClass
|
||||
* @throws dml_exception
|
||||
*/
|
||||
private static function get_percheckerrors(): stdClass {
|
||||
global $DB;
|
||||
|
||||
$sql = 'SELECT ' . $DB->sql_concat_join("'_'", ['courseid', 'checkid']) . ' as tmpid,
|
||||
ca.courseid, ca.status, ca.checkid, ch.shortname, ca.checkcount, ca.errorcount
|
||||
FROM {' . manager::DB_CACHECHECK . '} ca
|
||||
INNER JOIN {' . manager::DB_CHECKS . '} ch on ch.id = ca.checkid
|
||||
ORDER BY courseid, checkid ASC';
|
||||
|
||||
$combo = $DB->get_records_sql($sql);
|
||||
|
||||
return (object) [
|
||||
'percheckerrors' => $combo,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content type errors.
|
||||
* @return stdClass
|
||||
* @throws dml_exception
|
||||
*/
|
||||
private static function get_contenttypeerrors(): stdClass {
|
||||
global $DB;
|
||||
|
||||
$fields = 'courseid, status, activities, activitiespassed, activitiesfailed,
|
||||
errorschecktype1, errorschecktype2, errorschecktype3, errorschecktype4,
|
||||
errorschecktype5, errorschecktype6, errorschecktype7,
|
||||
failedchecktype1, failedchecktype2, failedchecktype3, failedchecktype4,
|
||||
failedchecktype5, failedchecktype6, failedchecktype7,
|
||||
percentchecktype1, percentchecktype2, percentchecktype3, percentchecktype4,
|
||||
percentchecktype5, percentchecktype6, percentchecktype7';
|
||||
$combo = $DB->get_records(manager::DB_SUMMARY, null, 'courseid ASC', $fields);
|
||||
|
||||
return (object) [
|
||||
'typeerrors' => $combo,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get summary data storage.
|
||||
* @return array
|
||||
* @throws dml_exception
|
||||
*/
|
||||
private static function get_summary_data_storage(): array {
|
||||
global $DB;
|
||||
|
||||
$fields = $DB->sql_concat_join("''", ['component', 'courseid']) . ' as tmpid,
|
||||
courseid, component, errorcount, totalactivities, failedactivities, passedactivities';
|
||||
$combo = $DB->get_records(manager::DB_CACHEACTS, null, 'courseid, component ASC', $fields);
|
||||
return $combo;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield;
|
||||
|
||||
use tool_brickfield\task\process_analysis_requests;
|
||||
|
||||
/**
|
||||
* Analysis and deployment class.
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 Brickfield Education Labs https://www.brickfield.ie
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class analysis {
|
||||
|
||||
/**
|
||||
* Indicates that analysis of content has been enabled.
|
||||
*/
|
||||
const ANALYSISDISABLED = '0';
|
||||
|
||||
/**
|
||||
* Indicates that analysis is enabled and using request method.
|
||||
*/
|
||||
const ANALYSISBYREQUEST = '1';
|
||||
|
||||
/**
|
||||
* Return the type of analysis being used (currently only request).
|
||||
*
|
||||
* @return false|mixed|object|string|null
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function get_type() {
|
||||
// Moodle caches these calls, so it's not expensive.
|
||||
return get_config(manager::PLUGINNAME, 'analysistype');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true is analysis has been enabled.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function is_enabled(): bool {
|
||||
$analysistype = static::get_type();
|
||||
return ($analysistype !== false) && ($analysistype !== self::ANALYSISDISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if analysis is by request method.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function type_is_byrequest(): bool {
|
||||
return static::get_type() === self::ANALYSISBYREQUEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the course has been analyzed.
|
||||
*
|
||||
* @param int $courseid
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function is_course_analyzed(int $courseid): bool {
|
||||
return scheduler::is_course_analyzed($courseid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a redirect message with the earliest time expected for analysis to complete.
|
||||
* @return \lang_string|string
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function redirect_message() {
|
||||
$epoch = new process_analysis_requests;
|
||||
$time = userdate($epoch->get_next_scheduled_time(), get_string('strftimetime', 'core_langconfig'));
|
||||
$message = get_string('confirmationmessage', manager::PLUGINNAME, $time);
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield;
|
||||
|
||||
/**
|
||||
* Area base class.
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 Brickfield Education Labs https://www.brickfield.ie
|
||||
*/
|
||||
abstract class area_base {
|
||||
|
||||
/**
|
||||
* Defines the unknown for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* Defines the form for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_FORM = 1;
|
||||
|
||||
/**
|
||||
* Defines the image for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_IMAGE = 2;
|
||||
|
||||
/**
|
||||
* Defines the layout for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_LAYOUT = 3;
|
||||
|
||||
/**
|
||||
* Defines the link for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_LINK = 4;
|
||||
|
||||
/**
|
||||
* Defines the media for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_MEDIA = 5;
|
||||
|
||||
/**
|
||||
* Defines the table for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_TABLE = 6;
|
||||
|
||||
/**
|
||||
* Defines the text for the 'checkgroup' field in the tool_brickfield_checks table.
|
||||
*/
|
||||
const CHECKGROUP_TEXT = 7;
|
||||
|
||||
/** @var string[] Array for quick access of string names for 'checkgroups'. */
|
||||
const CHECKGROUP_NAMES = [
|
||||
self::CHECKGROUP_UNKNOWN => 'unknown',
|
||||
self::CHECKGROUP_FORM => 'form',
|
||||
self::CHECKGROUP_IMAGE => 'image',
|
||||
self::CHECKGROUP_LAYOUT => 'layout',
|
||||
self::CHECKGROUP_LINK => 'link',
|
||||
self::CHECKGROUP_MEDIA => 'media',
|
||||
self::CHECKGROUP_TABLE => 'table',
|
||||
self::CHECKGROUP_TEXT => 'text'
|
||||
];
|
||||
|
||||
/**
|
||||
* Defines field value.
|
||||
*/
|
||||
const TYPE_FIELD = 0;
|
||||
|
||||
/**
|
||||
* Defines file value.
|
||||
*/
|
||||
const TYPE_FILE = 1;
|
||||
|
||||
/**
|
||||
* Return the name for the specified checkgroup value, or 'unknown' if no valid name for the value. Preferably, use this rather
|
||||
* than direct access to CHECKGROUP_NAMES, since it checks value boundaries.
|
||||
* @param int $checkgroupvalue
|
||||
* @return string
|
||||
*/
|
||||
final public static function checkgroup_name(int $checkgroupvalue): string {
|
||||
if (($checkgroupvalue < 0) || ($checkgroupvalue >= count(self::CHECKGROUP_NAMES))) {
|
||||
return self::CHECKGROUP_NAMES[self::CHECKGROUP_UNKNOWN];
|
||||
} else {
|
||||
return self::CHECKGROUP_NAMES[$checkgroupvalue];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for the specified checkgroup name, or the unknown value if no valid value for the name.
|
||||
* @param string $checkgroupname
|
||||
* @return int
|
||||
*/
|
||||
final public static function checkgroup_value(string $checkgroupname): int {
|
||||
$value = array_search($checkgroupname, self::CHECKGROUP_NAMES);
|
||||
return ($value !== false) ? $value : self::CHECKGROUP_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the defined content type.
|
||||
* @return int
|
||||
*/
|
||||
protected function get_type(): int {
|
||||
return self::TYPE_FIELD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the component from the full class name.
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function get_component(): string {
|
||||
$parts = preg_split('|\\\\|', get_class($this));
|
||||
return $parts[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the system plugin is avaliable.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_available(): bool {
|
||||
list($type, $plugin) = \core_component::normalize_component($this->get_component());
|
||||
if ($type === 'core') {
|
||||
// We assume that all core components are defined corretly.
|
||||
return true;
|
||||
}
|
||||
// Some contrib plugins may not be installed.
|
||||
return ($dir = \core_component::get_component_directory($this->get_component()))
|
||||
&& file_exists($dir . '/version.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the database table where information is stored
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_tablename(): string;
|
||||
|
||||
/**
|
||||
* Return the name of the reference data table name.
|
||||
* @return string
|
||||
*/
|
||||
public function get_ref_tablename(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the field in the table that has the content
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_fieldname(): string;
|
||||
|
||||
/**
|
||||
* Return a recordset of the relevant areas for the component/module.
|
||||
* @param \core\event\base $event
|
||||
* @return \moodle_recordset|null
|
||||
*/
|
||||
abstract public function find_relevant_areas(\core\event\base $event): ?\moodle_recordset;
|
||||
|
||||
/**
|
||||
* Return a recordset of the course areas for the course id.
|
||||
* @param int $courseid
|
||||
* @return \moodle_recordset|null
|
||||
*/
|
||||
abstract public function find_course_areas(int $courseid): ?\moodle_recordset;
|
||||
|
||||
/**
|
||||
* Return an array of area objects that contain content at the site and system levels only. Override this where necessary.
|
||||
* @return \moodle_recordset|null
|
||||
*/
|
||||
public function find_system_areas(): ?\moodle_recordset {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard Moodle parameter DML parameter substitution doesn't work on all versions of MySQL or Postgres, so we need to use
|
||||
* inline function substitution to ensure that the left side is a string.
|
||||
* @return string
|
||||
*/
|
||||
public function get_standard_area_fields_sql(): string {
|
||||
return '\'' . $this->get_component() . '\' AS component,
|
||||
\'' . $this->get_tablename() . '\' AS tablename,
|
||||
\'' . $this->get_fieldname() . '\' AS fieldorarea, ';
|
||||
}
|
||||
|
||||
/**
|
||||
* The standard Moodle parameter DML parameter substitution doesn't work on all versions of MySQL or Postgres, so we need to use
|
||||
* inline function substitution to ensure that the left side is a string.
|
||||
* @return string
|
||||
*/
|
||||
public function get_reftable_field_sql(): string {
|
||||
return '\'' . $this->get_ref_tablename() . '\' AS reftable, ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any sql filtering data. Implement in extensions.
|
||||
*
|
||||
* @return null
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function get_courseid_filtering() {
|
||||
$this->filter = '';
|
||||
$this->filterparams = [];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
// The curl class is in filelib.
|
||||
global $CFG;
|
||||
require_once("{$CFG->libdir}/filelib.php");
|
||||
|
||||
use curl;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* Class brickfieldconnect. Contains all function to connect to Brickfield external services.
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @author 2020 Onwards Mike Churchward <mike@brickfieldlabs.ie>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
*/
|
||||
class brickfieldconnect extends curl {
|
||||
|
||||
/** @var string The base api uri. */
|
||||
private static $baseapiuri = 'https://api.mybrickfield.ie/moodle/';
|
||||
|
||||
/** @var array Endpoint details for setting and checking a site registration */
|
||||
const ACTION_CHECK_REGISTRATION = [
|
||||
'endpoint' => 'checkRegister',
|
||||
'method' => 'get',
|
||||
];
|
||||
|
||||
/** @var array Endpoint details for sending site summary data */
|
||||
const ACTION_SEND_SUMMARY = [
|
||||
'endpoint' => 'summary',
|
||||
'method' => 'post',
|
||||
];
|
||||
|
||||
/**
|
||||
* Object method to test whether site is already registered.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_registered(): bool {
|
||||
return !empty($this->get_registration_id_for_credentials());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update registration of this site.
|
||||
* @param string $apikey The API key to use for the registration attempt
|
||||
* @param string $secretkey The secret key to use
|
||||
* @return bool
|
||||
*/
|
||||
public function update_registration(string $apikey, string $secretkey): bool {
|
||||
$registrationid = $this->get_registration_id_for_credentials($apikey, $secretkey);
|
||||
if (empty($registrationid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(new registration())->set_siteid($registrationid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the summary data to Brickfield.
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function send_summary(): bool {
|
||||
// Run a registration check.
|
||||
if (!(new registration())->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$headers = $this->get_common_headers();
|
||||
|
||||
// Sanity-check $headers 'id' value.
|
||||
if (!isset($headers['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->set_headers($headers);
|
||||
$summary = accessibility::get_summary_data($headers['id']);
|
||||
$body = json_encode($summary, JSON_UNESCAPED_SLASHES);
|
||||
$result = json_decode($this->call(self::ACTION_SEND_SUMMARY, $body));
|
||||
if (is_object($result) && ((int)$result->statusCode === 200)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL required for the command.
|
||||
*
|
||||
* @param array $command The command to call, for example see self::ACTION_REGISTER
|
||||
* @return string The complete URL
|
||||
*/
|
||||
protected function get_url_for_command(array $command): string {
|
||||
return $this->get_baseapiuri() . $command['endpoint'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the specified command.
|
||||
*
|
||||
* @param array $command The command to call, for example see self::ACTION_REGISTER
|
||||
* @param array|string $params The params provided to the call
|
||||
* @return string The response body
|
||||
*/
|
||||
protected function call(array $command, $params = ''): string {
|
||||
$url = $this->get_url_for_command($command);
|
||||
if ($command['method'] === 'get') {
|
||||
return $this->get($url, $params);
|
||||
}
|
||||
|
||||
if ($command['method'] === 'post') {
|
||||
return $this->post($url, $params);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the common headers used for all calls to the Brickfields endpoints.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_common_headers(): array {
|
||||
$headers = [
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Content-Type' => 'application/json',
|
||||
'siteurl' => static::get_siteurl(),
|
||||
];
|
||||
|
||||
if (static::has_registration_key()) {
|
||||
$registration = new registration();
|
||||
$headers['secret'] = $registration->get_api_key();
|
||||
$headers['userhash'] = $registration->get_secret_key();
|
||||
$headers['id'] = $registration->get_siteid();
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set headers on the request from the specified list of headers.
|
||||
*
|
||||
* @param array $headers An array of header name => value
|
||||
*/
|
||||
protected function set_headers(array $headers) {
|
||||
foreach ($headers as $key => $value) {
|
||||
$this->setHeader("{$key}: {$value}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the site currently has a registration key stored.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function has_registration_key(): bool {
|
||||
$registration = new registration();
|
||||
$localkey = $registration->get_api_key();
|
||||
$localhash = $registration->get_secret_key();
|
||||
$localid = $registration->get_siteid();
|
||||
|
||||
if (!$localhash || !$localkey || !$localid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a normalised URL for the site.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_siteurl(): string {
|
||||
return (new moodle_url('/'))->out(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the registration ID for the given set of credentials.
|
||||
* @param null|string $apikey The API key to use for the registration attempt
|
||||
* @param null|string $secretkey The secret key to use
|
||||
* @return null|string The registration ID if registration was successful, or null if not
|
||||
*/
|
||||
protected function get_registration_id_for_credentials(string $apikey = null, string $secretkey = null): string {
|
||||
$headers = $this->get_common_headers();
|
||||
if ($apikey || $secretkey) {
|
||||
$headers['secret'] = $apikey;
|
||||
$headers['userhash'] = $secretkey;
|
||||
} else if (!$this->has_registration_key()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->set_headers($headers);
|
||||
$response = $this->call(self::ACTION_CHECK_REGISTRATION);
|
||||
|
||||
if ((int)$this->info['http_code'] !== 200) {
|
||||
// The response was unsuccessful.
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = json_decode($response);
|
||||
if (!$result) {
|
||||
// The response could not be decoded.
|
||||
return '';
|
||||
}
|
||||
|
||||
if ((int)$result->statusCode !== 200) {
|
||||
// The data from the response suggests a failure.
|
||||
return '';
|
||||
}
|
||||
|
||||
// Decode the actual result.
|
||||
$registrationdata = json_decode($result->body);
|
||||
if (empty($registrationdata) || !is_array($registrationdata)) {
|
||||
// Unable to decode the body of the response.
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!property_exists($registrationdata[0], 'id') || !property_exists($registrationdata[0]->id, 'N')) {
|
||||
// Unable to find a valid id in the response.
|
||||
return '';
|
||||
}
|
||||
|
||||
return $registrationdata[0]->id->N;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the check registration API URI.
|
||||
* @return string
|
||||
* @throws dml_exception
|
||||
*/
|
||||
protected function get_baseapiuri(): string {
|
||||
$baseapiuri = get_config(manager::PLUGINNAME, 'baseapiuri');
|
||||
if (!empty($baseapiuri)) {
|
||||
return $baseapiuri;
|
||||
} else {
|
||||
set_config('baseapiuri', self::$baseapiuri, manager::PLUGINNAME);
|
||||
return self::$baseapiuri;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield\event;
|
||||
|
||||
use tool_brickfield\manager;
|
||||
|
||||
/**
|
||||
* Class analysis_requested
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 onward Brickfield Education Labs, https://www.brickfield.ie
|
||||
* @author Mike Churchward <mike@brickfieldlabs.ie>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
*/
|
||||
class analysis_requested extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'c';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
* @return \lang_string|string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('eventanalysis_requested', manager::PLUGINNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description.
|
||||
* @return \lang_string|string|null
|
||||
*/
|
||||
public function get_description() {
|
||||
return get_string('eventanalysis_requesteddesc', manager::PLUGINNAME, $this->other['course']);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield\event;
|
||||
|
||||
use tool_brickfield\manager;
|
||||
|
||||
/**
|
||||
* Class report_downloaded
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 onward Brickfield Education Labs, https://www.brickfield.ie
|
||||
* @author Mike Churchward <mike@brickfieldlabs.ie>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
*/
|
||||
class report_downloaded extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'r';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
* @return \lang_string|string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('eventreport_downloaded', manager::PLUGINNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description.
|
||||
* @return \lang_string|string|null
|
||||
*/
|
||||
public function get_description() {
|
||||
return get_string('eventreport_downloadeddesc', manager::PLUGINNAME, $this->courseid);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield\event;
|
||||
|
||||
use tool_brickfield\manager;
|
||||
|
||||
/**
|
||||
* Class report_viewed
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 onward Brickfield Education Labs, https://www.brickfield.ie
|
||||
* @author Mike Churchward <mike@brickfieldlabs.ie>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
*/
|
||||
class report_viewed extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'r';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
* @return \lang_string|string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('eventreport_viewed', manager::PLUGINNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description.
|
||||
* @return \lang_string|string|null
|
||||
*/
|
||||
public function get_description() {
|
||||
return get_string('eventreport_vieweddesc', manager::PLUGINNAME, (object)$this->other);
|
||||
}
|
||||
}
|
||||
@@ -1,438 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield;
|
||||
|
||||
use core\event\base;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Observer class containing methods monitoring various events.
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 Brickfield Education Labs https://www.brickfield.ie
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*
|
||||
*/
|
||||
class eventobservers {
|
||||
|
||||
/**
|
||||
* For course events to be handled, the accessibility tool needs to be enabled, and if a courseid is specified, the course
|
||||
* has to have been added to the scheduler.
|
||||
* @param int $courseid
|
||||
* @return bool
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
private static function course_event_should_be_handled(int $courseid): bool {
|
||||
return accessibility::is_accessibility_enabled() && analysis::is_enabled() &&
|
||||
(empty($courseid) || (isset($courseid) && scheduler::is_course_in_schedule($courseid)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Content area altered event observer.
|
||||
* @param base $event The area altered event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
private static function area_altered(base $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
manager::find_new_or_updated_areas($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course event observers.
|
||||
* These observer monitors course created / restored / updated events,
|
||||
* then its HTML content is processed with accessibility checking.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Course created event observer.
|
||||
* @param \core\event\course_created $event The course created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_created(\core\event\course_created $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
// Need to trigger rerun check for automatically created forum...
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->item = 'coursererun';
|
||||
static::observer_insert($data);
|
||||
static::course_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course deleted event observer.
|
||||
* @param \core\event\course_deleted $event The course deleted event.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_deleted(\core\event\course_deleted $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->contextid = -1;
|
||||
$data->item = 'core_course';
|
||||
static::observer_insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course restored event observer.
|
||||
* @param \core\event\course_restored $event The course restored event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_restored(\core\event\course_restored $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->item = 'coursererun';
|
||||
static::observer_insert($data);
|
||||
static::course_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course update event observer.
|
||||
* @param \core\event\course_updated $event The course updated event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_updated(\core\event\course_updated $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::course_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course update event observer. This is called on both course_created and course_updated, so use the base class as a type hint.
|
||||
* @param base $event The course updated event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_altered(base $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
// Need to rerun caching of allowed courseid, from category white/blacklist.
|
||||
$data = new stdClass();
|
||||
$data->courseid = $data->contextid = $event->courseid;
|
||||
$data->contextid = -1;
|
||||
$data->item = 'category';
|
||||
static::observer_insert($data);
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course section created event observer.
|
||||
* @param \core\event\course_section_created $event The course section created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_section_created(\core\event\course_section_created $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course section deleted event observer.
|
||||
* @param \core\event\course_section_deleted $event The course section deleted event.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_section_deleted(\core\event\course_section_deleted $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->contextid = -1;
|
||||
$data->innercontextid = $event->objectid;
|
||||
$data->item = 'course_sections';
|
||||
static::observer_insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course section update event observer.
|
||||
* @param \core\event\course_section_updated $event The course section updated event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_section_updated(\core\event\course_section_updated $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course module event observers.
|
||||
* These observer monitors course module created / restored / updated events,
|
||||
* then its HTML content is processed with accessibility checking.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Course module created event observer.
|
||||
* @param \core\event\course_module_created $event The course module created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_module_created(\core\event\course_module_created $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course module deleted event observer.
|
||||
* @param \core\event\course_module_deleted $event The course module deleted event.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_module_deleted(\core\event\course_module_deleted $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->contextid = -1;
|
||||
$data->innercontextid = $event->other['instanceid'];
|
||||
$data->item = 'mod_' . $event->other['modulename'];
|
||||
static::observer_insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course module restored event observer.
|
||||
* @param \core\event\course_module_restored $event The course module restored event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_module_restored(\core\event\course_module_restored $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Course module updated event observer.
|
||||
* @param \core\event\course_module_updated $event The course module updated event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_module_updated(\core\event\course_module_updated $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* course_category created event observer.
|
||||
* @param \core\event\course_category_created $event The course_category created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_category_created(\core\event\course_category_created $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* course_category deleted event observer.
|
||||
* @param \core\event\course_category_deleted $event The course_category deleted event.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_category_deleted(\core\event\course_category_deleted $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
$data = new stdClass();
|
||||
$data->contextid = $data->courseid = -1;
|
||||
$data->innercontextid = $event->objectid;
|
||||
$data->item = 'course_categories';
|
||||
static::observer_insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* course_category update event observer.
|
||||
* @param \core\event\course_category_updated $event The course_category updated event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function course_category_updated(\core\event\course_category_updated $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mod_lesson_page created event observer.
|
||||
* @param \mod_lesson\event\page_created $event The mod_lesson page created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function mod_lesson_page_created(\mod_lesson\event\page_created $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mod_lesson_page deleted event observer.
|
||||
* @param \mod_lesson\event\page_deleted $event The mod_lesson page deleted event.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function mod_lesson_page_deleted(\mod_lesson\event\page_deleted $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->contextid = -1;
|
||||
$data->innercontextid = $event->objectid;
|
||||
$data->item = 'lesson_pages';
|
||||
static::observer_insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mod_lesson_page updated event observer.
|
||||
* @param \mod_lesson\event\page_updated $event The mod_lesson page updated event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function mod_lesson_page_updated(\mod_lesson\event\page_updated $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* core_question created observer
|
||||
* @param \core\event\question_created $event The core_question created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function core_question_created(\core\event\question_created $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
manager::find_new_or_updated_areas($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* core_question updated observer
|
||||
* @param \core\event\question_updated $event The core_question created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function core_question_updated(\core\event\question_updated $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
manager::find_new_or_updated_areas($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* core_question deleted observer
|
||||
* @param \core\event\question_deleted $event The core_question deleted event.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function core_question_deleted(\core\event\question_deleted $event) {
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($event->courseid)) {
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->contextid = -1;
|
||||
$data->innercontextid = $event->objectid;
|
||||
$data->item = $event->objecttable;
|
||||
static::observer_insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Book chapter created event observer.
|
||||
* @param \mod_book\event\chapter_created $event The book chapter created event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function book_chapter_created(\mod_book\event\chapter_created $event) {
|
||||
// If this feature has been disabled, do nothing.
|
||||
if (accessibility::is_accessibility_enabled()) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Book chapter deleted event observer.
|
||||
* @param \mod_book\event\chapter_deleted $event The book chapter deleted event.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function book_chapter_deleted(\mod_book\event\chapter_deleted $event) {
|
||||
// If this feature has been disabled, do nothing.
|
||||
if (accessibility::is_accessibility_enabled()) {
|
||||
$data = new stdClass();
|
||||
$data->courseid = $event->courseid;
|
||||
$data->contextid = -1;
|
||||
$data->innercontextid = $event->objectid;
|
||||
$data->item = 'book_chapters';
|
||||
static::observer_insert($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Book chapter update event observer.
|
||||
* @param \mod_book\event\chapter_updated $event The book chapter updated event.
|
||||
* @throws \ReflectionException
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function book_chapter_updated(\mod_book\event\chapter_updated $event) {
|
||||
// If this feature has been disabled, do nothing.
|
||||
if (accessibility::is_accessibility_enabled()) {
|
||||
static::area_altered($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an observer record if appropriate.
|
||||
* @param stdClass $data
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
private static function observer_insert(stdClass $data) {
|
||||
global $DB;
|
||||
|
||||
// Handle if this feature is enabled and this course is in the schedule.
|
||||
if (static::course_event_should_be_handled($data->courseid)) {
|
||||
$data->timecreated = time();
|
||||
$data->timecompleted = 0;
|
||||
|
||||
$DB->insert_record(manager::DB_PROCESS, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Class registration_form
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2021 Brickfield Education Labs https://www.brickfield.ie
|
||||
* @author 2020 JM Tomas <jmtomas@tresipunt.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
*/
|
||||
|
||||
namespace tool_brickfield\form;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use dml_exception;
|
||||
use html_writer;
|
||||
use moodle_exception;
|
||||
use moodleform;
|
||||
use stdClass;
|
||||
use tool_brickfield\manager;
|
||||
use tool_brickfield\registration;
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/formslib.php');
|
||||
|
||||
/**
|
||||
* Class registration_form
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @author 2020 JM Tomas <jmtomas@tresipunt.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
*/
|
||||
class registration_form extends moodleform {
|
||||
|
||||
/**
|
||||
* Form definition.
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = & $this->_form;
|
||||
$required = get_string('required');
|
||||
$info = $this->get_defaultinfo();
|
||||
$registration = new registration();
|
||||
$key = $registration->get_api_key();
|
||||
$hash = $registration->get_secret_key();
|
||||
|
||||
$mform->addElement('header', 'activationheader', get_string('activationheader', manager::PLUGINNAME));
|
||||
$mform->addElement('text', 'key', get_string('secretkey', manager::PLUGINNAME));
|
||||
$mform->setType('key', PARAM_TEXT);
|
||||
$mform->setDefault('key', !empty($key) ? $key : '');
|
||||
$mform->addHelpButton('key', 'secretkey', manager::PLUGINNAME);
|
||||
|
||||
$mform->addElement('text', 'hash', get_string('sitehash', manager::PLUGINNAME));
|
||||
$mform->setType('hash', PARAM_TEXT);
|
||||
$mform->setDefault('hash', !empty($hash) ? $hash : '');
|
||||
$mform->addHelpButton('hash', 'sitehash', manager::PLUGINNAME);
|
||||
|
||||
$mform->addElement('header', 'moreinfo', get_string('moreinfo', manager::PLUGINNAME));
|
||||
$mform->addElement('static', 'siteinfosummary', '',
|
||||
get_string('sendfollowinginfo', manager::PLUGINNAME, $info->moreinfostring));
|
||||
|
||||
$mform->addElement('hidden', 'lang', $info->languagecode);
|
||||
$mform->setType('lang', PARAM_TEXT);
|
||||
$mform->addElement('hidden', 'countrycode', $info->country);
|
||||
$mform->setType('countrycode', PARAM_TEXT);
|
||||
$mform->addElement('hidden', 'url', $info->url);
|
||||
$mform->setType('url', PARAM_URL);
|
||||
|
||||
$this->add_action_buttons(false, get_string('activate', manager::PLUGINNAME, '#'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default data for registration form
|
||||
*
|
||||
* @throws moodle_exception
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function get_defaultinfo(): stdClass {
|
||||
global $CFG;
|
||||
$admin = get_admin();
|
||||
$site = get_site();
|
||||
$data = new stdClass();
|
||||
$data->name = $site->fullname;
|
||||
$data->url = $CFG->wwwroot;
|
||||
$data->language = get_string('thislanguage', 'langconfig');
|
||||
$data->languagecode = $admin->lang ?: $CFG->lang;
|
||||
$data->country = $admin->country ?: $CFG->country;
|
||||
$data->email = $admin->email;
|
||||
$data->moreinfo = self::get_moreinfo();
|
||||
$data->moreinfostring = self::get_moreinfostring($data->moreinfo);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more information.
|
||||
* @return array
|
||||
* @throws dml_exception
|
||||
*/
|
||||
private static function get_moreinfo(): array {
|
||||
global $CFG, $DB;
|
||||
$moreinfo = array();
|
||||
$moodlerelease = $CFG->release;
|
||||
if (preg_match('/^(\d+\.\d.*?)[. ]/', $moodlerelease, $matches)) {
|
||||
$moodlerelease = $matches[1];
|
||||
}
|
||||
$moreinfo['release'] = $moodlerelease;
|
||||
$moreinfo['numcourses'] = $DB->count_records('course') - 1;
|
||||
$moreinfo['numusers'] = $DB->count_records('user', array('deleted' => 0));
|
||||
$moreinfo['numfiles'] = $DB->count_records('files');
|
||||
$moreinfo['numfactivities'] = $DB->count_records('course_modules');
|
||||
$moreinfo['mobileservice'] = empty($CFG->enablemobilewebservice) ? 0 : $CFG->enablemobilewebservice;
|
||||
$moreinfo['usersmobileregistered'] = $DB->count_records('user_devices');
|
||||
$moreinfo['contentyperesults'] = '';
|
||||
$moreinfo['contenttypeerrors'] = '';
|
||||
$moreinfo['percheckerrors'] = '';
|
||||
return $moreinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML list for more information.
|
||||
*
|
||||
* @param array $moreinfo
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
private static function get_moreinfostring(array $moreinfo): string {
|
||||
$html = html_writer::start_tag('ul');
|
||||
foreach ($moreinfo as $key => $value) {
|
||||
$html .= html_writer::tag('li', get_string($key, manager::PLUGINNAME, $value));
|
||||
}
|
||||
$html .= html_writer::end_tag('ul');
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
// This file is part of the Query submission plugin
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
namespace tool_brickfield\local\areas\core_course;
|
||||
|
||||
use core\event\course_created;
|
||||
use core\event\course_updated;
|
||||
use core\event\course_restored;
|
||||
use tool_brickfield\area_base;
|
||||
|
||||
/**
|
||||
* Base class for various course-related areas
|
||||
*
|
||||
* This is an abstract class so it will be skipped by manager when it finds all areas
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 onward: Brickfield Education Labs, www.brickfield.ie
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base extends area_base {
|
||||
|
||||
/**
|
||||
* Find recordset of the relevant areas.
|
||||
* @param \core\event\base $event
|
||||
* @return \moodle_recordset|null
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function find_relevant_areas(\core\event\base $event): ?\moodle_recordset {
|
||||
if ($event instanceof course_created) {
|
||||
return $this->find_fields_in_course_table(['courseid' => $event->courseid]);
|
||||
} else if ($event instanceof course_restored) {
|
||||
return $this->find_fields_in_course_table(['courseid' => $event->courseid]);
|
||||
} else if ($event instanceof course_updated) {
|
||||
return $this->find_fields_in_course_table(['courseid' => $event->courseid]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find recordset of the course areas.
|
||||
* @param int $courseid
|
||||
* @return \moodle_recordset
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function find_course_areas(int $courseid): ?\moodle_recordset {
|
||||
return $this->find_fields_in_course_table(['courseid' => $courseid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of area objects that contain content at the site and system levels only.
|
||||
* @return mixed
|
||||
*/
|
||||
public function find_system_areas(): ?\moodle_recordset {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that can be used by the classes that define a field in the 'course' table
|
||||
*
|
||||
* @param array $params
|
||||
* @return \moodle_recordset
|
||||
* @throws \coding_exception
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
protected function find_fields_in_course_table(array $params = []): \moodle_recordset {
|
||||
global $DB;
|
||||
$where = [];
|
||||
|
||||
if (!empty($params['courseid'])) {
|
||||
$where[] = 't.id = :courseid';
|
||||
}
|
||||
|
||||
$rs = $DB->get_recordset_sql('SELECT
|
||||
' . $this->get_type() . ' AS type,
|
||||
ctx.id AS contextid,
|
||||
' . $this->get_standard_area_fields_sql() . '
|
||||
t.id AS itemid,
|
||||
t.id AS courseid,
|
||||
t.'.$this->get_fieldname().' AS content
|
||||
FROM {'.$this->get_tablename().'} t
|
||||
JOIN {context} ctx ON ctx.instanceid = t.id AND ctx.contextlevel = :pctxlevelcourse '.
|
||||
($where ? 'WHERE ' . join(' AND ', $where) : '') . '
|
||||
ORDER BY t.id',
|
||||
['pctxlevelcourse' => CONTEXT_COURSE] + $params);
|
||||
|
||||
return $rs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the moodle_url of the page to edit the error.
|
||||
* @param \stdClass $componentinfo
|
||||
* @return \moodle_url
|
||||
*/
|
||||
public static function get_edit_url(\stdClass $componentinfo): \moodle_url {
|
||||
if ($componentinfo->tablename == 'course_sections') {
|
||||
return new \moodle_url('/course/editsection.php', ['id' => $componentinfo->itemid, 'sr' => '']);
|
||||
} else if ($componentinfo->tablename == 'course_categories') {
|
||||
return new \moodle_url('/course/editcategory.php', ['id' => $componentinfo->itemid]);
|
||||
} else {
|
||||
return new \moodle_url('/course/edit.php', ['id' => $componentinfo->courseid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace tool_brickfield\local\areas\core_course;
|
||||
|
||||
use core\event\course_category_created;
|
||||
use core\event\course_category_updated;
|
||||
use tool_brickfield\area_base;
|
||||
|
||||
/**
|
||||
* Base class for all areas that represent a field from the course_categories table (such as 'intro' or 'name')
|
||||
*
|
||||
* @package tool_brickfield
|
||||
* @copyright 2020 onward: Brickfield Education Labs, www.brickfield.ie
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class category_base extends area_base {
|
||||
|
||||
/**
|
||||
* Find recordset of the relevant areas.
|
||||
* @param \core\event\base $event
|
||||
* @return \moodle_recordset|null
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function find_relevant_areas(\core\event\base $event): ?\moodle_recordset {
|
||||
if ($event instanceof course_category_updated || $event instanceof course_category_created) {
|
||||
return $this->find_fields_in_course_categories_table(['itemid' => $event->objectid]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find recordset of the course areas.
|
||||
* @param int $courseid
|
||||
* @return \moodle_recordset|null
|
||||
*/
|
||||
public function find_course_areas(int $courseid): ?\moodle_recordset {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of area objects that contain content at the site and system levels only. All category content is considered
|
||||
* system level.
|
||||
* @return mixed
|
||||
*/
|
||||
public function find_system_areas(): ?\moodle_recordset {
|
||||
return $this->find_fields_in_course_categories_table();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that can be used by the classes that define a field in the respective module table
|
||||
*
|
||||
* @param array $params
|
||||
* @return \moodle_recordset
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
protected function find_fields_in_course_categories_table(array $params = []): \moodle_recordset {
|
||||
global $DB;
|
||||
$where = [];
|
||||
if (!empty($params['itemid'])) {
|
||||
$where[] = 't.id = :itemid';
|
||||
}
|
||||
|
||||
$rs = $DB->get_recordset_sql('SELECT
|
||||
' . $this->get_type() . ' AS type,
|
||||
ctx.id AS contextid,
|
||||
' . $this->get_standard_area_fields_sql() . '
|
||||
t.id AS itemid,
|
||||
t.id AS categoryid,
|
||||
t.'.$this->get_fieldname().' AS content
|
||||
FROM {'.$this->get_tablename().'} t
|
||||
JOIN {context} ctx ON ctx.instanceid = t.id AND ctx.contextlevel = :pctxlevelcategory '.
|
||||
($where ? 'WHERE ' . join(' AND ', $where) : '') . '
|
||||
ORDER BY t.id',
|
||||
['pctxlevelcategory' => CONTEXT_COURSECAT] + $params);
|
||||
|
||||
return $rs;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user