Compare commits
705 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f84dd778b | |||
| fb7872a5d3 | |||
| 67dd781b75 | |||
| 5780b8c228 | |||
| 8116bf4059 | |||
| 53f840cd4a | |||
| a32d53f1aa | |||
| 556a285702 | |||
| 126d617b77 | |||
| 8a344dbe0b | |||
| 88828ee3af | |||
| 393f787b8f | |||
| 4b9f6e77d3 | |||
| 4528d12f52 | |||
| 30e1117ed1 | |||
| ff90193c15 | |||
| f2b0e454e6 | |||
| 2264b79dbe | |||
| b03d9fcc3f | |||
| f13b4e0caf | |||
| 15b019781d | |||
| fff830198f | |||
| 45a503f086 | |||
| a184b0b1fb | |||
| 3feb4e3b22 | |||
| 571be8e18c | |||
| a20fdb53f5 | |||
| 6a8d0fb2b0 | |||
| 2283877ba6 | |||
| d6cd2eb5fe | |||
| f32d3af553 | |||
| 224dbd8063 | |||
| 18d692f70b | |||
| 219899c8ce | |||
| 4f45925082 | |||
| 84150d8909 | |||
| 7a79c4d751 | |||
| b0f737dbb9 | |||
| 5907407061 | |||
| 427b0d3fee | |||
| 5ea392de64 | |||
| 3ed8b67746 | |||
| f8d5db3ea7 | |||
| b681fa9b5f | |||
| 44dd499b96 | |||
| bc763d3c0a | |||
| 0492cbf9b7 | |||
| 437738f488 | |||
| 3932499ea6 | |||
| ad4046a5a2 | |||
| df7b18b9bc | |||
| ad287dc5e5 | |||
| b9c148dbde | |||
| 0f64786579 | |||
| 1011b7166b | |||
| ee44b0bf5c | |||
| db242167a3 | |||
| 510bbe8a53 | |||
| 42f5f85440 | |||
| 226ed0720c | |||
| 7a8178bb64 | |||
| 7ddd5b33e7 | |||
| 54bbef1f5b | |||
| e756e542d0 | |||
| 468be1ea44 | |||
| 2209f6a8f1 | |||
| 4a07c82f34 | |||
| cfcf3ab6a9 | |||
| cc4a3ee105 | |||
| eee0a8e89f | |||
| 48fbf7ed14 | |||
| 8387191264 | |||
| 44a72bc059 | |||
| b7d38b140f | |||
| 36febde242 | |||
| a7bc47969e | |||
| f2358a0d24 | |||
| 6505fbbdb1 | |||
| cfd56552f7 | |||
| 911d28ca9c | |||
| 74b3080301 | |||
| 9d3ca32e49 | |||
| 4791e99ecd | |||
| 52695bbb94 | |||
| 2e3a273b53 | |||
| e991ae96e2 | |||
| 3c78b6304e | |||
| 9e31deb6d8 | |||
| 8df121e399 | |||
| e7ebe317a5 | |||
| 455a187b0a | |||
| fa924d4936 | |||
| 10d5a2bb2a | |||
| 747c921e6c | |||
| 43922d6125 | |||
| af5161261c | |||
| 7368993bb8 | |||
| c0930849d6 | |||
| 6142dd41f2 | |||
| 1f2ac8c548 | |||
| 9c23c1735e | |||
| ad243c92e5 | |||
| b6121b9d1a | |||
| 40ae05718c | |||
| 0739484ac1 | |||
| c7f9966a35 | |||
| 4a68477a49 | |||
| 73ea78fcc9 | |||
| 3ff7d53def | |||
| 14503ba527 | |||
| 4e65a8f094 | |||
| b684c13efc | |||
| 7b9d56e0df | |||
| 9a49d022aa | |||
| 068eec99a8 | |||
| 12704fb808 | |||
| 800acd242a | |||
| bca168b02c | |||
| b6a66ef378 | |||
| 4e62e08558 | |||
| 4a8cd5019f | |||
| 101d476c33 | |||
| 6080ef9fe7 | |||
| aae5c2b7db | |||
| de6f5d6333 | |||
| ae60d04ffa | |||
| 554b7a2bc4 | |||
| 64909575cf | |||
| 1d8dd426aa | |||
| 5c24699cbb | |||
| 1129d43162 | |||
| a538c90eec | |||
| ef367f6b6e | |||
| 569d925366 | |||
| 9548f6bef3 | |||
| 663e4efbd6 | |||
| e74cdb09a4 | |||
| 2fee3b63b0 | |||
| 324b256141 | |||
| 42074afdfa | |||
| 7aaab961cc | |||
| abde115855 | |||
| 378614ed32 | |||
| 7d533d3f0f | |||
| 9f0ab79ba9 | |||
| 2a7697dc75 | |||
| b1c6e30a90 | |||
| e8c1759b02 | |||
| 22c10c8b14 | |||
| e06ad7179e | |||
| deb73ef1a4 | |||
| 18b04fed1b | |||
| d26d56ee34 | |||
| 6f70b7f650 | |||
| 2f024231da | |||
| 54bf8d378c | |||
| 0ac0b5dbb1 | |||
| 5ba3b9e9a9 | |||
| 67f5534d53 | |||
| 3b53984d0d | |||
| 48ec9b3bd2 | |||
| beb8242c98 | |||
| d132858820 | |||
| 7d4f09b978 | |||
| 8c719efc1b | |||
| 198a42a084 | |||
| da9bbf5a96 | |||
| a8fe1be288 | |||
| d44fe7013d | |||
| 010d2ffb83 | |||
| e679f8f11f | |||
| 9f7163869e | |||
| 6435680f5a | |||
| 316865e2ae | |||
| 873baf6623 | |||
| 466009c2a4 | |||
| 74a3fa91e4 | |||
| 3beb0e2080 | |||
| fa50ca4ba1 | |||
| eb1eee3b71 | |||
| 32487e36d9 | |||
| ccbb6bd37e | |||
| ccec3cf2e1 | |||
| 84662768ec | |||
| 41b9fb7623 | |||
| 9d3e70fe06 | |||
| f44de225ac | |||
| 87617b8daa | |||
| 6ff1d5393a | |||
| 782e402138 | |||
| 02dc1a1079 | |||
| 4064887a19 | |||
| fc8bbc49ea | |||
| d90f7daba1 | |||
| acecd19472 | |||
| 5881620471 | |||
| 06f9ad8a38 | |||
| f7010a6d9b | |||
| 5cd72ef46d | |||
| 24585543ba | |||
| becb952d29 | |||
| dd660845cc | |||
| d56b3a7911 | |||
| 887da1cd67 | |||
| 827446a7ba | |||
| 82244afc3a | |||
| 4d0d8f7f1b | |||
| 835b2d01cc | |||
| 1d54f1f1f9 | |||
| 144f2f0a52 | |||
| 05dc4a42d5 | |||
| 8145ad6b6a | |||
| aa0b05ff8e | |||
| c5713d6b76 | |||
| bf61a48eeb | |||
| 806dcd5eee | |||
| 8929efb8b5 | |||
| b6d4be7a3c | |||
| 97e996c764 | |||
| e7d19f0cb3 | |||
| 70f7b59c3c | |||
| 6249537886 | |||
| f08dadcdf8 | |||
| 631d94a872 | |||
| 796935efc2 | |||
| a58f004545 | |||
| 42b22b46fe | |||
| 99f60697ec | |||
| f192195a76 | |||
| 22e5c8aba6 | |||
| 9e8c90c86c | |||
| b0c3178932 | |||
| 38f2164bfe | |||
| fa51aa95a6 | |||
| b149b40a9c | |||
| d369e3628e | |||
| f5192ab0b3 | |||
| 0e676bd8c2 | |||
| 076379a485 | |||
| 5a94a957bc | |||
| 22c6b1a162 | |||
| a83ac73434 | |||
| 50dd5eaab1 | |||
| 4743212ffb | |||
| f07be5e649 | |||
| 3ccc4d1681 | |||
| 4d52b09e84 | |||
| 2fe32ba8cf | |||
| a2085cbfaf | |||
| f1d798a1e0 | |||
| c86be8ac94 | |||
| a1cf1082b7 | |||
| d239ef73c9 | |||
| 36c5f8d2e0 | |||
| 85fb00c041 | |||
| 0f752d0bca | |||
| f7829013e5 | |||
| 0953e51829 | |||
| aba3431ae9 | |||
| 1a7ab557b6 | |||
| 142df0645c | |||
| a1f2d724cc | |||
| fb59440170 | |||
| f91e98828d | |||
| f29220e39d | |||
| f84479e3d6 | |||
| 6fb76aa958 | |||
| 4e2b5137f4 | |||
| 5665addea4 | |||
| 8357d9b18f | |||
| 4a2aa40512 | |||
| a53b8e1d2c | |||
| 96a452dd41 | |||
| 42df379616 | |||
| e600e8e6b4 | |||
| d5d0774fec | |||
| 8b571da7fe | |||
| d1b7d46143 | |||
| 381755e1f9 | |||
| f8dec8bfb2 | |||
| 8cc5641be0 | |||
| 40ae0fe34e | |||
| 2d462e1d39 | |||
| f810374a0e | |||
| 3eca273205 | |||
| 6c73be6e8e | |||
| b8fe6ee6c3 | |||
| 5a7599385f | |||
| eae457d10d | |||
| 56eb910aed | |||
| 11dcef291d | |||
| 42cbafd610 | |||
| 7af3c41dd3 | |||
| b60adc89ec | |||
| fb1752711e | |||
| 3919d8e3b0 | |||
| 9d291854a5 | |||
| 77a5658f0e | |||
| bbfbc8abd1 | |||
| def6645cdc | |||
| 99eb359503 | |||
| b9698228ab | |||
| cdb365b9f4 | |||
| 0f4b02411e | |||
| ae93fa2696 | |||
| 6f344ff877 | |||
| 68c20d41fe | |||
| d916077929 | |||
| a432e1767f | |||
| 7a0a94b6b2 | |||
| 4272fc3f28 | |||
| 1bd5dfe090 | |||
| f8d0e86350 | |||
| e092c75d15 | |||
| 90b3f5fc26 | |||
| c77bef3ad7 | |||
| 260eef0a72 | |||
| af0fe95d84 | |||
| ee0c928f10 | |||
| d1796d1f0e | |||
| ac2acd0072 | |||
| 155abc6945 | |||
| cf0b182dee | |||
| f863084cc9 | |||
| 2ddad8ce7a | |||
| 8d45a9f2a2 | |||
| 4b0a34f08a | |||
| 3a7f291d38 | |||
| bbfc6312a8 | |||
| d6df348ec4 | |||
| 61f8a1fe55 | |||
| 67666fda0e | |||
| 77fe4a8e3e | |||
| fe8d759b17 | |||
| bf3a6a122f | |||
| e368e04824 | |||
| 2671324c9e | |||
| 983921ecff | |||
| f72244a8c2 | |||
| d09f737c7d | |||
| b654282b18 | |||
| bdb5a4ba40 | |||
| 419d7f40c3 | |||
| 7be05aae41 | |||
| 04261e8722 | |||
| a53121b74a | |||
| 6814c42260 | |||
| 658953e34f | |||
| c2c03f203f | |||
| 9a1a58e593 | |||
| 0f63155d58 | |||
| 25e00b5e1f | |||
| b2bc3b9e7e | |||
| 38ed732727 | |||
| 463f16617e | |||
| 260924aeb8 | |||
| d7a1ef5dfa | |||
| b38413afe4 | |||
| 05ab53734b | |||
| 708732ed72 | |||
| 121d6fb20a | |||
| e4acb387bb | |||
| ff74bb2682 | |||
| 17fb4afaab | |||
| 0cb2fd1a7f | |||
| 60541d90e5 | |||
| 4b26df011f | |||
| fbecc7132d | |||
| bbea6e1d31 | |||
| 9e3ca2a866 | |||
| 40ee4ab3fa | |||
| d40fc8708a | |||
| 2a60bc604f | |||
| e8237560d3 | |||
| 258783a754 | |||
| 81de0469c1 | |||
| 638cbfac93 | |||
| 85c70539df | |||
| 5b442418ad | |||
| 9c641bf38e | |||
| f9797aca63 | |||
| d4b1ecbed4 | |||
| d140771110 | |||
| 762df4050c | |||
| a56003588a | |||
| bee2d552b7 | |||
| f172248f20 | |||
| db71e79e05 | |||
| 05c9ce4bd8 | |||
| ade9f7177a | |||
| 05846ae405 | |||
| e186ad572d | |||
| 9f636a3de3 | |||
| d79ad5de6a | |||
| b7c8f7f723 | |||
| 46192ca77e | |||
| d68ea95f26 | |||
| 85af2e6a7c | |||
| 4ac4b93064 | |||
| 136aef1fe6 | |||
| 79ea6a2d22 | |||
| e2f5104676 | |||
| bc133f6fc3 | |||
| bea6119692 | |||
| b04fcb4527 | |||
| b4278ca6b0 | |||
| 9785551527 | |||
| 533074d453 | |||
| d20c47f89c | |||
| d29433ddc6 | |||
| 0a494aa9f6 | |||
| a418e9e53a | |||
| 3f10ec31b5 | |||
| 488a653e50 | |||
| 7fa08a0b35 | |||
| 6a89731462 | |||
| fd8b7fc730 | |||
| 006eaeef08 | |||
| 2862cce2a9 | |||
| e3d76202cd | |||
| 6441cca4d1 | |||
| 04892e4707 | |||
| 3ef7a7d76a | |||
| 8cf880c5de | |||
| 17282deeea | |||
| c2e8975523 | |||
| c5e3ce289e | |||
| 83e442cc69 | |||
| b8f0f87b5f | |||
| 539e5db429 | |||
| 4c3523d987 | |||
| 222b528160 | |||
| a0951e25b3 | |||
| 70a95b8eae | |||
| 5705496179 | |||
| e77912a7c5 | |||
| fb13f6dd01 | |||
| fbeef47a58 | |||
| 8fa5a3008d | |||
| 2c94380acf | |||
| 8cd66034e4 | |||
| 0bb3a2f20a | |||
| 96f06326f8 | |||
| 8b6d43705e | |||
| eb0575859b | |||
| a6ce3c0758 | |||
| a9bfc2075b | |||
| 6a1644abff | |||
| 1bbc0425e3 | |||
| 015b2a0cd8 | |||
| cb18237b5c | |||
| c898074fd8 | |||
| 14b5d0694f | |||
| 54d0894ec3 | |||
| 0b326e9e72 | |||
| eae154a081 | |||
| 42022a1d26 | |||
| c59c0f906b | |||
| e82f1d43c8 | |||
| 6ff6327a6b | |||
| 4b34028221 | |||
| 1e8af88afd | |||
| edbbe29706 | |||
| fc35bc5876 | |||
| 6c5f358ca2 | |||
| 895189107f | |||
| d8c4a57ce8 | |||
| 3d5c60ad26 | |||
| a2cfa93011 | |||
| e40f4e4d83 | |||
| 1f0f185176 | |||
| d5d2c59161 | |||
| c2c357dfb2 | |||
| fccf80c3a3 | |||
| ee26c16e90 | |||
| 26c5ffd356 | |||
| d38211efc6 | |||
| dd844035ac | |||
| 778b8f51b1 | |||
| 4d28563eeb | |||
| 89f3fced22 | |||
| 1ed0cb2562 | |||
| 7e1589b2ee | |||
| bc5d145dfb | |||
| 5aaba2850f | |||
| 3116b46192 | |||
| 3a1aed3578 | |||
| a2544fd5ca | |||
| ebab82c9f3 | |||
| 40e56b3adc | |||
| 0a360e1eb2 | |||
| 59aa25852c | |||
| ba60319c94 | |||
| ad836b6d4c | |||
| 72d7395c96 | |||
| d376741b93 | |||
| ca991dbe29 | |||
| 3a7594c71a | |||
| 9f88fb2125 | |||
| f29c4e36a4 | |||
| a87db5a60d | |||
| 07bbe2fa5e | |||
| 1c11297195 | |||
| 5f59e2a61e | |||
| 7e4119de7e | |||
| c30c1ca9cd | |||
| f50c83ac37 | |||
| c54b573bd0 | |||
| 1f5e140ae9 | |||
| f65d067dec | |||
| dbe914883c | |||
| 783dc4cdb2 | |||
| 26e91219e8 | |||
| 15526541b3 | |||
| c78364eb73 | |||
| 0c9bc880c1 | |||
| 93f282a345 | |||
| 7d4ba18a65 | |||
| e78cbaa6c4 | |||
| 9b401dfc29 | |||
| 3e179325e9 | |||
| 60727a7cdf | |||
| 4d59c788ee | |||
| d5993fffd4 | |||
| 136bd2c4c4 | |||
| b2d8615725 | |||
| 4e880779f9 | |||
| e5efe8b6d1 | |||
| da4a2d6191 | |||
| 84ec153634 | |||
| b69c42db5d | |||
| 55d5f7756d | |||
| b262a67c89 | |||
| 05492ddbb3 | |||
| 185856927d | |||
| 935c2509f1 | |||
| a502fbcd1e | |||
| 061186c215 | |||
| eac49ffa4c | |||
| db82329f6c | |||
| 875ad48c9d | |||
| c627e1a4de | |||
| 3b0165ec67 | |||
| 96bfaf67fd | |||
| fc4354c7e3 | |||
| b5c49b8ac9 | |||
| 2721a7cdc3 | |||
| ab79a0e972 | |||
| 275af6c114 | |||
| dcc8300ef4 | |||
| af62e9773c | |||
| 0ac35f9248 | |||
| a028969791 | |||
| 7bc41babf8 | |||
| 4e08cbd42e | |||
| d3427d45fa | |||
| 67a690d176 | |||
| 81d72254b5 | |||
| b2e58aed7b | |||
| 5ca36b0731 | |||
| b105ec5188 | |||
| a85f7c200b | |||
| f09f15e0be | |||
| e35be12fb7 | |||
| 2f1a67685f | |||
| d3bc17fe56 | |||
| adfd93a303 | |||
| 82f9838f28 | |||
| a18057fd06 | |||
| dbc285e49e | |||
| 31b409d920 | |||
| 501c58729f | |||
| 3cc283a25e | |||
| 188b5021fb | |||
| be7a698223 | |||
| 60a1c97289 | |||
| 92afaa484f | |||
| 9fdb460330 | |||
| ccfc156b43 | |||
| 10f10ee269 | |||
| 47bf3467a2 | |||
| 04d7b13fc4 | |||
| a3e7f7fd14 | |||
| 745b504426 | |||
| 89913c7eeb | |||
| eb658e33c3 | |||
| 03e68d81b4 | |||
| ae80f632bf | |||
| e2079313e2 | |||
| b4bade5ca9 | |||
| 10883ef268 | |||
| 19e1e834a7 | |||
| f30b0eee9b | |||
| 0ad939f062 | |||
| e8f534c4f4 | |||
| 7e22a9c2c7 | |||
| c542a77f11 | |||
| db52a8bc31 | |||
| 4800afbc3e | |||
| 990b740ec1 | |||
| b1d436a281 | |||
| 8bf31f2277 | |||
| 97fc41ebcb | |||
| bac3ffbe79 | |||
| dc7dd96189 | |||
| 67c567062e | |||
| b7ee271c8b | |||
| 81cdd8db81 | |||
| 90d7b45f5f | |||
| 632d4faeec | |||
| aac3b19bff | |||
| a65a8fc40a | |||
| c0bedfd833 | |||
| b72551c475 | |||
| 437967f78e | |||
| e05747e353 | |||
| 4865c40790 | |||
| d63fd0365a | |||
| f054958593 | |||
| 7ee0ee6206 | |||
| cf715c550a | |||
| 440a27c5de | |||
| 2130e4ff8b | |||
| 676626064c | |||
| 655ac0b6a6 | |||
| 28233e5b3d | |||
| e001508f68 | |||
| 9b96653fc5 | |||
| 920ddcb70f | |||
| 82d1033f43 | |||
| 6856da7f09 | |||
| 150d134fa2 | |||
| d30fa9918f | |||
| 343ad024c0 | |||
| 4f8778cfc7 | |||
| b770c56b6d | |||
| 156953f115 | |||
| 07a6698d01 | |||
| 1ec619db69 | |||
| 9f5f8d4a0a | |||
| 810f134cdc | |||
| 7b1a0a0e65 | |||
| 447530591d | |||
| 34223e32ef | |||
| ac3cda51f0 | |||
| f25ac24adf | |||
| c3ba14a052 | |||
| bd844a184c | |||
| c968b1236f | |||
| ff2d542295 | |||
| bf68987951 | |||
| e2049d770c | |||
| 021bdd0e4b | |||
| 9bb59ad004 | |||
| fd1035ecf0 | |||
| 0602fdc0cb | |||
| 1294e309dc | |||
| 596bc2c2bf | |||
| 09568062fd | |||
| b205b758e8 | |||
| 55077e45bb | |||
| 9c4f0d31f2 | |||
| c220ad62dd | |||
| ffc8c116fd | |||
| 8679a15604 | |||
| 9995038273 | |||
| 70ee7b1beb | |||
| 5cc2ab26eb | |||
| cf0446155b | |||
| 87c42699d9 | |||
| 97bcb22dba | |||
| ecd82700a7 | |||
| daa598b1b4 | |||
| 13eb20d8d1 | |||
| d0a9c57c0b | |||
| 21b11cdb3d | |||
| d4b2c50163 | |||
| bd50ac1876 | |||
| da4204c491 | |||
| 780b99229c | |||
| bebde352fe | |||
| 1adf99597e | |||
| 70c1b02809 | |||
| 335c9c920d | |||
| b1ad4fb830 | |||
| 2b5073a222 | |||
| 731b150241 | |||
| 228ee621e6 | |||
| 16f65ba275 | |||
| 165918258d | |||
| 3392a198d7 | |||
| 7cba44007c | |||
| 0004f31e2e | |||
| 83865c7979 | |||
| b43222dc67 | |||
| e857efcf28 | |||
| e7ab69fa26 | |||
| f4743bbb8d | |||
| d1b63c5e18 | |||
| cf5065ed71 | |||
| bf380e2d46 | |||
| 4b40f8273d | |||
| c0f2ff773f | |||
| 2ef18bce0d | |||
| 536780ce38 |
@@ -1,7 +0,0 @@
|
||||
*** PLEASE DO NOT OPEN PULL REQUESTS VIA GITHUB ***
|
||||
|
||||
The moodle.git repository at Github is just a mirror of the official repository. We do not accept pull requests at Github.
|
||||
|
||||
See CONTRIBUTING.md guidelines for how to contribute patches for Moodle. Thank you.
|
||||
|
||||
--
|
||||
@@ -1,6 +0,0 @@
|
||||
> [!IMPORTANT]
|
||||
> PLEASE DO NOT PUBLISH SECURITY FINDINGS PUBLICLY.
|
||||
|
||||
If you discover a potential security issue, please report it via our [Security Submission Form](https://moodle.org/security/report).
|
||||
|
||||
For more information about our security processes and responsible disclosure policy, see the [Security Procedures documentation](https://moodledev.io/general/development/process/security).
|
||||
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1231.24 314.44" preserveAspectRatio="xMinYMid meet"><defs><style>.cls-1{fill:#f48020;}.cls-2,.cls-3{fill:#383738;stroke:#4a4a4c;}.cls-2{stroke-width:2.37px;}.cls-3{stroke-width:2.38px;}.cls-4{fill:#585858;}</style></defs><title>Moodle_Logo_UseonBlack_RGB</title><path class="cls-1" d="M1086.3,235.34a53.42,53.42,0,0,0,16.71,29q13.26,12.27,29.48,12.27,11.29,0,24.56-5.89,16.23-6.88,16.22-16.22l58-.49q-12.29,29-41.76,45.2-27.52,15.23-60.43,15.24-42.25,0-70-26.29t-27.75-68.54q0-43.73,27.75-69.51t72-25.8q48.15,0,75.17,31.94,25.07,29.47,25.06,79.1Zm89.91-33.91q-3.43-17.69-14.73-29-12.8-12.28-29.49-12.29-17.21,0-28.74,11.79t-15.47,29.48Z"/><path class="cls-1" d="M965.45,308.54V0h55V308.54Z"/><path class="cls-1" d="M892.69,308.54V290.36q-7.38,9.83-25.06,15.72a92.31,92.31,0,0,1-30,5.41q-39.31,0-63.13-27T750.7,217.65q0-39.3,23.34-65.84t62.15-26.53q43.71,0,56.5,23.58V0h53.55V308.54Zm0-102.68q0-14.75-14-28.26t-28.74-13.51q-21.12,0-33.41,17.2-10.8,15.24-10.8,37.33,0,21.63,10.8,36.85,12.29,17.71,33.41,17.69,12.78,0,27.76-12t15-24.81Z"/><path class="cls-1" d="M647,313.45q-41.77,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.75,0,69.27,26.53t27.52,68.28q0,41.79-27.52,68.3T647,313.45ZM647,168q-19.89,0-30.58,15.07t-10.68,35.79q0,20.73,9.94,35.05,11.43,16.31,31.32,16.28t31.32-16.28q10.44-14.31,10.44-35.05t-9.94-35Q667.42,168,647,168Z"/><path class="cls-1" d="M448.07,313.45q-41.76,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.74,0,69.28,26.53t27.5,68.28q0,41.79-27.5,68.3T448.07,313.45Zm0-145.43q-19.89,0-30.59,15.07t-10.69,35.79q0,20.73,9.94,35.05,11.43,16.31,31.34,16.28t31.32-16.28q10.42-14.31,10.44-35.05t-9.94-35Q468.45,168,448.07,168Z"/><path class="cls-1" d="M288.89,309.11V201q0-33.9-28-33.9t-28,33.9V309.11h-55V201q0-33.9-27.52-33.9-28,0-28,33.9V309.11h-55V194.65q0-35.37,24.56-53.54,21.61-16.22,58.46-16.22,37.35,0,55,19.16,15.23-19.16,55.51-19.16,36.86,0,58.45,16.22,24.57,18.16,24.57,53.54V309.11Z"/><path class="cls-2" d="M.76,145.76"/><path class="cls-3" d="M1.73,145.32"/><path class="cls-4" d="M173.86,116.85l55.08-40.1-.7-2.44C135,83.92,80.42,96.44,0,144.13l.73,2.38,21-.67c15.17.52,30.85,1.26,46,1.32-1,24.09.83,32.65.83,32.65,51.92,17.58,101.22,1.53,127.14-43.9A248.72,248.72,0,0,0,173.86,116.85Z"/><path class="cls-4" d="M113.78,110c-.3-1.08-2.23-1.48-4.28-.9a4.73,4.73,0,0,0-3.14,2.19c-18.53,5-49.16,13.72-70.73,21l-1.33.45c-16.68,5.61-24.73,8.49-25.22,12.55-2.29,18.44-.87,42.18-.86,42.48l.19,6.4c-9.74,27.56.06,49.6,8.38,69.77.06-24.92,1.07-45.22-6-71-.2-3.82-1.49-30.36.61-47.34.34-2.75,11.65-6.56,23.63-10.59l1.33-.44c22-7.42,53.57-16.39,71.9-21.29a6.75,6.75,0,0,0,2.29-.25C112.64,112.43,114.08,111.09,113.78,110Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,31 +0,0 @@
|
||||
name: 'Close Pull Requests'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: opened
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close:
|
||||
# Avoid forks to run this job
|
||||
if: github.repository_owner == 'moodle'
|
||||
name: Close Pull Requests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/repo-lockdown@v4
|
||||
with:
|
||||
process-only: prs
|
||||
close-pr: true
|
||||
skip-closed-pr-comment: true
|
||||
pr-comment: >
|
||||
**Please, don't open pull requests via GitHub.**
|
||||
|
||||
|
||||
This repository (moodle.git) at GitHub is just a mirror of the official Moodle repository.
|
||||
|
||||
|
||||
For how to contribute patches for Moodle see
|
||||
[CONTRIBUTING.md](https://github.com/moodle/moodle/blob/main/CONTRIBUTING.md).
|
||||
|
||||
|
||||
Closing this pull request.
|
||||
@@ -1,143 +0,0 @@
|
||||
name: One by One Testing
|
||||
# Run all the individual unit tests one by one, with
|
||||
# fully independent PHPUnit executions. Useful to
|
||||
# detect issues with some tests that are using stuff
|
||||
# that has been made available by others, but is not
|
||||
# available when running individually.
|
||||
#
|
||||
# Note that we aren't using PHPUnit's own isolation
|
||||
# here but completely separated runs, one for each
|
||||
# test.
|
||||
#
|
||||
# The workflow will fail reporting all the tests
|
||||
# that have failed (and will pass if no failure is
|
||||
# detected, of course).
|
||||
#
|
||||
# It's only executed via workflow dispatch (automated
|
||||
# or manual), not by push/tag. And acceptd configuration
|
||||
# of phpunit, specially useful to run it with PHPUnit's
|
||||
# own isolation or any other option.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phpunit_extra_options:
|
||||
description: Additional options to apply to PHPUnit
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
chunks: 7
|
||||
|
||||
jobs:
|
||||
collect:
|
||||
name: Collect individual unit tests
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{steps.individual-tests.outputs.matrix }}
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Looking for all individual tests
|
||||
id: individual-tests
|
||||
run: |
|
||||
count=0 # Number of individual tests found.
|
||||
while read -r testfile; do # For each test file.
|
||||
while read -r testname; do # For each unit test in a file.
|
||||
count=$((count + 1))
|
||||
# Sent it to the correct chunk file.
|
||||
chunk=$(((($count % $chunks)) + 1))
|
||||
echo "$testname $testfile" >> ./chunk_$chunk.txt
|
||||
done < <(grep "function test_" "${testfile}" | sed -r "s/^.*function (test_[a-zA-Z0-9_]+).*/\1/")
|
||||
done < <(find . -name "*_test.php")
|
||||
# Generate the matrix to run tests.
|
||||
echo "matrix=$(ls -1 chunk_*.txt | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
echo "$count individual tests collected in $chunks files"
|
||||
|
||||
- name: Upload individual tests files
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: individual_tests
|
||||
path: chunk_*.txt
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
name: Run tests
|
||||
needs: collect
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
exttests:
|
||||
image: moodlehq/moodle-exttests
|
||||
ports:
|
||||
- 8080:80
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
file: ${{ fromJson(needs.collect.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Setting up DB pgsql
|
||||
uses: m4nu56/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: 13
|
||||
postgresql db: test
|
||||
postgresql user: test
|
||||
postgresql password: test
|
||||
|
||||
- name: Setting up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.3
|
||||
ini-values: max_input_vars=5000
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download individual test files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: individual_tests # Make all the chunk files available for the next steps.
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
echo "pathtophp=$(which php)" >> $GITHUB_ENV
|
||||
cp .github/workflows/config-template.php config.php
|
||||
mkdir ../moodledata
|
||||
sudo locale-gen en_AU.UTF-8
|
||||
php admin/tool/phpunit/cli/init.php --no-composer-self-update
|
||||
|
||||
- name: Run PHPUnit test (one by one)
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
status=0
|
||||
count=0
|
||||
while read -r line; do # For each line in the chunk file
|
||||
count=$((count + 1))
|
||||
filter="${line% *}"
|
||||
file="${line#* }"
|
||||
# Run the individual unit test and report problems if needed to.
|
||||
if ! php vendor/bin/phpunit \
|
||||
--fail-on-empty-test-suite \
|
||||
--fail-on-warning \
|
||||
--fail-on-risky \
|
||||
--filter "$filter" ${{ inputs.phpunit_extra_options }} \
|
||||
"$file" >/dev/null 2>&1; then
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "Problems found, list of PHPUnit commands failing:"
|
||||
fi
|
||||
echo "vendor/bin/phpunit --filter '${filter}' ${{ inputs.phpunit_extra_options }} $file"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
done < ${{ matrix.file }}
|
||||
echo "Finished: $count individual tests executed, $status tests failed"
|
||||
exit $status
|
||||
@@ -3,7 +3,7 @@ name: Core
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
- master
|
||||
- MOODLE_[0-9]+_STABLE
|
||||
tags-ignore:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
default: ''
|
||||
|
||||
env:
|
||||
php: 8.3
|
||||
php: 8.2
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
@@ -62,12 +62,12 @@ jobs:
|
||||
include:
|
||||
# MySQL builds always run with the lowest PHP supported version.
|
||||
- os: ubuntu-22.04
|
||||
php: 8.1
|
||||
php: 8.0
|
||||
extensions:
|
||||
db: mysqli
|
||||
# PostgreSQL builds always run with the highest PHP supported version.
|
||||
- os: ubuntu-22.04
|
||||
php: 8.3
|
||||
php: 8.2
|
||||
db: pgsql
|
||||
|
||||
steps:
|
||||
@@ -122,6 +122,3 @@ jobs:
|
||||
dbtype: ${{ matrix.db }}
|
||||
phpunit_options: ${{ secrets.phpunit_options }}
|
||||
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
|
||||
|
||||
- name: Git status
|
||||
run: git status
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Web Installer Testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
InstallationTest:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [8.1, 8.3]
|
||||
steps:
|
||||
- name: Construct repository URL
|
||||
run: echo "REPOSITORY_URL=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- uses: moodlehq/moodle-webinstaller-test@main
|
||||
with:
|
||||
repository: ${{ env.REPOSITORY_URL }}
|
||||
branch: ${{ github.ref_name }}
|
||||
php: ${{ matrix.php }}
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
required: false
|
||||
default: ''
|
||||
env:
|
||||
php: 8.3
|
||||
php: 8.2
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
php: 8.3
|
||||
php: 8.2
|
||||
# Ideally we should use mysql/mariadb, but they are 4x slower without tweaks and configuration
|
||||
# so let's run only postgres (1.5h vs 6h) only, If some day we want to improve the mysql runs,
|
||||
# this is the place to enable them.
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
db: pgsql
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
|
||||
- os: windows-latest
|
||||
php: 8.1
|
||||
php: 8.0
|
||||
db: pgsql
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium
|
||||
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
/.patches/
|
||||
/.idea/
|
||||
.phpstorm.*
|
||||
!/.phpstorm.meta.php/
|
||||
/.phpstorm.meta.php/*
|
||||
!/.phpstorm.meta.php/di.php
|
||||
/nbproject/
|
||||
CVS
|
||||
.DS_Store
|
||||
@@ -58,4 +55,3 @@ moodle-plugin-ci.phar
|
||||
.hugo_build.lock
|
||||
phpcs.xml
|
||||
jsconfig.json
|
||||
UPGRADING-CURRENT.md
|
||||
|
||||
+2
-27
@@ -40,20 +40,9 @@ const fetchComponentData = () => {
|
||||
if (!Object.entries(componentData).length) {
|
||||
componentData.subsystems = {};
|
||||
componentData.pathList = [];
|
||||
componentData.components = {};
|
||||
componentData.standardComponents = {};
|
||||
|
||||
// Fetch the component definiitions from the distributed JSON file.
|
||||
const components = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/components.json`));
|
||||
const pluginData = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/plugins.json`));
|
||||
|
||||
componentData.pluginTypes = components.plugintypes;
|
||||
|
||||
const standardPlugins = Object.entries(pluginData.standard).map(
|
||||
([pluginType, pluginNames]) => {
|
||||
return pluginNames.map(pluginName => `${pluginType}_${pluginName}`);
|
||||
}
|
||||
).reduce((acc, val) => acc.concat(val), []);
|
||||
|
||||
// Build the list of moodle subsystems.
|
||||
componentData.subsystems.lib = 'core';
|
||||
@@ -66,8 +55,8 @@ const fetchComponentData = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// The list of components includes the list of subsystems.
|
||||
componentData.components = {...componentData.subsystems};
|
||||
// The list of components incldues the list of subsystems.
|
||||
componentData.components = componentData.subsystems;
|
||||
|
||||
// Go through each of the plugintypes.
|
||||
Object.entries(components.plugintypes).forEach(([pluginType, pluginTypePath]) => {
|
||||
@@ -98,20 +87,6 @@ const fetchComponentData = () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Create a list of the standard subsystem and plugins.
|
||||
componentData.standardComponents = Object.fromEntries(
|
||||
Object.entries(componentData.components).filter(([, name]) => {
|
||||
if (name === 'core' || name.startsWith('core_')) {
|
||||
return true;
|
||||
}
|
||||
return standardPlugins.indexOf(name) !== -1;
|
||||
})
|
||||
);
|
||||
|
||||
componentData.componentMapping = Object.fromEntries(
|
||||
Object.entries(componentData.components).map(([path, name]) => [name, path])
|
||||
);
|
||||
}
|
||||
|
||||
return componentData;
|
||||
|
||||
@@ -1,138 +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/>.
|
||||
|
||||
import * as Components from '../../components.js';
|
||||
|
||||
const componentData = Components.fetchComponentData();
|
||||
|
||||
/**
|
||||
* The standard components shipped with core Moodle.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const standardComponents = componentData.standardComponents;
|
||||
|
||||
/**
|
||||
* All components of the current Moodle instance.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const allComponents = componentData.components;
|
||||
|
||||
/**
|
||||
* Get all components of the current Moodle instance.
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getAllComponents = () => {
|
||||
let components = new Map(Object.entries(componentData.pluginTypes).map(([value, path]) => ([path,{
|
||||
path,
|
||||
value,
|
||||
name: `${value} (plugin type)`,
|
||||
}])));
|
||||
|
||||
Object
|
||||
.entries(componentData.components)
|
||||
.filter(([path, value]) => Object.values(componentData.standardComponents).includes(value))
|
||||
.forEach(([path, value]) => {
|
||||
const entry = {
|
||||
path,
|
||||
value,
|
||||
name: value,
|
||||
};
|
||||
if (Object.values(componentData.subsystems).includes(value)) {
|
||||
if (components.has(path)) {
|
||||
entry.name = `${value} (subsystem / plugintype)`;
|
||||
} else {
|
||||
entry.name = `${value} (subsystem)`;
|
||||
}
|
||||
}
|
||||
|
||||
components.set(path, entry);
|
||||
});
|
||||
|
||||
return Array.from(components.values());
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the specified component is a standard component shipped with core Moodle.
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isStandardComponent = (componentName) => {
|
||||
if (Object.values(componentData.standardComponents).includes(componentName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const rewritePlugintypeAsSubsystem = (componentName) => {
|
||||
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
|
||||
const pluginTypePath = componentData.pluginTypes[componentName];
|
||||
if (Object.keys(componentData.subsystems).includes(pluginTypePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the specified component is a community component.
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isCommunityComponent = (componentName) => {
|
||||
if (isStandardComponent(componentName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.values(componentData.components).indexOf(componentName) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort method for components.
|
||||
*
|
||||
* This method sorts components putting `core` first, followed by core subsystems, then everything else.
|
||||
*
|
||||
* @param {String} a
|
||||
* @param {String} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const sortComponents = (a, b) => {
|
||||
// Always put 'core' first.
|
||||
if (a === 'core') {
|
||||
return -1;
|
||||
} else if (b === 'core') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Put core subsystems next.
|
||||
if (a.startsWith('core_') && !b.startsWith('core_')) {
|
||||
return -1;
|
||||
} else if (b.startsWith('core_') && !a.startsWith('core_')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort alphabetically for everything else.
|
||||
return a.localeCompare(b);
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createNote } from './note.mjs';
|
||||
import { getInitialValues } from './helpers.mjs';
|
||||
import * as Prompts from './prompts.mjs';
|
||||
import logger from './logger.mjs';
|
||||
|
||||
export default async (options) => {
|
||||
// Processs the initial values.
|
||||
const initialValues = getInitialValues(options);
|
||||
|
||||
// Fetch information.
|
||||
const messages = [];
|
||||
const { issueNumber } = await inquirer.prompt([
|
||||
Prompts.getIssuePrompt(),
|
||||
], initialValues);
|
||||
|
||||
let selection = {};
|
||||
let notePath;
|
||||
do {
|
||||
selection = {};
|
||||
selection = await inquirer.prompt([
|
||||
Prompts.getComponentsPrompt(),
|
||||
Prompts.getTypePrompt(),
|
||||
Prompts.getMessagePromptInput(),
|
||||
], initialValues);
|
||||
if (selection.message === '') {
|
||||
selection = Object.assign(
|
||||
selection,
|
||||
await inquirer.prompt([
|
||||
Prompts.getMessagePromptEditor(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`
|
||||
Creating upgrade note with the following options:
|
||||
|
||||
- Issue: ${chalk.bold(issueNumber)}
|
||||
- Component: ${chalk.bold(selection.components)}
|
||||
- Type: ${chalk.bold(selection.type)}
|
||||
- Message:
|
||||
${chalk.bold(selection.message)}
|
||||
`);
|
||||
|
||||
messages.push({
|
||||
components: [selection.components],
|
||||
type: selection.type,
|
||||
message: selection.message,
|
||||
});
|
||||
|
||||
// Save the note so far.
|
||||
if (notePath) {
|
||||
await createNote(issueNumber, messages, notePath);
|
||||
logger.info(`Updated note at: ${chalk.underline(chalk.bold(notePath))}`);
|
||||
} else {
|
||||
notePath = await createNote(issueNumber, messages);
|
||||
logger.info(`Note created at: ${chalk.underline(chalk.bold(notePath))}`);
|
||||
}
|
||||
|
||||
selection = Object.assign(
|
||||
selection,
|
||||
await inquirer.prompt([
|
||||
Prompts.getAddAnotherPrompt(),
|
||||
], initialValues),
|
||||
);
|
||||
} while (selection.addAnother);
|
||||
};
|
||||
@@ -1,357 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { getAllComponents } from './components.mjs';
|
||||
import { getCombinedNotesByComponent, deleteAllNotes } from './note.mjs';
|
||||
import { getNoteName } from './noteTypes.mjs';
|
||||
import { writeFile, readFile, unlink } from 'fs/promises';
|
||||
import { join as joinPath } from 'path';
|
||||
import logger from './logger.mjs';
|
||||
import { getCurrentVersion } from './helpers.mjs';
|
||||
|
||||
/**
|
||||
* Helper to fetch the current notes from a file.
|
||||
*
|
||||
* @param {string} file
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const getCurrentNotes = async (file) => {
|
||||
try {
|
||||
return await readFile(file, 'utf8');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the UPGRADING.md file.
|
||||
*
|
||||
* @param {string} upgradeNotes
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.deleteNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const updateUpgradeNotes = async (upgradeNotes, options) => {
|
||||
const fileName = 'UPGRADING.md';
|
||||
// Write the notes to a file.
|
||||
logger.info(`Writing notes to ${chalk.underline(chalk.bold(fileName))}`);
|
||||
// Prepend to the existing file.
|
||||
const existingContent = await getCurrentNotes(fileName);
|
||||
if (existingContent) {
|
||||
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
|
||||
} else {
|
||||
// This should not normally happen.
|
||||
await writeFile(fileName, upgradeNotes);
|
||||
}
|
||||
|
||||
if (options.deleteNotes) {
|
||||
logger.warn(`>>> Deleting all notes <<<`)
|
||||
// Delete the notes.
|
||||
deleteAllNotes();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the current summary notes.
|
||||
*
|
||||
* @param {string} upgradeNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const createCurrentSummary = async (upgradeNotes) => {
|
||||
const fileName = 'UPGRADING-CURRENT.md';
|
||||
const notes = `# Moodle upgrade notes\n\n${upgradeNotes}`;
|
||||
await writeFile(fileName, notes);
|
||||
|
||||
logger.info(`Running upgrade notes written to ${chalk.underline(chalk.bold(fileName))}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the indexes of the lines that contain the version headings.
|
||||
*
|
||||
* @param {array<string>} lines
|
||||
* @returns {array<object>}
|
||||
*/
|
||||
const getVersionLineIndexes = (lines) => {
|
||||
const h2Indexes = [];
|
||||
lines.forEach((line, index) => {
|
||||
const matches = line.match(/^##\s(?<version>.*)$/);
|
||||
if (matches) {
|
||||
h2Indexes.push({
|
||||
index,
|
||||
line,
|
||||
version: matches.groups.version,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return h2Indexes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the index of the Unreleased heading.
|
||||
*
|
||||
* @param {array<object>} versionHeadings
|
||||
* @returns {number}
|
||||
*/
|
||||
const findUnreleasedHeadingIndex = (versionHeadings) => versionHeadings.findIndex((heading) => {
|
||||
if (heading.version === 'Unreleased') {
|
||||
// Used if version cannot be guessed.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.endsWith('+')) {
|
||||
// Weekly release for a stable branch.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.match(/beta|rc\d/)) {
|
||||
// Beta and RC rolls are treated as weeklies.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.endsWith('dev')) {
|
||||
// Development version.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the before and after content, to facilitate replacing any existing Unreleased notes.
|
||||
*
|
||||
* @param {array<string>} lines
|
||||
* @returns {Object} {beforeContent: string, afterContent: string}
|
||||
*/
|
||||
const getBeforeAndAfterContent = (lines) => {
|
||||
const existingLines = lines.split('\n');
|
||||
const versionHeadings = getVersionLineIndexes(existingLines);
|
||||
|
||||
if (versionHeadings.length > 0) {
|
||||
const unreleasedHeadingIndex = findUnreleasedHeadingIndex(versionHeadings);
|
||||
if (unreleasedHeadingIndex !== -1) {
|
||||
const beforeContent = existingLines.slice(0, versionHeadings[unreleasedHeadingIndex].index).join('\n');
|
||||
if (versionHeadings.length > unreleasedHeadingIndex + 1) {
|
||||
const afterContent = existingLines.slice(versionHeadings[unreleasedHeadingIndex + 1].index).join('\n');
|
||||
return {
|
||||
beforeContent,
|
||||
afterContent,
|
||||
};
|
||||
}
|
||||
return {
|
||||
beforeContent,
|
||||
afterContent: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
beforeContent: existingLines.slice(0, versionHeadings[0].index).join('\n'),
|
||||
afterContent: existingLines.slice(versionHeadings[0].index).join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
beforeContent: existingLines.join('\n'),
|
||||
afterContent: '',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the notes for the component.
|
||||
*
|
||||
* @param {string} types
|
||||
* @param {Number} headingLevel
|
||||
* @returns {string}
|
||||
*/
|
||||
const getNotesForComponent = (types, headingLevel) => {
|
||||
let upgradeNotes = '';
|
||||
Object.entries(types).forEach(([type, notes]) => {
|
||||
upgradeNotes += '#'.repeat(headingLevel);
|
||||
upgradeNotes += ` ${getNoteName(type)}\n\n`;
|
||||
notes.forEach(({ message, issueNumber }) => {
|
||||
// Split the message into lines, removing empty lines.
|
||||
const messageLines = message
|
||||
.split('\n')
|
||||
// Remove empty lines between tables, and list entries, but not after lists.
|
||||
.filter((line, index, lines) => {
|
||||
if (line.trim().length === 0) {
|
||||
// This line is empty.
|
||||
|
||||
// If it's the first line in the file, remove it.
|
||||
if (index === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is the last line in the file, remove it.
|
||||
if (index === lines.length - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the previous line relates to a table, remove this line.
|
||||
if (lines[index - 1].match(/^\s*\|/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the next line is also empty, do not remove this line.
|
||||
if (lines[index + 1].trim().length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not remove the line if the previous line was a list item.
|
||||
if (lines[index - 1].match(/^\s*[-*]\s/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lines[index - 1].match(/^\s*\d+\.\s/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Preserve all other empty lines by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep any line which has content.
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
const firstLine = messageLines.shift().trim();
|
||||
upgradeNotes += `- ${firstLine}\n`;
|
||||
|
||||
messageLines
|
||||
.forEach((line) => {
|
||||
upgradeNotes += ` ${line}`.trimEnd() + `\n`;
|
||||
});
|
||||
upgradeNotes += `\n For more information see [${issueNumber}](https://tracker.moodle.org/browse/${issueNumber})\n`;
|
||||
});
|
||||
upgradeNotes += '\n';
|
||||
});
|
||||
|
||||
return upgradeNotes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the updated notes mixed with existing content.
|
||||
*
|
||||
* @param {string} existingContent
|
||||
* @param {string} upgradeNotes
|
||||
*/
|
||||
const getUpdatedNotes = (existingContent, upgradeNotes) => {
|
||||
const { beforeContent, afterContent } = getBeforeAndAfterContent(existingContent);
|
||||
const newContent = `${beforeContent}\n${upgradeNotes}\n${afterContent}`
|
||||
.split('\n')
|
||||
.filter((line, index, lines) => {
|
||||
if (line === '' && lines[index - 1] === '') {
|
||||
// Remove multiple consecutive empty lines.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return newContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the notes for each component.
|
||||
*/
|
||||
const updateComponentNotes = (
|
||||
notes,
|
||||
version,
|
||||
notesFileName = 'UPGRADING.md',
|
||||
removeEmpty = false,
|
||||
) => {
|
||||
return getAllComponents().map(async (component) => {
|
||||
logger.verbose(`Updating notes for ${component.name} into ${component.path}`);
|
||||
const fileName = joinPath(component.path, notesFileName);
|
||||
|
||||
const existingContent = await getCurrentNotes(fileName);
|
||||
|
||||
if (!existingContent) {
|
||||
if (!notes[component.value]) {
|
||||
// No existing notes, and no new notes to add.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!notes[component.value]) {
|
||||
// There is existing content, but nothing to add.
|
||||
if (removeEmpty) {
|
||||
logger.verbose(`Removing empty notes file ${fileName}`);
|
||||
await unlink(fileName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const componentNotes = notes[component.value];
|
||||
let upgradeNotes = `## ${version}\n\n`;
|
||||
upgradeNotes += getNotesForComponent(componentNotes, 3);
|
||||
|
||||
if (existingContent) {
|
||||
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
|
||||
} else {
|
||||
await writeFile(
|
||||
fileName,
|
||||
`# ${component.name} Upgrade notes\n\n${upgradeNotes}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the upgrade notes for a new release.
|
||||
*
|
||||
* @param {string|undefined} version
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.generateUpgradeNotes
|
||||
* @param {boolean} options.deleteNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export default async (version, options = {}) => {
|
||||
const notes = await getCombinedNotesByComponent();
|
||||
|
||||
if (Object.keys(notes).length === 0) {
|
||||
logger.warn('No notes to generate');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
version = await getCurrentVersion();
|
||||
}
|
||||
|
||||
// Generate the upgrade notes for this release.
|
||||
// We have
|
||||
// - a title with the release name
|
||||
// - the change types
|
||||
// - which contain the components
|
||||
// - which document each change
|
||||
let upgradeNotes = `## ${version}\n\n`;
|
||||
|
||||
Object.entries(notes).forEach(([component, types]) => {
|
||||
upgradeNotes += `### ${component}\n\n`;
|
||||
upgradeNotes += getNotesForComponent(types, 4);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
createCurrentSummary(upgradeNotes),
|
||||
...updateComponentNotes(notes, version, 'UPGRADING-CURRENT.md', true),
|
||||
]);
|
||||
if (options.generateUpgradeNotes) {
|
||||
await Promise.all(updateComponentNotes(notes, version));
|
||||
await updateUpgradeNotes(upgradeNotes, options);
|
||||
}
|
||||
};
|
||||
@@ -1,230 +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/>.
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { isStandardComponent, isCommunityComponent, rewritePlugintypeAsSubsystem } from './components.mjs';
|
||||
import { isValidNoteName } from './noteTypes.mjs';
|
||||
import logger from './logger.mjs';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
/**
|
||||
* Validate an issue number input
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string|boolean}
|
||||
*/
|
||||
export const validateIssueNumber = (input) => {
|
||||
if (!input) {
|
||||
return 'You must provide a tracker issue number';
|
||||
}
|
||||
|
||||
if (input.match(/^[a-zA-Z]*-\d+$/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (input.match(/^\d+$/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'The issue number was not recognised as a valid issue number';
|
||||
};
|
||||
|
||||
/**
|
||||
* Format an issue number input.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
export const formatIssueNumber = (input) => {
|
||||
if (input.match(/^[a-zA-Z]*-\d+$/)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input.match(/^\d+$/)) {
|
||||
return `MDL-${input}`;
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a component.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string|boolean}
|
||||
*/
|
||||
export const validateComponent = (input) => {
|
||||
if (isStandardComponent(input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isCommunityComponent(input)) {
|
||||
return 'Currently only core plugins are supported.';
|
||||
}
|
||||
|
||||
return 'The component was not recognised as a standard component';
|
||||
};
|
||||
|
||||
export const formatComponent = (input) => {
|
||||
if (rewritePlugintypeAsSubsystem(input)) {
|
||||
return `core_${input}`;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial values from the options.
|
||||
*
|
||||
* @param {object} options
|
||||
* @returns {object}
|
||||
*/
|
||||
export const getInitialValues = (options) => {
|
||||
const initialValues = {};
|
||||
|
||||
const type = getInitialTypeValue(options);
|
||||
if (type) {
|
||||
initialValues.type = type;
|
||||
}
|
||||
|
||||
const issueNumber = getInitialIssueValue(options);
|
||||
if (issueNumber) {
|
||||
initialValues.issueNumber = issueNumber;
|
||||
}
|
||||
|
||||
const component = getInitialComponentValue(options);
|
||||
if (component) {
|
||||
initialValues.components = component;
|
||||
}
|
||||
|
||||
const message = getInitialMessageValue(options);
|
||||
if (message) {
|
||||
initialValues.message = message
|
||||
initialValues.addAnother = false;
|
||||
}
|
||||
|
||||
return initialValues;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial type value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
const getInitialTypeValue = (options) => {
|
||||
if (!options.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.type = options.type.trim().toLowerCase();
|
||||
|
||||
if (isValidNoteName(options.type)) {
|
||||
return options.type;
|
||||
}
|
||||
|
||||
logger.warn(`Note type "${chalk.underline(chalk.red(options.type))}" is not valid.`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial issue number value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
|
||||
const getInitialIssueValue = (options) => {
|
||||
if (!options.issue) {
|
||||
return;
|
||||
}
|
||||
options.issue = options.issue.trim().toUpperCase();
|
||||
|
||||
const issueNumberValidated = validateIssueNumber(options.issue);
|
||||
if (issueNumberValidated === true) {
|
||||
const issueNumber = formatIssueNumber(options.issue);
|
||||
if (issueNumber !== options.issue) {
|
||||
logger.warn(
|
||||
`Issue number "${chalk.underline(chalk.red(options.issue))}" was updated to ` +
|
||||
`"${chalk.underline(chalk.green(issueNumber))}"`
|
||||
);
|
||||
}
|
||||
|
||||
return issueNumber;
|
||||
} else {
|
||||
logger.warn(`Issue number "${chalk.underline(chalk.red(options.issue))}" is not valid: ${issueNumberValidated}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial component value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
const getInitialComponentValue = (options) => {
|
||||
if (!options.component) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.component = options.component.trim().toLowerCase();
|
||||
const componentValidated = validateComponent(options.component);
|
||||
if (componentValidated === true) {
|
||||
const component = formatComponent(options.component);
|
||||
if (component !== options.component) {
|
||||
logger.warn(
|
||||
`Component "${chalk.underline(chalk.red(options.component))}" was updated to ` +
|
||||
`"${chalk.underline(chalk.green(component))}"`
|
||||
);
|
||||
}
|
||||
|
||||
return component;
|
||||
} else {
|
||||
logger.warn(`Component "${chalk.underline(chalk.red(options.component))}" is not valid: ${componentValidated}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial message value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
|
||||
const getInitialMessageValue = (options) => {
|
||||
if (!options.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
return options.message.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current version from the project /version.php file.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const getCurrentVersion = async () => {
|
||||
const versionRegex = new RegExp(/^ *\$release *= *['\"](?<release>[^ \+]+\+?) *\(Build:.*/m);
|
||||
try {
|
||||
const versionFile = await readFile('version.php', 'utf8');
|
||||
const match = versionFile.match(versionRegex);
|
||||
if (match) {
|
||||
return match.groups.release;
|
||||
}
|
||||
} catch(error) {
|
||||
logger.error('Unable to read the version file');
|
||||
}
|
||||
|
||||
return "Unreleased";
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import winston from 'winston';
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
],
|
||||
});
|
||||
|
||||
export default logger;
|
||||
@@ -1,194 +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/>.
|
||||
|
||||
import yaml from 'js-yaml';
|
||||
import path from 'path';
|
||||
import { writeFile, mkdir, readdir, readFile, unlink } from 'fs/promises';
|
||||
import { isValidNoteName, sortNoteTypes } from './noteTypes.mjs';
|
||||
import { sortComponents } from './components.mjs';
|
||||
|
||||
const unreleasedPath = path.resolve('.upgradenotes');
|
||||
|
||||
/**
|
||||
* Get the filename for the note.
|
||||
*
|
||||
* @param {string} issueNumnber The issue number
|
||||
* @returns {string}
|
||||
*/
|
||||
const getFilename = (issueNumber) => {
|
||||
const dateTimeFormat = new Intl.DateTimeFormat(undefined, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
fractionalSecondDigits: 2,
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
|
||||
const date = Object.fromEntries(
|
||||
dateTimeFormat.formatToParts(new Date())
|
||||
.filter((p) => p.type !== 'literal')
|
||||
.map((p) => ([p.type, p.value]))
|
||||
);
|
||||
|
||||
const dateString = [
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
date.hour,
|
||||
date.minute,
|
||||
date.second,
|
||||
date.fractionalSecond,
|
||||
].join('');
|
||||
|
||||
return `${issueNumber}-${dateString}.yml`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new note.
|
||||
*
|
||||
* @param {string} issueNumber
|
||||
* @param {[type: string]: {message: string}} messages
|
||||
* @returns {string} The path to the note on disk
|
||||
*/
|
||||
export const createNote = async (
|
||||
issueNumber,
|
||||
messages,
|
||||
notePath,
|
||||
) => {
|
||||
const note = {
|
||||
issueNumber,
|
||||
notes: {},
|
||||
};
|
||||
|
||||
messages.forEach(({components, type, message}) => {
|
||||
note.notes[components] = note.notes[components] || [];
|
||||
note.notes[components].push({message, type});
|
||||
});
|
||||
|
||||
if (!notePath) {
|
||||
notePath = path.resolve(unreleasedPath, getFilename(issueNumber));
|
||||
}
|
||||
const noteContent = yaml.dump(note);
|
||||
|
||||
await mkdir(unreleasedPath, {recursive: true});
|
||||
await writeFile(notePath, noteContent);
|
||||
|
||||
return notePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all unreleased notes.
|
||||
*
|
||||
* @returns {Promise<{issueNumber: string, components: string[], types: {[type: string]: {message: string}[]}}[]>
|
||||
*/
|
||||
export const getAllNotes = async () => {
|
||||
const files = await readdir(unreleasedPath);
|
||||
const notes = files
|
||||
.filter((file) => file.endsWith('.yml'))
|
||||
.map(async (file) => {
|
||||
const filePath = path.resolve(unreleasedPath, file);
|
||||
const fileContent = await readFile(filePath, 'utf8');
|
||||
|
||||
return {
|
||||
...yaml.load(fileContent),
|
||||
filePath,
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.all(notes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of notes, grouped by note type, then component.
|
||||
*
|
||||
* @returns {Promise<{[type: string]: {[components: string]: {message: string, issueNumber: string}[]}}>}
|
||||
*/
|
||||
export const getCombinedNotes = async () => {
|
||||
const notes = await getAllNotes();
|
||||
const combinedNotes = {};
|
||||
|
||||
notes.forEach((note) => {
|
||||
Object.entries(note.notes).forEach(([components, data]) => {
|
||||
data.forEach((entry) => {
|
||||
if (!isValidNoteName(entry.type)) {
|
||||
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
|
||||
}
|
||||
combinedNotes[entry.type] = combinedNotes[entry.type] || {};
|
||||
combinedNotes[entry.type][components] = combinedNotes[entry.type][components] || [];
|
||||
combinedNotes[entry.type][components].push({message: entry.message, issueNumber: note.issueNumber});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return combinedNotes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of notes, grouped by component, then by note type.
|
||||
*
|
||||
* @returns {Promise<{[component: string]: {[type: string]: {message: string, issueNumber: string}[]}}>}
|
||||
*/
|
||||
export const getCombinedNotesByComponent = async () => {
|
||||
const notes = await getAllNotes();
|
||||
const combinedNotes = {};
|
||||
|
||||
notes.forEach((note) => {
|
||||
Object.entries(note.notes).forEach(([component, data]) => {
|
||||
combinedNotes[component] = combinedNotes[component] || {};
|
||||
data.forEach((entry) => {
|
||||
if (!isValidNoteName(entry.type)) {
|
||||
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
|
||||
}
|
||||
combinedNotes[component][entry.type] = combinedNotes[component][entry.type] || [];
|
||||
combinedNotes[component][entry.type].push({
|
||||
component,
|
||||
message: entry.message,
|
||||
issueNumber: note.issueNumber,
|
||||
type: entry.type,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Sort notes by note type.
|
||||
Object.entries(combinedNotes).forEach(([component, types]) => {
|
||||
combinedNotes[component] = Object.fromEntries(
|
||||
Object.entries(types).sort(([a], [b]) => sortNoteTypes(a, b))
|
||||
);
|
||||
});
|
||||
|
||||
// Sort components.
|
||||
return Object.fromEntries(
|
||||
Object.entries(combinedNotes).sort(([a], [b]) => sortComponents(a, b))
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all unreleased notes.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const deleteAllNotes = async () => {
|
||||
const files = await readdir(unreleasedPath);
|
||||
return Promise.all(
|
||||
files
|
||||
.filter((item, index) => files.indexOf(item) === index)
|
||||
.filter((file) => file.endsWith('.yml'))
|
||||
.map((file) => unlink(`${unreleasedPath}/${file}`))
|
||||
);
|
||||
};
|
||||
@@ -1,80 +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/>.
|
||||
|
||||
/**
|
||||
* The note types.
|
||||
*
|
||||
* Matches the suggested order in https://keepachangelog.com/en/1.1.0/.
|
||||
* @type {Object<string, string>}
|
||||
*/
|
||||
const noteTypes = {
|
||||
'improved': 'Added',
|
||||
'changed': 'Changed',
|
||||
'deprecated': 'Deprecated',
|
||||
'removed': 'Removed',
|
||||
'fixed': 'Fixed',
|
||||
};
|
||||
|
||||
/**
|
||||
* The preferred order of note types.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const preferredOrder = Object.keys(noteTypes);
|
||||
|
||||
/**
|
||||
* Comparison method to sort note types.
|
||||
*
|
||||
* @param {String} a
|
||||
* @param {String} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const sortNoteTypes = (a, b) => {
|
||||
const aIndex = preferredOrder.indexOf(a);
|
||||
const bIndex = preferredOrder.indexOf(b);
|
||||
|
||||
if (aIndex === -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return aIndex - bIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the note names.
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export const getNoteNames = () => Object.keys(noteTypes);
|
||||
|
||||
/**
|
||||
* Get the human-readable note name.
|
||||
*
|
||||
* @param {string} type
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getNoteName = (type) => noteTypes[type];
|
||||
|
||||
/**
|
||||
* Whether the note name is valid.
|
||||
*
|
||||
* @param {string} type
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isValidNoteName = (type) => noteTypes[type] !== undefined;
|
||||
@@ -1,153 +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/>.
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
import SearchList from 'inquirer-search-list';
|
||||
|
||||
import { getNoteNames } from './noteTypes.mjs';
|
||||
import { getAllComponents } from './components.mjs';
|
||||
import {
|
||||
formatComponent,
|
||||
formatIssueNumber,
|
||||
validateComponent,
|
||||
validateIssueNumber,
|
||||
} from './helpers.mjs';
|
||||
|
||||
/**
|
||||
* A Search List which accepts an initial value.
|
||||
*/
|
||||
class SearchListWithInitialValue extends SearchList {
|
||||
constructor(options, ...args) {
|
||||
super(options, ...args);
|
||||
|
||||
if (options.default) {
|
||||
const pointer = this.filterList.findIndex((item) => {
|
||||
return item.value === options.default;
|
||||
});
|
||||
if (pointer > -1) {
|
||||
this.pointer = pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inquirer.registerPrompt('search-list', SearchListWithInitialValue);
|
||||
|
||||
/**
|
||||
* Get the issue type prompt.
|
||||
*
|
||||
* @param {string} defaultData The initially selected value
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getTypePrompt = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'search-list',
|
||||
message: 'Type of change',
|
||||
name: 'type',
|
||||
choices: getNoteNames(),
|
||||
validate: (selection) => {
|
||||
if (selection.length < 1) {
|
||||
return 'You must select at least one type of change';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the component prompt.
|
||||
*
|
||||
* @param {string} [defaultValue='core'] The initally selected value.
|
||||
* @returns
|
||||
*/
|
||||
export const getComponentsPrompt = (defaultValue) => {
|
||||
if (!defaultValue ) {
|
||||
defaultValue = 'core';
|
||||
}
|
||||
|
||||
return {
|
||||
choices: getAllComponents(),
|
||||
default: defaultValue,
|
||||
type: 'search-list',
|
||||
message: 'Component',
|
||||
name: 'components',
|
||||
validate: validateComponent,
|
||||
filter: formatComponent,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the issue number prompt as an inline input.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns {object}
|
||||
*/
|
||||
export const getIssuePrompt = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'input',
|
||||
message: 'Tracker issue number',
|
||||
name: 'issueNumber',
|
||||
validate: validateIssueNumber,
|
||||
filter: formatIssueNumber,
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a message prompt.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns
|
||||
*/
|
||||
export const getMessagePromptEditor = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: process.stdin.isTTY ? 'editor' : 'input',
|
||||
postfix: '.md',
|
||||
message: 'Message',
|
||||
name: 'message',
|
||||
waitUserInput: false,
|
||||
validate: (input) => {
|
||||
if (!input) {
|
||||
return 'You must provide a message';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Remove any trailing whitespace.
|
||||
filter: (input) => input.split('\n').map((line) => line.trimEnd()).join('\n'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a message prompt.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns
|
||||
*/
|
||||
export const getMessagePromptInput = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'input',
|
||||
message: 'Message (leave empty to use editor)',
|
||||
name: 'message',
|
||||
filter: (input) => input.trim(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a prompt to ask the user if they wish to add another entry.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getAddAnotherPrompt = () => ({
|
||||
type: 'confirm',
|
||||
message: 'Do you want to add another note?',
|
||||
default: false,
|
||||
name: 'addAnother',
|
||||
});
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function to generate the destination for the minification 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
|
||||
@@ -60,6 +60,7 @@ module.exports = grunt => {
|
||||
grunt.registerTask('js', ['amd', 'yui']);
|
||||
|
||||
// Register NPM tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-rollup');
|
||||
|
||||
@@ -103,7 +104,6 @@ module.exports = grunt => {
|
||||
// The queue runner will run the next `size` items in the queue.
|
||||
const runQueue = (size = 1) => {
|
||||
queue.splice(0, size).forEach(resolve => {
|
||||
grunt.log.debug(`Item resolved. Kicking off next one.`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
@@ -113,17 +113,15 @@ module.exports = grunt => {
|
||||
|
||||
// The options hook is run in parallel.
|
||||
// We can return an unresolved Promise which is queued for later resolution.
|
||||
options: async(options) => {
|
||||
options: async() => {
|
||||
return new Promise(resolve => {
|
||||
queue.push(resolve);
|
||||
startQueue();
|
||||
return options;
|
||||
});
|
||||
},
|
||||
|
||||
// When an item in the queue completes, start the next item in the queue.
|
||||
generateBundle: (options, bundle) => {
|
||||
grunt.log.debug(`Finished output phase for ${Object.keys(bundle).join(', ')}`);
|
||||
buildEnd: () => {
|
||||
runQueue();
|
||||
},
|
||||
};
|
||||
@@ -157,6 +155,16 @@ module.exports = grunt => {
|
||||
],
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
targets: {
|
||||
browsers: [
|
||||
">0.3%",
|
||||
"last 2 versions",
|
||||
"not ie >= 0",
|
||||
"not op_mini all",
|
||||
"not Opera > 0",
|
||||
"not dead"
|
||||
]
|
||||
},
|
||||
modules: false,
|
||||
useBuiltIns: false
|
||||
}]
|
||||
|
||||
+12
-28
@@ -20,33 +20,17 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = (grunt) => {
|
||||
const path = require('path');
|
||||
|
||||
grunt.registerTask('jsdoc', 'Generate JavaScript documentation using jsdoc', function() {
|
||||
const done = this.async();
|
||||
const configuration = path.resolve('.grunt/jsdoc/jsdoc.conf.js');
|
||||
|
||||
grunt.util.spawn({
|
||||
cmd: 'jsdoc',
|
||||
args: [
|
||||
'--configure',
|
||||
configuration,
|
||||
]
|
||||
}, function(error, result, code) {
|
||||
if (result.stdout) {
|
||||
grunt.log.write(result.stdout);
|
||||
}
|
||||
|
||||
if (result.stderr) {
|
||||
grunt.log.error(result.stderr);
|
||||
}
|
||||
if (error) {
|
||||
grunt.fail.fatal(`JSDoc failed with error code ${code}`);
|
||||
} else {
|
||||
grunt.log.write('JSDoc completed successfully'.green);
|
||||
}
|
||||
done();
|
||||
});
|
||||
module.exports = grunt => {
|
||||
// Project configuration.
|
||||
grunt.config.merge({
|
||||
jsdoc: {
|
||||
dist: {
|
||||
options: {
|
||||
configure: ".grunt/jsdoc/jsdoc.conf.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
};
|
||||
|
||||
@@ -29,7 +29,6 @@ module.exports = grunt => {
|
||||
// Use a fully-qualified path.
|
||||
src: files,
|
||||
options: {
|
||||
quietDeprecationWarnings: true,
|
||||
configOverrides: {
|
||||
rules: {
|
||||
// These rules have to be disabled in .stylelintrc for scss compat.
|
||||
@@ -46,10 +45,7 @@ module.exports = grunt => {
|
||||
return {
|
||||
stylelint: {
|
||||
scss: {
|
||||
options: {
|
||||
quietDeprecationWarnings: true,
|
||||
customSyntax: 'postcss-scss',
|
||||
},
|
||||
options: {syntax: 'scss'},
|
||||
src: files,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// 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/>.
|
||||
|
||||
import { Argument, Option, program } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getNoteNames } from './notes/src/noteTypes.mjs';
|
||||
import createAction from './notes/src/create.mjs';
|
||||
import generateAction from './notes/src/generate.mjs';
|
||||
import logger from './notes/src/logger.mjs';
|
||||
|
||||
console.log(`
|
||||
${chalk.bold(chalk.underline(chalk.green('Moodle Upgrade Notes Generator')))}
|
||||
|
||||
This tool is used to generate the upgrade notes for changes you make in Moodle.
|
||||
|
||||
Please remember that the intended audience of these changes is
|
||||
${chalk.italic('plugin developers')} who need to know how to update their plugins
|
||||
for a new Moodle version.
|
||||
|
||||
Upgrade notes should not be used to document changes for site administrators, or
|
||||
for internal API changes which are not expected to be used outside of the
|
||||
relevant component.
|
||||
`)
|
||||
|
||||
program.configureHelp({
|
||||
helpWidth: 100,
|
||||
});
|
||||
|
||||
program.on('option:verbose', () => {
|
||||
logger.level = 'verbose';
|
||||
});
|
||||
|
||||
program.addOption(
|
||||
new Option(
|
||||
'-v, --verbose',
|
||||
'Output more information during the generation process',
|
||||
)
|
||||
.default(false)
|
||||
);
|
||||
|
||||
// Define the command line options.
|
||||
program
|
||||
.command('create')
|
||||
.summary('Generate a new upgrade note')
|
||||
.addOption(
|
||||
new Option('-t, --type <type>', `The type of change to document. Valid types are: ${getNoteNames().join(', ')}`)
|
||||
)
|
||||
.addOption(new Option('-i, --issue <issue>', 'The tracker issue number'))
|
||||
.addOption(new Option('-c, --component <component>', 'The component to write a note for'))
|
||||
.addOption(new Option(
|
||||
'-m, --message <message>',
|
||||
'The message to use for the upgrade note',
|
||||
))
|
||||
.action((options) => createAction(options));
|
||||
|
||||
program
|
||||
.command('summary')
|
||||
.summary('Generate a local copy of the upgrade notes summary')
|
||||
.addArgument(
|
||||
new Argument('[version]', 'The Moodle version to create the summary notes for')
|
||||
)
|
||||
.action((version) => generateAction(version));
|
||||
|
||||
program
|
||||
.command('release')
|
||||
.summary('Generate the markdown copies of the upgrade notes for a Moodle release')
|
||||
.addArgument(
|
||||
new Argument('[version]', 'The Moodle version to create the release notes for')
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'--generate-upgrade-notes',
|
||||
'Generate the UPGRADING.md notes for the release. ' +
|
||||
'Note: This option is intended for use by the release manager when generating the upgrade notes.',
|
||||
)
|
||||
.default(true)
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'-d, --delete-notes',
|
||||
'Delete the notes after generating the UPGRADING.md notes for the release. ' +
|
||||
'Note: This option is intended for use by the release manager when generating the upgrade notes.' +
|
||||
'This option has no effect unless --generate-upgrade-notes is also set.'
|
||||
)
|
||||
.default(false)
|
||||
)
|
||||
.action((version, options) => generateAction(version, options));
|
||||
|
||||
program.parse();
|
||||
@@ -1,40 +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/>.
|
||||
|
||||
/**
|
||||
* Helper file for PhpStorm, and other IDEs, to provide better code completion.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
// Assume that anything returned by \core\di::get('...') is an instance of the first argument.
|
||||
override(\core\di::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
|
||||
// Assume that anything returned by \Psr\Container\ContainerInterface::get('...') is an instance of the first argument.
|
||||
override(\Psr\Container\ContainerInterface::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
|
||||
// Assume that anything returned by \DI\Container::get('...') is an instance of the first argument.
|
||||
override(\DI\Container::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
+4
-16
@@ -1,14 +1,9 @@
|
||||
{
|
||||
"customSyntax": "postcss-scss",
|
||||
"plugins": [
|
||||
"stylelint-csstree-validator"
|
||||
],
|
||||
"rules": {
|
||||
"csstree/validator": {
|
||||
"syntaxExtensions": [
|
||||
"sass"
|
||||
]
|
||||
},
|
||||
"csstree/validator": true,
|
||||
"at-rule-empty-line-before": [ "always",
|
||||
{"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
|
||||
],
|
||||
@@ -50,7 +45,7 @@
|
||||
"function-name-case": "lower",
|
||||
"function-parentheses-newline-inside": "always-multi-line",
|
||||
"function-parentheses-space-inside": "never-single-line",
|
||||
"function-url-scheme-disallowed-list": ["data"],
|
||||
"function-url-scheme-blacklist": ["data"],
|
||||
"function-whitespace-after": "always",
|
||||
"indentation": 4,
|
||||
"keyframe-declaration-no-important": true,
|
||||
@@ -89,19 +84,12 @@
|
||||
"selector-type-no-unknown": true,
|
||||
"string-no-newline": true,
|
||||
"time-min-milliseconds": 100,
|
||||
"unit-disallowed-list": ["pt"],
|
||||
"unit-blacklist": ["pt"],
|
||||
"unit-case": "lower",
|
||||
"unit-no-unknown": true,
|
||||
"value-keyword-case": ["lower", {"ignoreKeywords": ["/(@|$)/"]}],
|
||||
"value-list-comma-newline-after": "always-multi-line",
|
||||
"value-list-comma-space-after": "always-single-line",
|
||||
"value-list-comma-space-before": "never",
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/yui/**/*.css"],
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# Contributing to Moodle
|
||||
|
||||
[Moodle][1] is made by people like you. We are members of a big worldwide community of developers, designers, teachers, testers, translators and many more. We work in universities, schools, companies and other places. You are very welcome to join us and contribute to the project.
|
||||
|
||||
There are many ways that you can contribute to Moodle, not just through development. See our [community contribution guide][2] for some of the many ways that you can help.
|
||||
|
||||
## Github
|
||||
|
||||
> [!NOTE]
|
||||
> Please do not open pull requests via Github.
|
||||
|
||||
All issues should be reported via, and patched provided to the [Moodle Tracker][3].
|
||||
|
||||
The Moodle [Github][4] repository is a clone of the official Moodle repository, whcih can be found at https://git.moodle.org.
|
||||
|
||||
## Moodle core bug fixes and new features
|
||||
|
||||
Over the years, Moodle has developed a mature process for reporting, reviewing, and accepting patches. This is fully documented in our [documentation on development processes][6], but in summary:
|
||||
|
||||
- Every bug fix or new feature must have a tracker issue.
|
||||
- You publish the branch implementing the fix or new feature in your public clone of the moodle.git repository (typically on Github).
|
||||
- Your patch is peer-reviewed, discussed, integrated, tested and then released as a part of one of our weekly releases.
|
||||
- New features are developed on the `main` branch. Bug fixes are also backported to currently supported maintenance (stable) branches.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please do not publish security issues, or patches releating to them publicly.
|
||||
> See our [Responsible Disclosure Policy][5] for more information.
|
||||
|
||||
|
||||
## Moodle plugins
|
||||
|
||||
Moodle has a framework for additional plugins to extend its functionality. We
|
||||
have a Moodle plugins directory <https://moodle.org/plugins/> where you can
|
||||
register and maintain your plugin. Plugins hosted in the plugins directory can
|
||||
be easily installed and updated via the Moodle administration interface.
|
||||
|
||||
* You are expected to have a public source code repository with your plugin
|
||||
code.
|
||||
* After registering your plugin in the plugins directory it is reviewed before
|
||||
being published.
|
||||
* You are expected to continuously release updated versions of the plugin via
|
||||
the plugins directory. We do not pull from your code repository; you must do
|
||||
it explicitly.
|
||||
|
||||
For further details, see <https://moodledev.io/general/community/plugincontribution>.
|
||||
|
||||
[1]: https://moodle.org
|
||||
[2]: https://moodledev.io/general/community/contribute
|
||||
[3]: https://tracker.moodle.org
|
||||
[4]: https://github.com/moodle/moodle
|
||||
[5]: https://moodledev.io/general/development/process/security
|
||||
[6]: https://moodledev.io/general/development/process
|
||||
@@ -0,0 +1,56 @@
|
||||
CONTRIBUTING TO MOODLE
|
||||
======================
|
||||
|
||||
Moodle is made by people like you. We are members of a big worldwide community
|
||||
of developers, designers, teachers, testers, translators and other users. We
|
||||
work in universities, schools, companies and other places. You are very welcome
|
||||
to join us and contribute to the project.
|
||||
|
||||
See <https://moodledev.io/general/community/contribute> for the many ways you
|
||||
can help, not only with coding.
|
||||
|
||||
Moodle is open to community contributions to core, though all code must go
|
||||
through peer-review, automated behaviour testing, continuous integration and
|
||||
human post-integration checks.
|
||||
|
||||
Pull requests
|
||||
-------------
|
||||
|
||||
Please do not open pull requests via Github. The repository there is just a
|
||||
mirror of the official repository at <https://git.moodle.org>. Issues are
|
||||
reported and patches provided via <https://tracker.moodle.org>. See below for
|
||||
more information.
|
||||
|
||||
Moodle core bug fixes and new features
|
||||
--------------------------------------
|
||||
|
||||
During the years of intensive development, a mature process of including
|
||||
submitted patches has evolved.
|
||||
|
||||
* Every bug fix or new feature must have a tracker issue.
|
||||
* You publish the branch implementing the fix or new feature in your public
|
||||
clone of the moodle.git repository (typically on Github).
|
||||
* Your patch is peer-reviewed, discussed, integrated, tested and then released
|
||||
as a part of moodle.git.
|
||||
* New features are developed on the master branch. Bug fixes are also
|
||||
backported to currently supported maintenance (stable) branches.
|
||||
|
||||
For further details, see <https://moodledev.io/general/development/process>.
|
||||
|
||||
Moodle plugins
|
||||
--------------
|
||||
|
||||
Moodle has a framework for additional plugins to extend its functionality. We
|
||||
have a Moodle plugins directory <https://moodle.org/plugins/> where you can
|
||||
register and maintain your plugin. Plugins hosted in the plugins directory can
|
||||
be easily installed and updated via the Moodle administration interface.
|
||||
|
||||
* You are expected to have a public source code repository with your plugin
|
||||
code.
|
||||
* After registering your plugin in the plugins directory it is reviewed before
|
||||
being published.
|
||||
* You are expected to continuously release updated versions of the plugin via
|
||||
the plugins directory. We do not pull from your code repository; you must do
|
||||
it explicitly.
|
||||
|
||||
For further details, see <https://moodledev.io/general/community/plugincontribution>.
|
||||
@@ -0,0 +1,7 @@
|
||||
*** PLEASE DO NOT OPEN PULL REQUESTS VIA GITHUB ***
|
||||
|
||||
The moodle.git repository at Github is just a mirror of the official repository. We do not accept pull requests at Github.
|
||||
|
||||
See CONTRIBUTING.txt guidelines for how to contribute patches for Moodle. Thank you.
|
||||
|
||||
--
|
||||
@@ -1,48 +0,0 @@
|
||||
# Moodle
|
||||
|
||||
<p align="center"><a href="https://moodle.org" target="_blank" title="Moodle Website">
|
||||
<img src="https://raw.githubusercontent.com/moodle/moodle/main/.github/moodlelogo.svg" alt="The Moodle Logo">
|
||||
</a></p>
|
||||
|
||||
[Moodle][1] is the World's Open Source Learning Platform, widely used around the world by countless universities, schools, companies, and all manner of organisations and individuals.
|
||||
|
||||
Moodle is designed to allow educators, administrators and learners to create personalised learning environments with a single robust, secure and integrated system.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Read our [User documentation][3]
|
||||
- Discover our [developer documentation][5]
|
||||
- Take a look at our [demo site][4]
|
||||
|
||||
## Community
|
||||
|
||||
[moodle.org][1] is the central hub for the Moodle Community, with spaces for educators, administrators and developers to meet and work together.
|
||||
|
||||
You may also be interested in:
|
||||
|
||||
- attending a [Moodle Moot][6]
|
||||
- our regular series of [developer meetings][7]
|
||||
- the [Moodle User Association][8]
|
||||
|
||||
## Installation and hosting
|
||||
|
||||
Moodle is Free, and Open Source software. You can easily [download Moodle][9] and run it on your own web server, however you may prefer to work with one of our experienced [Moodle Partners][10].
|
||||
|
||||
Moodle also offers hosting through both [MoodleCloud][11], and our [partner network][10].
|
||||
|
||||
## License
|
||||
|
||||
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. For more information on our license see
|
||||
|
||||
[1]: https://moodle.org
|
||||
[2]: https://moodle.com
|
||||
[3]: https://docs.moodle.org/
|
||||
[4]: https://sandbox.moodledemo.net/
|
||||
[5]: https://moodledev.io
|
||||
[6]: https://moodle.com/events/mootglobal/
|
||||
[7]: https://moodledev.io/general/community/meetings
|
||||
[8]: https://moodleassociation.org/
|
||||
[9]: https://download.moodle.org
|
||||
[10]: https://moodle.com/partners
|
||||
[11]: https://moodle.com/cloud
|
||||
[12]: https://moodledev.io/general/license
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
.-..-.
|
||||
_____ | || |
|
||||
/____/-.---_ .---. .---. .-.| || | .---.
|
||||
| | _ _ |/ _ \/ _ \/ _ || |/ __ \
|
||||
* | | | | | || |_| || |_| || |_| || || |___/
|
||||
|_| |_| |_|\_____/\_____/\_____||_|\_____)
|
||||
|
||||
Moodle - the world's open source learning platform
|
||||
|
||||
Moodle <https://moodle.org> is a learning platform designed to provide
|
||||
educators, administrators and learners with a single robust, secure and
|
||||
integrated system to create personalised learning environments.
|
||||
|
||||
You can download Moodle <https://download.moodle.org> and run it on your own
|
||||
web server, ask one of our Moodle Partners <https://moodle.com/partners/> to
|
||||
assist you, or have a MoodleCloud site <https://moodle.com/cloud/> set up for
|
||||
you.
|
||||
|
||||
Moodle is widely used around the world by universities, schools, companies and
|
||||
all manner of organisations and individuals.
|
||||
|
||||
Moodle is provided freely as open source software, under the GNU General Public
|
||||
License <https://moodledev.io/general/license>.
|
||||
|
||||
Moodle is written in PHP and JavaScript and uses an SQL database for storing
|
||||
the data.
|
||||
|
||||
See <https://docs.moodle.org> for details of Moodle's many features.
|
||||
-1216
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
# core_admin (subsystem) Upgrade notes
|
||||
|
||||
## 4.5rc1
|
||||
|
||||
### Removed
|
||||
|
||||
- The HTTP Server setting "Use slash arguments" (slasharguments) configuration setting and related to it has been removed. Calling the option with $CFG->slasharguments or get_config('slasharguments') is no longer available.
|
||||
|
||||
For more information see [MDL-62640](https://tracker.moodle.org/browse/MDL-62640)
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Manage AI subsystem plugins settings.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
$action = required_param('action', PARAM_ALPHANUMEXT);
|
||||
$type = required_param('type', PARAM_PLUGIN); // Must be provider or placement.
|
||||
$name = required_param('name', PARAM_PLUGIN); // Plugin name, e.g openai.
|
||||
|
||||
$syscontext = context_system::instance();
|
||||
$PAGE->set_url('/admin/ai.php');
|
||||
$PAGE->set_context($syscontext);
|
||||
|
||||
require_admin();
|
||||
require_sesskey();
|
||||
|
||||
$return = new moodle_url('/admin/settings.php', ['section' => "aisettings$type"]);
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type("ai$type");
|
||||
$sortorder = array_flip(array_keys($plugins));
|
||||
|
||||
if (!isset($plugins[$name])) {
|
||||
throw new moodle_exception('aipluginnotfound', 'core_ai', $return, $name);
|
||||
}
|
||||
|
||||
$plugintypename = $plugins[$name]->type . '_' . $plugins[$name]->name;
|
||||
|
||||
switch ($action) {
|
||||
case 'disable':
|
||||
if ($plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
|
||||
$class::enable_plugin($name, false);
|
||||
}
|
||||
break;
|
||||
case 'enable':
|
||||
if (!$plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
|
||||
$class::enable_plugin($name, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
redirect($return);
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
define("core_admin/bulk_user_actions",["exports","core_reportbuilder/local/selectors","core_table/local/dynamic/events","core_form/changechecker","core/custom_interaction_events","jquery"],(function(_exports,reportSelectors,tableEvents,FormChangeChecker,CustomEvents,_jquery){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
|
||||
/**
|
||||
* Add bulk actions to the users list report
|
||||
*
|
||||
* @module core_admin/bulk_user_actions
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,reportSelectors=_interopRequireWildcard(reportSelectors),tableEvents=_interopRequireWildcard(tableEvents),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),CustomEvents=_interopRequireWildcard(CustomEvents),_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const Selectors_bulkActionsForm="form#user-bulk-action-form",Selectors_userReportWrapper='[data-region="report-user-list-wrapper"]',Selectors_checkbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',Selectors_masterCheckbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',Selectors_checkedRows='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked';_exports.init=()=>{var _userBulkForm$closest;const userBulkForm=document.querySelector(Selectors_bulkActionsForm),userReport=null==userBulkForm||null===(_userBulkForm$closest=userBulkForm.closest(Selectors_userReportWrapper))||void 0===_userBulkForm$closest?void 0:_userBulkForm$closest.querySelector(reportSelectors.regions.report);if(!userBulkForm||!userReport)return;const actionSelect=userBulkForm.querySelector("select");CustomEvents.define(actionSelect,[CustomEvents.events.accessibleChange]),(0,_jquery.default)(actionSelect).on(CustomEvents.events.accessibleChange,(event=>{if(event.target.value&&"0"!=="".concat(event.target.value)){const e=new Event("submit",{cancelable:!0});userBulkForm.dispatchEvent(e),e.defaultPrevented||(FormChangeChecker.markFormSubmitted(userBulkForm),userBulkForm.submit())}}));const updateUserIds=()=>{const selectedUsers=[...userReport.querySelectorAll(Selectors_checkedRows)],selectedUserIds=selectedUsers.map((check=>parseInt(check.value)));userBulkForm.querySelector('[name="userids"]').value=selectedUserIds.join(","),actionSelect.disabled=0===selectedUsers.length,actionSelect.disabled&&(actionSelect.value="0");const selectedUsersNames=selectedUsers.map((check=>document.querySelector('label[for="'.concat(check.id,'"]')).textContent));userBulkForm.data={userids:selectedUserIds,usernames:selectedUsersNames}};updateUserIds(),document.addEventListener("change",(event=>{(event.target.matches(Selectors_checkbox)||event.target.matches(Selectors_masterCheckbox))&&userReport.contains(event.target)&&updateUserIds()})),document.addEventListener(tableEvents.tableContentRefreshed,(event=>{userReport.contains(event.target)&&updateUserIds()}))}}));
|
||||
|
||||
//# sourceMappingURL=bulk_user_actions.min.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
-12
@@ -1,12 +0,0 @@
|
||||
define("core_admin/expand_hash",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.expandSection=void 0;
|
||||
/**
|
||||
* Expand the collapse section element.
|
||||
*
|
||||
* @module core_admin/expand_hash
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.5
|
||||
*/
|
||||
const SELECTORS_COLLAPSE_ELEMENTS='[data-toggle="collapse"]',SELECTORS_FOCUSTHENEXPAND_ELEMENTS=".focus-expand";_exports.init=()=>{const focusexpand=document.querySelector(SELECTORS_FOCUSTHENEXPAND_ELEMENTS);null==focusexpand||focusexpand.addEventListener("click",(()=>{expandSection("".concat(focusexpand.getAttribute("href")))}))};const expandSection=hash=>{const container=document.querySelector(hash),targetContainer=null==container?void 0:container.querySelector(SELECTORS_COLLAPSE_ELEMENTS);if("false"===(null==targetContainer?void 0:targetContainer.getAttribute("aria-expanded"))){const collapseId=targetContainer.getAttribute("aria-controls"),collapseContainer=document.getElementById(collapseId);collapseContainer.classList.remove("collapse"),collapseContainer.classList.add("show"),targetContainer.setAttribute("aria-expanded","true"),targetContainer.classList.remove("collapsed");const collapseElement=document.querySelector(".collapseexpand.collapsemenu");collapseElement.setAttribute("aria-expanded","true"),collapseElement.classList.remove("collapsed")}};_exports.expandSection=expandSection}));
|
||||
|
||||
//# sourceMappingURL=expand_hash.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"expand_hash.min.js","sources":["../src/expand_hash.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 * Expand the collapse section element.\n *\n * @module core_admin/expand_hash\n * @copyright Meirza <meirza.arson@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.5\n */\n\nconst SELECTORS = {\n COLLAPSE_ELEMENTS: '[data-toggle=\"collapse\"]',\n FOCUSTHENEXPAND_ELEMENTS: '.focus-expand',\n};\n\n/**\n * Initializes the focus and expand functionality.\n */\nexport const init = () => {\n // Select all collapsible elements only.\n const focusexpand = document.querySelector(SELECTORS.FOCUSTHENEXPAND_ELEMENTS);\n\n // Add click event listener to the anchor element\n focusexpand?.addEventListener('click', () => {\n expandSection(`${focusexpand.getAttribute('href')}`);\n });\n};\n\n/**\n * Expands a section based on the provided URL hash.\n *\n * This function takes a hash string, finds the corresponding element in the DOM,\n * and expands it if it is currently collapsed. It updates the necessary ARIA\n * attributes and classes to reflect the expanded state.\n *\n * @param {string} hash - The hash (e.g., '#elementId') of the element to expand.\n */\nexport const expandSection = (hash) => {\n const container = document.querySelector(hash);\n const targetContainer = container?.querySelector(SELECTORS.COLLAPSE_ELEMENTS);\n\n if (targetContainer?.getAttribute('aria-expanded') === 'false') {\n const collapseId = targetContainer.getAttribute('aria-controls');\n const collapseContainer = document.getElementById(collapseId);\n\n // Show the content.\n collapseContainer.classList.remove('collapse');\n collapseContainer.classList.add('show');\n\n // Update aria-expanded attribute to reflect the new state.\n targetContainer.setAttribute('aria-expanded', 'true');\n targetContainer.classList.remove('collapsed');\n\n // Get collapse expand menu element.\n const collapseElement = document.querySelector('.collapseexpand.collapsemenu');\n // Ensure it gets noticed to make it work.\n collapseElement.setAttribute('aria-expanded', 'true');\n collapseElement.classList.remove('collapsed');\n }\n};\n"],"names":["SELECTORS","focusexpand","document","querySelector","addEventListener","expandSection","getAttribute","hash","container","targetContainer","collapseId","collapseContainer","getElementById","classList","remove","add","setAttribute","collapseElement"],"mappings":";;;;;;;;;MAwBMA,4BACiB,2BADjBA,mCAEwB,8BAMV,WAEVC,YAAcC,SAASC,cAAcH,oCAG3CC,MAAAA,aAAAA,YAAaG,iBAAiB,SAAS,KACnCC,wBAAiBJ,YAAYK,aAAa,oBAarCD,cAAiBE,aACpBC,UAAYN,SAASC,cAAcI,MACnCE,gBAAkBD,MAAAA,iBAAAA,UAAWL,cAAcH,gCAEM,WAAnDS,MAAAA,uBAAAA,gBAAiBH,aAAa,kBAA8B,OACtDI,WAAaD,gBAAgBH,aAAa,iBAC1CK,kBAAoBT,SAASU,eAAeF,YAGlDC,kBAAkBE,UAAUC,OAAO,YACnCH,kBAAkBE,UAAUE,IAAI,QAGhCN,gBAAgBO,aAAa,gBAAiB,QAC9CP,gBAAgBI,UAAUC,OAAO,mBAG3BG,gBAAkBf,SAASC,cAAc,gCAE/Cc,gBAAgBD,aAAa,gBAAiB,QAC9CC,gBAAgBJ,UAAUC,OAAO"}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
define("core_admin/plugin_management_table",["exports","core_table/dynamic","core_table/local/dynamic/selectors","core/ajax","core/pending","core/notification"],(function(_exports,_dynamic,Selectors,_ajax,_pending,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};let watching=!1;return _exports.default=class{constructor(){!function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"clickHandlers",[]),this.addClickHandler(this.handleStateToggle),this.addClickHandler(this.handleMoveUpDown),this.registerEventListeners()}static init(){watching||(watching=!0,new this)}addClickHandler(handler){this.clickHandlers.push(handler.bind(this))}registerEventListeners(){document.addEventListener("click",function(e){const tableRoot=this.getTableRoot(e);tableRoot&&this.clickHandlers.forEach((handler=>handler(tableRoot,e)))}.bind(this))}getTableRoot(e){const tableRoot=e.target.closest(Selectors.main.region);return tableRoot||!1}setPluginState(methodname,plugin,state){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state}}])[0]}setPluginOrder(methodname,plugin,direction){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,direction:direction}}])[0]}async handleStateToggle(tableRoot,e){const stateToggle=e.target.closest('[data-action="togglestate"][data-toggle-method]');if(stateToggle){e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:togglestate");await this.setPluginState(stateToggle.dataset.toggleMethod,stateToggle.dataset.plugin,"1"===stateToggle.dataset.state?0:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]);updatedRoot.querySelector('[data-action="togglestate"][data-plugin="'.concat(stateToggle.dataset.plugin,'"]')).focus(),this.removeTooltips(),pendingPromise.resolve()}}async handleMoveUpDown(tableRoot,e){const actionLink=e.target.closest('[data-action="move"][data-method][data-direction]');if(!actionLink)return;e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:processAction");await this.setPluginOrder(actionLink.dataset.method,actionLink.dataset.plugin,"up"===actionLink.dataset.direction?-1:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),exactMatch=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"][data-direction="').concat(actionLink.dataset.direction,'"]'));var _updatedRoot$querySel;exactMatch?exactMatch.focus():null===(_updatedRoot$querySel=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"]')))||void 0===_updatedRoot$querySel||_updatedRoot$querySel.focus();pendingPromise.resolve()}removeTooltips(){const tooltips=document.querySelectorAll('[id*="tooltip"]');tooltips.length>0&&tooltips.forEach((tooltip=>{tooltip.remove()}))}},_exports.default}));
|
||||
define("core_admin/plugin_management_table",["exports","core_table/dynamic","core_table/local/dynamic/selectors","core/ajax","core/pending","core/notification"],(function(_exports,_dynamic,Selectors,_ajax,_pending,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};let watching=!1;return _exports.default=class{constructor(){!function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"clickHandlers",[]),this.addClickHandler(this.handleStateToggle),this.addClickHandler(this.handleMoveUpDown),this.registerEventListeners()}static init(){watching||(watching=!0,new this)}addClickHandler(handler){this.clickHandlers.push(handler.bind(this))}registerEventListeners(){document.addEventListener("click",function(e){const tableRoot=this.getTableRoot(e);tableRoot&&this.clickHandlers.forEach((handler=>handler(tableRoot,e)))}.bind(this))}getTableRoot(e){const tableRoot=e.target.closest(Selectors.main.region);return tableRoot||!1}setPluginState(methodname,plugin,state){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state}}])[0]}setPluginOrder(methodname,plugin,direction){return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,direction:direction}}])[0]}async handleStateToggle(tableRoot,e){const stateToggle=e.target.closest('[data-action="togglestate"][data-toggle-method]');if(stateToggle){e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:togglestate");await this.setPluginState(stateToggle.dataset.toggleMethod,stateToggle.dataset.plugin,"1"===stateToggle.dataset.state?0:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]);updatedRoot.querySelector('[data-action="togglestate"][data-plugin="'.concat(stateToggle.dataset.plugin,'"]')).focus(),pendingPromise.resolve()}}async handleMoveUpDown(tableRoot,e){const actionLink=e.target.closest('[data-action="move"][data-method][data-direction]');if(!actionLink)return;e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:processAction");await this.setPluginOrder(actionLink.dataset.method,actionLink.dataset.plugin,"up"===actionLink.dataset.direction?-1:1);const[updatedRoot]=await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),exactMatch=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"][data-direction="').concat(actionLink.dataset.direction,'"]'));var _updatedRoot$querySel;exactMatch?exactMatch.focus():null===(_updatedRoot$querySel=updatedRoot.querySelector('[data-action="move"][data-plugin="'.concat(actionLink.dataset.plugin,'"]')))||void 0===_updatedRoot$querySel||_updatedRoot$querySel.focus();pendingPromise.resolve()}},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=plugin_management_table.min.js.map
|
||||
File diff suppressed because one or more lines are too long
-11
@@ -1,11 +0,0 @@
|
||||
define("core_admin/plugins_overview",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){const filters=document.querySelectorAll(SELECTORS_PLUGIN_FILTERS),pluginRows=document.querySelectorAll(SELECTORS_PLUGIN_ROWS),pluginTypeRows=document.querySelectorAll(SELECTORS_PLUGIN_TYPE_ROWS),filterPlugins=target=>{const filterBy=target.getAttribute("data-filterby"),headerVisibility={};for(const row of pluginRows){const type=[...row.classList].find((s=>s.startsWith("type-"))),visible="all"===filterBy||row.classList.contains(filterBy);row.style.display=visible?null:"none",visible&&type&&(headerVisibility[type]=!0)}for(const row of pluginTypeRows){const type=[...row.classList].find((s=>s.startsWith("type-")));if(type){const visible="all"===filterBy||headerVisibility[type];row.style.display=visible?null:"none"}}filters.forEach((el=>el.classList.remove("active"))),target.classList.add("active")};if(filters.forEach((target=>target.addEventListener("click",(e=>{e.preventDefault(),window.history.replaceState({},null,e.target.href),filterPlugins(target)})))),window.location.hash.length>1){const anchor=window.location.hash.substring(1),target=[...filters].find((t=>t.getAttribute("data-filterby")===anchor));target&&filterPlugins(target)}};
|
||||
/**
|
||||
* Allows to filter the plugin list on plugins overview page
|
||||
*
|
||||
* @module core_admin/plugins_overview
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
const SELECTORS_PLUGIN_FILTERS="#plugins-overview-panel [data-filterby]",SELECTORS_PLUGIN_ROWS="table#plugins-control-panel tbody tr:not(.plugintypeheader)",SELECTORS_PLUGIN_TYPE_ROWS="table#plugins-control-panel tbody tr.plugintypeheader"}));
|
||||
|
||||
//# sourceMappingURL=plugins_overview.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"plugins_overview.min.js","sources":["../src/plugins_overview.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 * Allows to filter the plugin list on plugins overview page\n *\n * @module core_admin/plugins_overview\n * @copyright 2024 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst SELECTORS = {\n PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',\n PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',\n PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',\n};\n\n/**\n * Initialise filters for the \"Plugins overview\" page\n */\nexport function init() {\n const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);\n const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);\n const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);\n\n const filterPlugins = (target) => {\n const filterBy = target.getAttribute('data-filterby');\n const headerVisibility = {};\n\n // Hide all plugin rows in the plugin table that do not match the filter and show all others.\n for (const row of pluginRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);\n row.style.display = visible ? null : 'none';\n if (visible && type) {\n headerVisibility[type] = true;\n }\n }\n\n // Hide all the plugin type headers that do not have any visible plugins and show all others.\n for (const row of pluginTypeRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n if (type) {\n const visible = filterBy === 'all' || headerVisibility[type];\n row.style.display = visible ? null : 'none';\n }\n }\n\n // Toggle 'active' class for the selected filter.\n filters.forEach(el => el.classList.remove('active'));\n target.classList.add('active');\n };\n\n // Add event listeners for the links changing plugins filters.\n filters\n .forEach(target => target.addEventListener('click', (e) => {\n e.preventDefault();\n window.history.replaceState({}, null, e.target.href);\n filterPlugins(target);\n }));\n\n // Pre-filter plugins based on the current url anchor.\n if (window.location.hash.length > 1) {\n const anchor = window.location.hash.substring(1);\n const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);\n if (target) {\n filterPlugins(target);\n }\n }\n}\n"],"names":["filters","document","querySelectorAll","SELECTORS","pluginRows","pluginTypeRows","filterPlugins","target","filterBy","getAttribute","headerVisibility","row","type","classList","find","s","startsWith","visible","contains","style","display","forEach","el","remove","add","addEventListener","e","preventDefault","window","history","replaceState","href","location","hash","length","anchor","substring","t"],"mappings":"4JAiCUA,QAAUC,SAASC,iBAAiBC,0BACpCC,WAAaH,SAASC,iBAAiBC,uBACvCE,eAAiBJ,SAASC,iBAAiBC,4BAE3CG,cAAiBC,eACbC,SAAWD,OAAOE,aAAa,iBAC/BC,iBAAmB,OAGpB,MAAMC,OAAOP,WAAY,OACpBQ,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,WACjDC,QAAuB,QAAbT,UAA4BG,IAAIE,UAAUK,SAASV,UACnEG,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,OACjCA,SAAWL,OACXF,iBAAiBE,OAAQ,OAK5B,MAAMD,OAAON,eAAgB,OACxBO,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,cACnDJ,KAAM,OACAK,QAAuB,QAAbT,UAAsBE,iBAAiBE,MACvDD,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,QAK7CjB,QAAQqB,SAAQC,IAAMA,GAAGT,UAAUU,OAAO,YAC1ChB,OAAOM,UAAUW,IAAI,cAIzBxB,QACCqB,SAAQd,QAAUA,OAAOkB,iBAAiB,SAAUC,IACjDA,EAAEC,iBACFC,OAAOC,QAAQC,aAAa,GAAI,KAAMJ,EAAEnB,OAAOwB,MAC/CzB,cAAcC,aAIdqB,OAAOI,SAASC,KAAKC,OAAS,EAAG,OAC3BC,OAASP,OAAOI,SAASC,KAAKG,UAAU,GACxC7B,OAAS,IAAIP,SAASc,MAAKuB,GAAKA,EAAE5B,aAAa,mBAAqB0B,SACtE5B,QACAD,cAAcC;;;;;;;;MAvDpBJ,yBACc,0CADdA,sBAEW,8DAFXA,2BAGgB"}
|
||||
@@ -1,10 +0,0 @@
|
||||
define("core_admin/themeselector/preview_modal",["exports","core/modal_events","core/modal_cancel","core/modal_save_cancel","core/notification","core/templates","core/str"],(function(_exports,_modal_events,_modal_cancel,_modal_save_cancel,_notification,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Modal for theme previews.
|
||||
*
|
||||
* @module core_admin/themeselector/preview_modal
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal_events=_interopRequireDefault(_modal_events),_modal_cancel=_interopRequireDefault(_modal_cancel),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);const SELECTORS_THEMES_CONTAINER="themelist",SELECTORS_PREVIEW='[data-action="preview"]';_exports.init=()=>{registerListenerEvents()};const registerListenerEvents=()=>{document.addEventListener("click",(e=>{const preview=e.target.closest(SELECTORS_PREVIEW);preview&&buildModal(preview).catch(_notification.default.exception)}))},buildModal=async element=>{let description=await(0,_str.getString)("choosereadme","theme_"+element.getAttribute("data-choose"));const themesContainer=document.getElementById(SELECTORS_THEMES_CONTAINER),definedInConfig=parseInt(themesContainer.dataset.definedinconfig),data={name:element.getAttribute("data-name"),image:element.getAttribute("data-image"),description:description.replace(/<[^>]+>/g," "),current:element.getAttribute("data-current"),actionurl:element.getAttribute("data-actionurl"),choose:element.getAttribute("data-choose"),sesskey:element.getAttribute("data-sesskey"),definedinconfig:definedInConfig};let modalTemplate=_modal_save_cancel.default;(data.current||data.definedinconfig)&&(modalTemplate=_modal_cancel.default);const modal=await modalTemplate.create({title:data.name,body:_templates.default.render("core_admin/themeselector/theme_preview_modal",data),large:!0,buttons:{save:(0,_str.getString)("selecttheme","moodle"),cancel:(0,_str.getString)("closebuttontitle","moodle")},show:!0});modal.getRoot().on(_modal_events.default.save,(()=>{modal.getRoot().find("form").submit()}))}}));
|
||||
|
||||
//# sourceMappingURL=preview_modal.min.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"preview_modal.min.js","sources":["../../src/themeselector/preview_modal.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 * Modal for theme previews.\n *\n * @module core_admin/themeselector/preview_modal\n * @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalEvents from 'core/modal_events';\nimport ModalCancel from 'core/modal_cancel';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\n\nconst SELECTORS = {\n THEMES_CONTAINER: 'themelist',\n PREVIEW: '[data-action=\"preview\"]',\n};\n\n/**\n * Entrypoint of the js.\n *\n * @method init\n */\nexport const init = () => {\n registerListenerEvents();\n};\n\n/**\n * Register theme related event listeners.\n *\n * @method registerListenerEvents\n */\nconst registerListenerEvents = () => {\n document.addEventListener('click', (e) => {\n const preview = e.target.closest(SELECTORS.PREVIEW);\n if (preview) {\n buildModal(preview).catch(Notification.exception);\n }\n });\n};\n\n/**\n * Build the modal with the provided data.\n *\n * @method buildModal\n * @param {object} element\n */\nconst buildModal = async(element) => {\n\n // This string can be long. We will fetch it with JS as opposed to passing it as an attribute.\n let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose'));\n\n const themesContainer = document.getElementById(SELECTORS.THEMES_CONTAINER);\n const definedInConfig = parseInt(themesContainer.dataset.definedinconfig);\n // Prepare data for modal.\n const data = {\n name: element.getAttribute('data-name'),\n image: element.getAttribute('data-image'),\n description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags.\n current: element.getAttribute('data-current'),\n actionurl: element.getAttribute('data-actionurl'),\n choose: element.getAttribute('data-choose'),\n sesskey: element.getAttribute('data-sesskey'),\n definedinconfig: definedInConfig,\n };\n\n // Determine which modal template we should use.\n let modalTemplate = ModalSaveCancel;\n if (data.current || data.definedinconfig) {\n modalTemplate = ModalCancel;\n }\n\n const modal = await modalTemplate.create({\n title: data.name,\n body: Templates.render('core_admin/themeselector/theme_preview_modal', data),\n large: true,\n buttons: {\n 'save': getString('selecttheme', 'moodle'),\n 'cancel': getString('closebuttontitle', 'moodle'),\n },\n show: true,\n });\n\n modal.getRoot().on(ModalEvents.save, () => {\n modal.getRoot().find('form').submit();\n });\n};\n"],"names":["SELECTORS","registerListenerEvents","document","addEventListener","e","preview","target","closest","buildModal","catch","Notification","exception","async","description","element","getAttribute","themesContainer","getElementById","definedInConfig","parseInt","dataset","definedinconfig","data","name","image","replace","current","actionurl","choose","sesskey","modalTemplate","ModalSaveCancel","ModalCancel","modal","create","title","body","Templates","render","large","buttons","show","getRoot","on","ModalEvents","save","find","submit"],"mappings":";;;;;;;gWA8BMA,2BACgB,YADhBA,kBAEO,wCAQO,KAChBC,gCAQEA,uBAAyB,KAC3BC,SAASC,iBAAiB,SAAUC,UAC1BC,QAAUD,EAAEE,OAAOC,QAAQP,mBAC7BK,SACAG,WAAWH,SAASI,MAAMC,sBAAaC,eAW7CH,WAAaI,MAAAA,cAGXC,kBAAoB,kBAAU,eAAgB,SAAWC,QAAQC,aAAa,sBAE5EC,gBAAkBd,SAASe,eAAejB,4BAC1CkB,gBAAkBC,SAASH,gBAAgBI,QAAQC,iBAEnDC,KAAO,CACTC,KAAMT,QAAQC,aAAa,aAC3BS,MAAOV,QAAQC,aAAa,cAC5BF,YAAaA,YAAYY,QAAQ,WAAY,KAC7CC,QAASZ,QAAQC,aAAa,gBAC9BY,UAAWb,QAAQC,aAAa,kBAChCa,OAAQd,QAAQC,aAAa,eAC7Bc,QAASf,QAAQC,aAAa,gBAC9BM,gBAAiBH,qBAIjBY,cAAgBC,4BAChBT,KAAKI,SAAWJ,KAAKD,mBACrBS,cAAgBE,6BAGdC,YAAcH,cAAcI,OAAO,CACrCC,MAAOb,KAAKC,KACZa,KAAMC,mBAAUC,OAAO,+CAAgDhB,MACvEiB,OAAO,EACPC,QAAS,OACG,kBAAU,cAAe,kBACvB,kBAAU,mBAAoB,WAE5CC,MAAM,IAGVR,MAAMS,UAAUC,GAAGC,sBAAYC,MAAM,KACjCZ,MAAMS,UAAUI,KAAK,QAAQC"}
|
||||
@@ -1,97 +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/>.
|
||||
|
||||
/**
|
||||
* Add bulk actions to the users list report
|
||||
*
|
||||
* @module core_admin/bulk_user_actions
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import * as reportSelectors from 'core_reportbuilder/local/selectors';
|
||||
import * as tableEvents from 'core_table/local/dynamic/events';
|
||||
import * as FormChangeChecker from 'core_form/changechecker';
|
||||
import * as CustomEvents from 'core/custom_interaction_events';
|
||||
import jQuery from 'jquery';
|
||||
|
||||
const Selectors = {
|
||||
bulkActionsForm: 'form#user-bulk-action-form',
|
||||
userReportWrapper: '[data-region="report-user-list-wrapper"]',
|
||||
checkbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',
|
||||
masterCheckbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',
|
||||
checkedRows: '[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise module
|
||||
*/
|
||||
export const init = () => {
|
||||
|
||||
const userBulkForm = document.querySelector(Selectors.bulkActionsForm);
|
||||
const userReport = userBulkForm?.closest(Selectors.userReportWrapper)?.querySelector(reportSelectors.regions.report);
|
||||
if (!userBulkForm || !userReport) {
|
||||
return;
|
||||
}
|
||||
const actionSelect = userBulkForm.querySelector('select');
|
||||
CustomEvents.define(actionSelect, [CustomEvents.events.accessibleChange]);
|
||||
|
||||
jQuery(actionSelect).on(CustomEvents.events.accessibleChange, event => {
|
||||
if (event.target.value && `${event.target.value}` !== "0") {
|
||||
const e = new Event('submit', {cancelable: true});
|
||||
userBulkForm.dispatchEvent(e);
|
||||
if (!e.defaultPrevented) {
|
||||
FormChangeChecker.markFormSubmitted(userBulkForm);
|
||||
userBulkForm.submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Every time the checkboxes in the report are changed, update the list of users in the form values
|
||||
// and enable/disable the action select.
|
||||
const updateUserIds = () => {
|
||||
const selectedUsers = [...userReport.querySelectorAll(Selectors.checkedRows)];
|
||||
const selectedUserIds = selectedUsers.map(check => parseInt(check.value));
|
||||
userBulkForm.querySelector('[name="userids"]').value = selectedUserIds.join(',');
|
||||
|
||||
// Disable the action selector if nothing selected, and reset the current selection.
|
||||
actionSelect.disabled = selectedUsers.length === 0;
|
||||
if (actionSelect.disabled) {
|
||||
actionSelect.value = "0";
|
||||
}
|
||||
|
||||
const selectedUsersNames = selectedUsers.map(check => document.querySelector(`label[for="${check.id}"]`).textContent);
|
||||
// Add the user ids and names to the form data attributes so they can be available from the
|
||||
// other JS modules that listen to the form submit event.
|
||||
userBulkForm.data = {userids: selectedUserIds, usernames: selectedUsersNames};
|
||||
};
|
||||
|
||||
updateUserIds();
|
||||
|
||||
document.addEventListener('change', event => {
|
||||
// When checkboxes are checked next to individual users or the master toggle (Select all/none).
|
||||
if ((event.target.matches(Selectors.checkbox) || event.target.matches(Selectors.masterCheckbox))
|
||||
&& userReport.contains(event.target)) {
|
||||
updateUserIds();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener(tableEvents.tableContentRefreshed, event => {
|
||||
// When the report contents is updated (i.e. page is changed, filters applied, etc).
|
||||
if (userReport.contains(event.target)) {
|
||||
updateUserIds();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,74 +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/>.
|
||||
|
||||
/**
|
||||
* Expand the collapse section element.
|
||||
*
|
||||
* @module core_admin/expand_hash
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.5
|
||||
*/
|
||||
|
||||
const SELECTORS = {
|
||||
COLLAPSE_ELEMENTS: '[data-toggle="collapse"]',
|
||||
FOCUSTHENEXPAND_ELEMENTS: '.focus-expand',
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the focus and expand functionality.
|
||||
*/
|
||||
export const init = () => {
|
||||
// Select all collapsible elements only.
|
||||
const focusexpand = document.querySelector(SELECTORS.FOCUSTHENEXPAND_ELEMENTS);
|
||||
|
||||
// Add click event listener to the anchor element
|
||||
focusexpand?.addEventListener('click', () => {
|
||||
expandSection(`${focusexpand.getAttribute('href')}`);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Expands a section based on the provided URL hash.
|
||||
*
|
||||
* This function takes a hash string, finds the corresponding element in the DOM,
|
||||
* and expands it if it is currently collapsed. It updates the necessary ARIA
|
||||
* attributes and classes to reflect the expanded state.
|
||||
*
|
||||
* @param {string} hash - The hash (e.g., '#elementId') of the element to expand.
|
||||
*/
|
||||
export const expandSection = (hash) => {
|
||||
const container = document.querySelector(hash);
|
||||
const targetContainer = container?.querySelector(SELECTORS.COLLAPSE_ELEMENTS);
|
||||
|
||||
if (targetContainer?.getAttribute('aria-expanded') === 'false') {
|
||||
const collapseId = targetContainer.getAttribute('aria-controls');
|
||||
const collapseContainer = document.getElementById(collapseId);
|
||||
|
||||
// Show the content.
|
||||
collapseContainer.classList.remove('collapse');
|
||||
collapseContainer.classList.add('show');
|
||||
|
||||
// Update aria-expanded attribute to reflect the new state.
|
||||
targetContainer.setAttribute('aria-expanded', 'true');
|
||||
targetContainer.classList.remove('collapsed');
|
||||
|
||||
// Get collapse expand menu element.
|
||||
const collapseElement = document.querySelector('.collapseexpand.collapsemenu');
|
||||
// Ensure it gets noticed to make it work.
|
||||
collapseElement.setAttribute('aria-expanded', 'true');
|
||||
collapseElement.classList.remove('collapsed');
|
||||
}
|
||||
};
|
||||
@@ -138,10 +138,6 @@ export default class {
|
||||
|
||||
// Refocus on the link that as pressed in the first place.
|
||||
updatedRoot.querySelector(`[data-action="togglestate"][data-plugin="${stateToggle.dataset.plugin}"]`).focus();
|
||||
|
||||
// When clicking the toggle and it remains focused, a new tooltip will be generated.
|
||||
// Therefore, the old tooltip should be removed.
|
||||
this.removeTooltips();
|
||||
pendingPromise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -180,16 +176,4 @@ export default class {
|
||||
|
||||
pendingPromise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tooltips.
|
||||
*/
|
||||
removeTooltips() {
|
||||
const tooltips = document.querySelectorAll('[id*="tooltip"]');
|
||||
if (tooltips.length > 0) {
|
||||
tooltips.forEach(tooltip => {
|
||||
tooltip.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +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/>.
|
||||
|
||||
/**
|
||||
* Allows to filter the plugin list on plugins overview page
|
||||
*
|
||||
* @module core_admin/plugins_overview
|
||||
* @copyright 2024 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
const SELECTORS = {
|
||||
PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',
|
||||
PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',
|
||||
PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise filters for the "Plugins overview" page
|
||||
*/
|
||||
export function init() {
|
||||
const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);
|
||||
const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);
|
||||
const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);
|
||||
|
||||
const filterPlugins = (target) => {
|
||||
const filterBy = target.getAttribute('data-filterby');
|
||||
const headerVisibility = {};
|
||||
|
||||
// Hide all plugin rows in the plugin table that do not match the filter and show all others.
|
||||
for (const row of pluginRows) {
|
||||
const type = [...row.classList].find(s => s.startsWith('type-'));
|
||||
const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);
|
||||
row.style.display = visible ? null : 'none';
|
||||
if (visible && type) {
|
||||
headerVisibility[type] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide all the plugin type headers that do not have any visible plugins and show all others.
|
||||
for (const row of pluginTypeRows) {
|
||||
const type = [...row.classList].find(s => s.startsWith('type-'));
|
||||
if (type) {
|
||||
const visible = filterBy === 'all' || headerVisibility[type];
|
||||
row.style.display = visible ? null : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle 'active' class for the selected filter.
|
||||
filters.forEach(el => el.classList.remove('active'));
|
||||
target.classList.add('active');
|
||||
};
|
||||
|
||||
// Add event listeners for the links changing plugins filters.
|
||||
filters
|
||||
.forEach(target => target.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
window.history.replaceState({}, null, e.target.href);
|
||||
filterPlugins(target);
|
||||
}));
|
||||
|
||||
// Pre-filter plugins based on the current url anchor.
|
||||
if (window.location.hash.length > 1) {
|
||||
const anchor = window.location.hash.substring(1);
|
||||
const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);
|
||||
if (target) {
|
||||
filterPlugins(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +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/>.
|
||||
|
||||
/**
|
||||
* Modal for theme previews.
|
||||
*
|
||||
* @module core_admin/themeselector/preview_modal
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import ModalEvents from 'core/modal_events';
|
||||
import ModalCancel from 'core/modal_cancel';
|
||||
import ModalSaveCancel from 'core/modal_save_cancel';
|
||||
import Notification from 'core/notification';
|
||||
import Templates from 'core/templates';
|
||||
import {getString} from 'core/str';
|
||||
|
||||
const SELECTORS = {
|
||||
THEMES_CONTAINER: 'themelist',
|
||||
PREVIEW: '[data-action="preview"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* Entrypoint of the js.
|
||||
*
|
||||
* @method init
|
||||
*/
|
||||
export const init = () => {
|
||||
registerListenerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register theme related event listeners.
|
||||
*
|
||||
* @method registerListenerEvents
|
||||
*/
|
||||
const registerListenerEvents = () => {
|
||||
document.addEventListener('click', (e) => {
|
||||
const preview = e.target.closest(SELECTORS.PREVIEW);
|
||||
if (preview) {
|
||||
buildModal(preview).catch(Notification.exception);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the modal with the provided data.
|
||||
*
|
||||
* @method buildModal
|
||||
* @param {object} element
|
||||
*/
|
||||
const buildModal = async(element) => {
|
||||
|
||||
// This string can be long. We will fetch it with JS as opposed to passing it as an attribute.
|
||||
let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose'));
|
||||
|
||||
const themesContainer = document.getElementById(SELECTORS.THEMES_CONTAINER);
|
||||
const definedInConfig = parseInt(themesContainer.dataset.definedinconfig);
|
||||
// Prepare data for modal.
|
||||
const data = {
|
||||
name: element.getAttribute('data-name'),
|
||||
image: element.getAttribute('data-image'),
|
||||
description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags.
|
||||
current: element.getAttribute('data-current'),
|
||||
actionurl: element.getAttribute('data-actionurl'),
|
||||
choose: element.getAttribute('data-choose'),
|
||||
sesskey: element.getAttribute('data-sesskey'),
|
||||
definedinconfig: definedInConfig,
|
||||
};
|
||||
|
||||
// Determine which modal template we should use.
|
||||
let modalTemplate = ModalSaveCancel;
|
||||
if (data.current || data.definedinconfig) {
|
||||
modalTemplate = ModalCancel;
|
||||
}
|
||||
|
||||
const modal = await modalTemplate.create({
|
||||
title: data.name,
|
||||
body: Templates.render('core_admin/themeselector/theme_preview_modal', data),
|
||||
large: true,
|
||||
buttons: {
|
||||
'save': getString('selecttheme', 'moodle'),
|
||||
'cancel': getString('closebuttontitle', 'moodle'),
|
||||
},
|
||||
show: true,
|
||||
});
|
||||
|
||||
modal.getRoot().on(ModalEvents.save, () => {
|
||||
modal.getRoot().find('form').submit();
|
||||
});
|
||||
};
|
||||
@@ -62,7 +62,7 @@ class filesize extends \admin_setting {
|
||||
* @param int|null $defaultunit GB, MB, etc. (in bytes)
|
||||
*/
|
||||
public function __construct(string $name, string $visiblename, string $description,
|
||||
?int $defaultvalue = null, ?int $defaultunit = null) {
|
||||
int $defaultvalue = null, int $defaultunit = null) {
|
||||
|
||||
$defaultsetting = self::parse_bytes($defaultvalue);
|
||||
|
||||
|
||||
@@ -1,71 +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 core_admin\output;
|
||||
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
|
||||
/**
|
||||
* Theme selector renderable.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class theme_selector implements renderable, templatable {
|
||||
|
||||
/** @var array $themedata Theme data to pass to the template. */
|
||||
private $themedata = null;
|
||||
|
||||
/** @var bool Whether $CFG->theme is defined in config.php. */
|
||||
private $definedinconfig;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $themedata Theme data used for template.
|
||||
* @param bool $definedinconfig Whether $CFG->theme is defined in config.php.
|
||||
*/
|
||||
public function __construct(array $themedata, bool $definedinconfig = false) {
|
||||
$this->themedata = $themedata;
|
||||
$this->definedinconfig = $definedinconfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output Renderer base.
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): stdClass {
|
||||
|
||||
$data = new stdClass();
|
||||
// Theme data used to populate cards and modal.
|
||||
$data->themes = $this->themedata;
|
||||
// Reset theme caches button.
|
||||
$reseturl = new moodle_url('/admin/themeselector.php', ['sesskey' => sesskey(), 'reset' => 1]);
|
||||
$resetbutton = new \single_button($reseturl, get_string('themeresetcaches', 'admin'), 'post',
|
||||
\single_button::BUTTON_SECONDARY);
|
||||
$data->resetbutton = $resetbutton->export_for_template($output);
|
||||
$data->definedinconfig = $this->definedinconfig;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class provider implements \core_privacy\local\metadata\null_provider {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason(): string {
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,14 +46,12 @@ class task_log extends base {
|
||||
protected const FAILED = 1;
|
||||
|
||||
/**
|
||||
* Database tables that this entity uses
|
||||
* Database tables that this entity uses and their default aliases
|
||||
*
|
||||
* @return string[]
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_tables(): array {
|
||||
return [
|
||||
'task_log',
|
||||
];
|
||||
protected function get_default_table_aliases(): array {
|
||||
return ['task_log' => 'tl'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,10 +103,9 @@ class task_log extends base {
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_fields("{$tablealias}.classname, {$tablealias}.id")
|
||||
->add_field("$tablealias.classname")
|
||||
->set_is_sortable(true)
|
||||
->add_callback(static function($value, stdClass $row): string {
|
||||
$classname = $row->classname;
|
||||
->add_callback(static function(string $classname): string {
|
||||
$output = '';
|
||||
if (class_exists($classname)) {
|
||||
$task = new $classname;
|
||||
@@ -119,7 +116,6 @@ class task_log extends base {
|
||||
$output .= \html_writer::tag('div', "\\{$classname}", [
|
||||
'class' => 'small text-muted',
|
||||
]);
|
||||
$output = \html_writer::link(new \moodle_url('/admin/tasklogs.php', ['logid' => $row->id]), $output);
|
||||
return $output;
|
||||
});
|
||||
|
||||
@@ -185,7 +181,15 @@ class task_log extends base {
|
||||
->set_type(column::TYPE_FLOAT)
|
||||
->add_field("{$tablealias}.timeend - {$tablealias}.timestart", 'duration')
|
||||
->set_is_sortable(true)
|
||||
->add_callback([format::class, 'format_time'], 2);
|
||||
->add_callback(static function(float $value): string {
|
||||
$duration = round($value, 2);
|
||||
if (empty($duration)) {
|
||||
// The format_time function returns 'now' when the difference is exactly 0.
|
||||
// Note: format_time performs concatenation in exactly this fashion so we should do this for consistency.
|
||||
return '0 ' . get_string('secs', 'moodle');
|
||||
}
|
||||
return format_time($duration);
|
||||
});
|
||||
|
||||
// Hostname column.
|
||||
$columns[] = (new column(
|
||||
@@ -205,8 +209,11 @@ class task_log extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_field("{$tablealias}.pid")
|
||||
->set_is_sortable(true);
|
||||
->set_is_sortable(true)
|
||||
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
|
||||
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
|
||||
|
||||
// Database column.
|
||||
$columns[] = (new column(
|
||||
@@ -215,14 +222,17 @@ class task_log extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_fields("{$tablealias}.dbreads, {$tablealias}.dbwrites")
|
||||
->set_is_sortable(true, ["{$tablealias}.dbreads", "{$tablealias}.dbwrites"])
|
||||
->add_callback(static function($value, stdClass $row): string {
|
||||
->add_callback(static function(int $value, stdClass $row): string {
|
||||
$output = '';
|
||||
$output .= \html_writer::div(get_string('task_stats:dbreads', 'admin', $row->dbreads));
|
||||
$output .= \html_writer::div(get_string('task_stats:dbwrites', 'admin', $row->dbwrites));
|
||||
return $output;
|
||||
});
|
||||
})
|
||||
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
|
||||
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
|
||||
|
||||
// Database reads column.
|
||||
$columns[] = (new column(
|
||||
|
||||
@@ -1,108 +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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_admin\reportbuilder\local\filters;
|
||||
|
||||
use core\context\system;
|
||||
use core_course_category;
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\filters\base;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
|
||||
/**
|
||||
* Course role report filter (by role, category, course)
|
||||
*
|
||||
* The provided filter field SQL must refer/return an expression for the user ID (e.g. "{$user}.id")
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class courserole extends base {
|
||||
|
||||
/**
|
||||
* Setup form
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(MoodleQuickForm $mform): void {
|
||||
$elements = [];
|
||||
|
||||
// Role.
|
||||
$elements['role'] = $mform->createElement('select', "{$this->name}_role", get_string('rolefullname', 'core_role'),
|
||||
[0 => get_string('anyrole', 'core_filters')] + get_default_enrol_roles(system::instance()));
|
||||
|
||||
// Category.
|
||||
$elements['category'] = $mform->createElement('select', "{$this->name}_category", get_string('category'),
|
||||
[0 => get_string('anycategory', 'core_filters')] + core_course_category::make_categories_list());
|
||||
|
||||
// Course.
|
||||
$elements['course'] = $mform->createElement('text', "{$this->name}_course", get_string('shortnamecourse'));
|
||||
$mform->setType("{$this->name}_course", PARAM_RAW_TRIMMED);
|
||||
|
||||
$mform->addGroup($elements, "{$this->name}_group", $this->get_header(), '', false)
|
||||
->setHiddenLabel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return filter SQL
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter(array $values): array {
|
||||
[$fieldsql, $params] = $this->filter->get_field_sql_and_params();
|
||||
|
||||
[$contextalias, $rolealias, $coursealias] = database::generate_aliases(3);
|
||||
[$roleparam, $categoryparam, $courseparam] = database::generate_param_names(3);
|
||||
|
||||
// Role.
|
||||
$role = (int) ($values["{$this->name}_role"] ?? 0);
|
||||
if ($role > 0) {
|
||||
$selects[] = "{$rolealias}.roleid = :{$roleparam}";
|
||||
$params[$roleparam] = $role;
|
||||
}
|
||||
|
||||
// Category.
|
||||
$category = (int) ($values["{$this->name}_category"] ?? 0);
|
||||
if ($category > 0) {
|
||||
$selects[] = "{$coursealias}.category = :{$categoryparam}";
|
||||
$params[$categoryparam] = $category;
|
||||
}
|
||||
|
||||
// Course.
|
||||
$course = trim($values["{$this->name}_course"] ?? '');
|
||||
if ($course !== '') {
|
||||
$selects[] = "{$coursealias}.shortname = :{$courseparam}";
|
||||
$params[$courseparam] = $course;
|
||||
}
|
||||
|
||||
// Filter values are not set.
|
||||
if (empty($selects)) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
return ["{$fieldsql} IN (
|
||||
SELECT {$rolealias}.userid
|
||||
FROM {role_assignments} {$rolealias}
|
||||
JOIN {context} {$contextalias} ON {$contextalias}.id = {$rolealias}.contextid AND {$contextalias}.contextlevel = 50
|
||||
JOIN {course} {$coursealias} ON {$coursealias}.id = {$contextalias}.instanceid
|
||||
WHERE " . implode(' AND ', $selects) . "
|
||||
)", $params];
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class task_logs extends system_report {
|
||||
new moodle_url('/admin/tasklogs.php', ['logid' => ':id']),
|
||||
new pix_icon('e/search', ''),
|
||||
[],
|
||||
false,
|
||||
true,
|
||||
new lang_string('view'),
|
||||
)));
|
||||
|
||||
|
||||
@@ -1,417 +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 core_admin\reportbuilder\local\systemreports;
|
||||
|
||||
use core_admin\reportbuilder\local\filters\courserole;
|
||||
use core\context\system;
|
||||
use core_cohort\reportbuilder\local\entities\cohort;
|
||||
use core_cohort\reportbuilder\local\entities\cohort_member;
|
||||
use core_reportbuilder\local\entities\user;
|
||||
use core_reportbuilder\local\filters\boolean_select;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
use core_reportbuilder\local\helpers\user_profile_fields;
|
||||
use core_reportbuilder\local\report\action;
|
||||
use core_reportbuilder\local\report\filter;
|
||||
use core_reportbuilder\system_report;
|
||||
use core_role\reportbuilder\local\entities\role;
|
||||
use core_user\fields;
|
||||
use lang_string;
|
||||
use moodle_url;
|
||||
use pix_icon;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir.'/authlib.php');
|
||||
require_once($CFG->libdir.'/enrollib.php');
|
||||
require_once($CFG->dirroot.'/user/lib.php');
|
||||
|
||||
/**
|
||||
* Browse users system report class implementation
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2023 David Carrillo <davidmc@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class users extends system_report {
|
||||
|
||||
/**
|
||||
* Initialise report, we need to set the main table, load our entities and set columns/filters
|
||||
*/
|
||||
protected function initialise(): void {
|
||||
global $CFG;
|
||||
|
||||
// Our main entity, it contains all of the column definitions that we need.
|
||||
$entityuser = new user();
|
||||
$entityuseralias = $entityuser->get_table_alias('user');
|
||||
|
||||
$this->set_main_table('user', $entityuseralias);
|
||||
$this->add_entity($entityuser);
|
||||
|
||||
// Any columns required by actions should be defined here to ensure they're always available.
|
||||
$fullnamefields = array_map(fn($field) => "{$entityuseralias}.{$field}", fields::get_name_fields());
|
||||
$this->add_base_fields("{$entityuseralias}.id, {$entityuseralias}.confirmed, {$entityuseralias}.mnethostid,
|
||||
{$entityuseralias}.suspended, {$entityuseralias}.username, " . implode(', ', $fullnamefields));
|
||||
|
||||
if ($this->get_parameter('withcheckboxes', false, PARAM_BOOL)) {
|
||||
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
|
||||
$this->set_checkbox_toggleall(static function(\stdClass $row) use ($canviewfullnames): array {
|
||||
return [$row->id, fullname($row, $canviewfullnames)];
|
||||
});
|
||||
}
|
||||
|
||||
$paramguest = database::generate_param_name();
|
||||
$this->add_base_condition_sql("{$entityuseralias}.deleted <> 1 AND {$entityuseralias}.id <> :{$paramguest}",
|
||||
[$paramguest => $CFG->siteguest]);
|
||||
|
||||
$entitycohortmember = new cohort_member();
|
||||
$entitycohortmemberalias = $entitycohortmember->get_table_alias('cohort_members');
|
||||
$this->add_entity($entitycohortmember
|
||||
->add_joins($entitycohortmember->get_joins())
|
||||
->add_join("LEFT JOIN {cohort_members} {$entitycohortmemberalias}
|
||||
ON {$entityuseralias}.id = {$entitycohortmemberalias}.userid")
|
||||
);
|
||||
|
||||
$entitycohort = new cohort();
|
||||
$entitycohortalias = $entitycohort->get_table_alias('cohort');
|
||||
$this->add_entity($entitycohort
|
||||
->add_joins($entitycohort->get_joins())
|
||||
->add_joins($entitycohortmember->get_joins())
|
||||
->add_join("LEFT JOIN {cohort} {$entitycohortalias}
|
||||
ON {$entitycohortalias}.id = {$entitycohortmemberalias}.cohortid")
|
||||
);
|
||||
|
||||
// Join the role entity (Needed for the system role filter).
|
||||
$roleentity = new role();
|
||||
$role = $roleentity->get_table_alias('role');
|
||||
$this->add_entity($roleentity
|
||||
->add_join("LEFT JOIN (
|
||||
SELECT DISTINCT r0.id, ras.userid
|
||||
FROM {role} r0
|
||||
JOIN {role_assignments} ras ON ras.roleid = r0.id
|
||||
WHERE ras.contextid = ".SYSCONTEXTID."
|
||||
) {$role} ON {$role}.userid = {$entityuseralias}.id")
|
||||
);
|
||||
|
||||
// Now we can call our helper methods to add the content we want to include in the report.
|
||||
$this->add_columns();
|
||||
$this->add_filters();
|
||||
$this->add_actions();
|
||||
|
||||
// Set if report can be downloaded.
|
||||
$this->set_downloadable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates access to view this report
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_view(): bool {
|
||||
return has_any_capability(['moodle/user:update', 'moodle/user:delete'], system::instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the columns we want to display in the report
|
||||
*
|
||||
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
|
||||
* unique identifier
|
||||
*/
|
||||
public function add_columns(): void {
|
||||
$entityuser = $this->get_entity('user');
|
||||
$entityuseralias = $entityuser->get_table_alias('user');
|
||||
|
||||
$this->add_column($entityuser->get_column('fullnamewithpicturelink'));
|
||||
|
||||
// Include identity field columns.
|
||||
$identitycolumns = $entityuser->get_identity_columns($this->get_context());
|
||||
foreach ($identitycolumns as $identitycolumn) {
|
||||
$this->add_column($identitycolumn);
|
||||
}
|
||||
|
||||
// Add "Last access" column.
|
||||
$this->add_column(($entityuser->get_column('lastaccess'))
|
||||
->set_callback(static function ($value, \stdClass $row): string {
|
||||
if ($row->lastaccess) {
|
||||
return format_time(time() - $row->lastaccess);
|
||||
}
|
||||
return get_string('never');
|
||||
})
|
||||
);
|
||||
|
||||
if ($column = $this->get_column('user:fullnamewithpicturelink')) {
|
||||
$column
|
||||
->add_fields("{$entityuseralias}.suspended, {$entityuseralias}.confirmed")
|
||||
->add_callback(static function(string $fullname, \stdClass $row): string {
|
||||
if ($row->suspended) {
|
||||
$fullname .= ' ' . \html_writer::tag('span', get_string('suspended', 'moodle'),
|
||||
['class' => 'badge badge-secondary ms-1']);
|
||||
}
|
||||
if (!$row->confirmed) {
|
||||
$fullname .= ' ' . \html_writer::tag('span', get_string('confirmationpending', 'admin'),
|
||||
['class' => 'badge badge-danger ms-1']);
|
||||
}
|
||||
return $fullname;
|
||||
});
|
||||
}
|
||||
|
||||
$this->set_initial_sort_column('user:fullnamewithpicturelink', SORT_ASC);
|
||||
$this->set_default_no_results_notice(new lang_string('nousersfound', 'moodle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filters we want to display in the report
|
||||
*
|
||||
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
|
||||
* unique identifier
|
||||
*/
|
||||
protected function add_filters(): void {
|
||||
$entityuser = $this->get_entity('user');
|
||||
$entityuseralias = $entityuser->get_table_alias('user');
|
||||
|
||||
$filters = [
|
||||
'user:fullname',
|
||||
'user:firstname',
|
||||
'user:lastname',
|
||||
'user:username',
|
||||
'user:idnumber',
|
||||
'user:email',
|
||||
'user:department',
|
||||
'user:institution',
|
||||
'user:city',
|
||||
'user:country',
|
||||
'user:confirmed',
|
||||
'user:suspended',
|
||||
'user:timecreated',
|
||||
'user:lastaccess',
|
||||
'user:timemodified',
|
||||
'user:auth',
|
||||
'user:lastip',
|
||||
'cohort:idnumber',
|
||||
'role:name',
|
||||
];
|
||||
$this->add_filters_from_entities($filters);
|
||||
|
||||
// Enrolled in any course filter.
|
||||
$ue = database::generate_alias();
|
||||
[$now1, $now2] = database::generate_param_names(2);
|
||||
$now = time();
|
||||
$sql = "CASE WHEN ({$entityuseralias}.id IN (
|
||||
SELECT userid FROM {user_enrolments} {$ue}
|
||||
WHERE {$ue}.status = " . ENROL_USER_ACTIVE . "
|
||||
AND ({$ue}.timestart = 0 OR {$ue}.timestart < :{$now1})
|
||||
AND ({$ue}.timeend = 0 OR {$ue}.timeend > :{$now2})
|
||||
)) THEN 1 ELSE 0 END";
|
||||
|
||||
$this->add_filter((new filter(
|
||||
boolean_select::class,
|
||||
'enrolledinanycourse',
|
||||
new lang_string('anycourses', 'filters'),
|
||||
$this->get_entity('user')->get_entity_name(),
|
||||
))
|
||||
->set_field_sql($sql, [
|
||||
$now1 => $now,
|
||||
$now2 => $now,
|
||||
])
|
||||
);
|
||||
|
||||
// Course role filter.
|
||||
$this->add_filter((new filter(
|
||||
courserole::class,
|
||||
'courserole',
|
||||
new lang_string('courserole', 'filters'),
|
||||
$this->get_entity('user')->get_entity_name(),
|
||||
))
|
||||
->set_field_sql("{$entityuseralias}.id")
|
||||
);
|
||||
|
||||
// Add user profile fields filters.
|
||||
$userprofilefields = new user_profile_fields($entityuseralias . '.id', $entityuser->get_entity_name());
|
||||
foreach ($userprofilefields->get_filters() as $filter) {
|
||||
$this->add_filter($filter);
|
||||
}
|
||||
|
||||
// Set options for system role filter.
|
||||
if ($filter = $this->get_filter('role:name')) {
|
||||
$filter
|
||||
->set_header(new lang_string('globalrole', 'role'))
|
||||
->set_options(get_assignable_roles(system::instance()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the system report actions. An extra column will be appended to each row, containing all actions added here
|
||||
*
|
||||
* Note the use of ":id" placeholder which will be substituted according to actual values in the row
|
||||
*/
|
||||
protected function add_actions(): void {
|
||||
global $DB, $USER;
|
||||
|
||||
$contextsystem = system::instance();
|
||||
|
||||
// Action to edit users.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/user/editadvanced.php', ['id' => ':id', 'course' => get_site()->id]),
|
||||
new pix_icon('t/edit', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('edit', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && (is_siteadmin($USER) || !is_siteadmin($row));
|
||||
}));
|
||||
|
||||
// Action to suspend users (non mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['suspend' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/show', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('suspenduser', 'admin'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended && !is_mnet_remote_user($row) &&
|
||||
!($row->id == $USER->id || is_siteadmin($row));
|
||||
}));
|
||||
|
||||
// Action to unsuspend users (non mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['unsuspend' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/hide', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('unsuspenduser', 'admin'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && $row->suspended && !is_mnet_remote_user($row) &&
|
||||
!($row->id == $USER->id || is_siteadmin($row));
|
||||
}));
|
||||
|
||||
// Action to unlock users (non mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['unlock' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/unlock', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('unlockaccount', 'admin'),
|
||||
))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && !is_mnet_remote_user($row) &&
|
||||
login_is_lockedout($row);
|
||||
}));
|
||||
|
||||
// Action to suspend users (mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'deny']),
|
||||
new pix_icon('t/show', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('denyaccess', 'mnet'),
|
||||
))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
|
||||
if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
|
||||
conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
|
||||
)) {
|
||||
$accessctrl = 'allow';
|
||||
}
|
||||
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
|
||||
is_mnet_remote_user($row) && $accessctrl == 'allow';
|
||||
}));
|
||||
|
||||
// Action to unsuspend users (mnet remote users).
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'allow']),
|
||||
new pix_icon('t/hide', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('allowaccess', 'mnet'),
|
||||
))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
|
||||
if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
|
||||
conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
|
||||
)) {
|
||||
$accessctrl = 'allow';
|
||||
}
|
||||
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
|
||||
is_mnet_remote_user($row) && $accessctrl == 'deny';
|
||||
}));
|
||||
|
||||
// Action to delete users.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['delete' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/delete', ''),
|
||||
[
|
||||
'class' => 'text-danger',
|
||||
'data-modal' => 'confirmation',
|
||||
'data-modal-title-str' => json_encode(['deleteuser', 'admin']),
|
||||
'data-modal-content-str' => ':deletestr',
|
||||
'data-modal-yes-button-str' => json_encode(['delete', 'core']),
|
||||
'data-modal-destination' => ':deleteurl',
|
||||
|
||||
],
|
||||
false,
|
||||
new lang_string('delete', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
|
||||
|
||||
// Populate deletion modal attributes.
|
||||
$row->deletestr = json_encode([
|
||||
'deletecheckfull',
|
||||
'moodle',
|
||||
fullname($row, true),
|
||||
]);
|
||||
|
||||
$row->deleteurl = (new moodle_url('/admin/user.php', [
|
||||
'delete' => $row->id,
|
||||
'confirm' => md5($row->id),
|
||||
'sesskey' => sesskey(),
|
||||
]))->out(false);
|
||||
|
||||
return has_capability('moodle/user:delete', $contextsystem) &&
|
||||
!is_mnet_remote_user($row) && $row->id != $USER->id && !is_siteadmin($row);
|
||||
}));
|
||||
|
||||
$this->add_action_divider();
|
||||
|
||||
// Action to confirm users.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['confirmuser' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/check', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('confirmaccount', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
|
||||
return has_capability('moodle/user:update', $contextsystem) && !$row->confirmed;
|
||||
}));
|
||||
|
||||
// Action to resend email.
|
||||
$this->add_action((new action(
|
||||
new moodle_url('/admin/user.php', ['resendemail' => ':id', 'sesskey' => sesskey()]),
|
||||
new pix_icon('t/email', ''),
|
||||
[],
|
||||
false,
|
||||
new lang_string('resendemail', 'moodle'),
|
||||
))->add_callback(static function(\stdclass $row): bool {
|
||||
return !$row->confirmed && !is_mnet_remote_user($row);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Row class
|
||||
*
|
||||
* @param \stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
public function get_row_class(\stdClass $row): string {
|
||||
return $row->suspended ? 'text-muted' : '';
|
||||
}
|
||||
}
|
||||
@@ -1,85 +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 core_admin\table;
|
||||
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
use html_writer;
|
||||
use get_string_manager;
|
||||
|
||||
/**
|
||||
* Availability admin settings.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class availability_management_table extends plugin_management_table {
|
||||
|
||||
protected function get_table_id(): string {
|
||||
return 'availabilityconditions_administration_table';
|
||||
}
|
||||
|
||||
protected function get_plugintype(): string {
|
||||
return 'availability';
|
||||
}
|
||||
|
||||
protected function get_action_url(array $params = []): moodle_url {
|
||||
return new moodle_url('/admin/tool/availabilityconditions/', $params);
|
||||
}
|
||||
|
||||
public function guess_base_url(): void {
|
||||
$this->define_baseurl(
|
||||
new moodle_url('/admin/tool/availabilityconditions/')
|
||||
);
|
||||
}
|
||||
|
||||
protected function get_column_list(): array {
|
||||
return [
|
||||
'name' => get_string('plugin'),
|
||||
'version' => get_string('version'),
|
||||
'enabled' => get_string('enabled', 'admin'),
|
||||
'defaultdisplaymode' => get_string('defaultdisplaymode', 'tool_availabilityconditions'),
|
||||
];
|
||||
}
|
||||
|
||||
public function setup(): void {
|
||||
$this->set_attribute('id', 'availabilityconditions_administration_table');
|
||||
$this->set_attribute('class', 'admintable generaltable');
|
||||
parent::setup();
|
||||
}
|
||||
|
||||
protected function col_name(stdClass $row): string {
|
||||
return html_writer::span(
|
||||
get_string('pluginname', 'availability_' . $row->plugininfo->name)
|
||||
);
|
||||
}
|
||||
|
||||
protected function col_defaultdisplaymode(stdClass $row): string {
|
||||
global $OUTPUT, $CFG;
|
||||
$displaymode = get_config('availability_' . $row->plugininfo->name, 'defaultdisplaymode') ? 'show' : 'hide';
|
||||
$paramsdisplaymode = [
|
||||
'sesskey' => sesskey(),
|
||||
'plugin' => $row->plugininfo->name,
|
||||
'displaymode' => $displaymode,
|
||||
];
|
||||
$urldisplaymode = new moodle_url('/' . $CFG->admin . '/tool/availabilityconditions/', $paramsdisplaymode);
|
||||
|
||||
return html_writer::link($urldisplaymode, $OUTPUT->pix_icon('t/' . $displaymode,
|
||||
get_string($displaymode)), ['class' => 'display-mode-' . $row->plugininfo->name]);
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ class hook_list_table extends flexible_table {
|
||||
*/
|
||||
public function out(): void {
|
||||
// All hook consumers referenced from the db/hooks.php files.
|
||||
$hookmanager = \core\di::get(\core\hook\manager::class);
|
||||
$hookmanager = \core\hook\manager::get_instance();
|
||||
$allhooks = (array)$hookmanager->get_all_callbacks();
|
||||
|
||||
// Add any unused hooks.
|
||||
@@ -154,7 +154,11 @@ class hook_list_table extends flexible_table {
|
||||
return '';
|
||||
}
|
||||
|
||||
$deprecates = \core\hook\manager::get_replaced_callbacks($row->classname);
|
||||
$rc = new \ReflectionClass($row->classname);
|
||||
if (!$rc->implementsInterface(\core\hook\deprecated_callback_replacement::class)) {
|
||||
return '';
|
||||
}
|
||||
$deprecates = call_user_func([$row->classname, 'get_deprecated_plugin_callbacks']);
|
||||
if (count($deprecates) === 0) {
|
||||
return '';
|
||||
}
|
||||
@@ -269,7 +273,7 @@ class hook_list_table extends flexible_table {
|
||||
if (is_array($tag)) {
|
||||
return $this->get_tag(...$tag);
|
||||
}
|
||||
return $this->get_tag($tag, 'badge bg-info text-white');
|
||||
return $this->get_tag($tag, 'badge badge-info');
|
||||
}, $tags);
|
||||
|
||||
return implode("\n", $taglist);
|
||||
|
||||
@@ -303,29 +303,28 @@ abstract class plugin_management_table extends flexible_table implements dynamic
|
||||
global $OUTPUT;
|
||||
|
||||
$enabled = $row->plugininfo->is_enabled();
|
||||
if ($enabled) {
|
||||
$labelstr = get_string('disableplugin', 'core_admin', $row->plugininfo->displayname);
|
||||
} else {
|
||||
$labelstr = get_string('enableplugin', 'core_admin', $row->plugininfo->displayname);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'id' => 'admin-toggle-' . $row->plugininfo->name,
|
||||
'checked' => $enabled,
|
||||
'dataattributes' => [
|
||||
'name' => 'id',
|
||||
'value' => $row->plugininfo->name,
|
||||
'toggle-method' => $this->get_toggle_service(),
|
||||
'action' => 'togglestate',
|
||||
'plugin' => $row->plugin,
|
||||
'state' => $enabled ? 1 : 0,
|
||||
],
|
||||
'title' => $labelstr,
|
||||
'label' => $labelstr,
|
||||
'labelclasses' => 'sr-only',
|
||||
'sesskey' => sesskey(),
|
||||
'plugin' => $row->plugininfo->name,
|
||||
'action' => $enabled ? 'disable' : 'enable',
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template('core_admin/setting_configtoggle', $params);
|
||||
if ($enabled) {
|
||||
$icon = $OUTPUT->pix_icon('t/hide', get_string('disableplugin', 'core_admin', $row->plugininfo->displayname));
|
||||
} else {
|
||||
$icon = $OUTPUT->pix_icon('t/show', get_string('enableplugin', 'core_admin', $row->plugininfo->displayname));
|
||||
}
|
||||
|
||||
return html_writer::link(
|
||||
$this->get_action_url($params),
|
||||
$icon,
|
||||
[
|
||||
'data-toggle-method' => $this->get_toggle_service(),
|
||||
'data-action' => 'togglestate',
|
||||
'data-plugin' => $row->plugin,
|
||||
'data-state' => $enabled ? 1 : 0,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
protected function col_order(stdClass $row): string {
|
||||
@@ -501,15 +500,4 @@ abstract class plugin_management_table extends flexible_table implements dynamic
|
||||
protected function supports_ordering(): bool {
|
||||
return $this->plugininfoclass::plugintype_supports_ordering();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has the capability to access this table.
|
||||
*
|
||||
* Default implementation for plugin management tables is to require 'moodle/site:config' capability
|
||||
*
|
||||
* @return bool Return true if capability check passed.
|
||||
*/
|
||||
public function has_capability(): bool {
|
||||
return has_capability('moodle/site:config', $this->get_context());
|
||||
}
|
||||
}
|
||||
|
||||
+7
-23
@@ -33,7 +33,6 @@ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
list($options, $unrecognized) = cli_get_params(array(
|
||||
'courseid' => false,
|
||||
'courseshortname' => '',
|
||||
'cmid' => false,
|
||||
'destination' => '',
|
||||
'help' => false,
|
||||
), array('h' => 'help'));
|
||||
@@ -43,14 +42,13 @@ if ($unrecognized) {
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help'] || !($options['courseid'] || $options['courseshortname'] || $options['cmid'])) {
|
||||
if ($options['help'] || !($options['courseid'] || $options['courseshortname'])) {
|
||||
$help = <<<EOL
|
||||
Perform backup of the given course or course module.
|
||||
Perform backup of the given course.
|
||||
|
||||
Options:
|
||||
--courseid=INTEGER Course ID for backup.
|
||||
--courseshortname=STRING Course shortname for backup. This option is ignored if courseid is set.
|
||||
--cmid=INTEGER Course module ID for backup. This option is ignored if courseid or courseshortname is set.
|
||||
--courseshortname=STRING Course shortname for backup.
|
||||
--destination=STRING Path where to store backup file. If not set the backup
|
||||
will be stored within the course backup file area.
|
||||
-h, --help Print out this help.
|
||||
@@ -78,26 +76,16 @@ if (!empty($dir)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the course or course module exists.
|
||||
// Check that the course exists.
|
||||
if ($options['courseid']) {
|
||||
$course = $DB->get_record('course', array('id' => $options['courseid']), '*', MUST_EXIST);
|
||||
} else if ($options['courseshortname']) {
|
||||
$course = $DB->get_record('course', array('shortname' => $options['courseshortname']), '*', MUST_EXIST);
|
||||
} else if ($options['cmid']) {
|
||||
$cm = $DB->get_record('course_modules', ['id' => $options['cmid']], '*', MUST_EXIST);
|
||||
}
|
||||
|
||||
cli_heading('Performing backup...');
|
||||
if (!empty($course)) {
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
|
||||
} else if (!empty($cm)) {
|
||||
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
|
||||
} else {
|
||||
throw new \moodle_exception('invalidoption');
|
||||
}
|
||||
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
|
||||
// Set the default filename.
|
||||
$format = $bc->get_format();
|
||||
$type = $bc->get_type();
|
||||
@@ -125,11 +113,7 @@ if (!empty($dir)) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!empty($course)) {
|
||||
mtrace("Backup completed, the new file is listed in the backup area of the given course");
|
||||
} else {
|
||||
mtrace("Backup completed, the new file is listed in the backup area of the given module");
|
||||
}
|
||||
mtrace("Backup completed, the new file is listed in the backup area of the given course");
|
||||
}
|
||||
$bc->destroy();
|
||||
exit(0);
|
||||
@@ -1,132 +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/>.
|
||||
|
||||
/**
|
||||
* This script allows to import a course from CLI.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage cli
|
||||
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
|
||||
* @copyright 2023 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', 1);
|
||||
|
||||
require(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir . '/clilib.php');
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
require_once($CFG->dirroot . "/backup/util/includes/restore_includes.php");
|
||||
|
||||
list($options, $unrecognized) = cli_get_params([
|
||||
'srccourseid' => '',
|
||||
'srccmid' => '',
|
||||
'dstcourseid' => '',
|
||||
'showdebugging' => false,
|
||||
'help' => false,
|
||||
], [
|
||||
's' => 'showdebugging',
|
||||
'h' => 'help',
|
||||
]);
|
||||
|
||||
if ($unrecognized) {
|
||||
$unrecognized = implode("\n ", $unrecognized);
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help'] ||
|
||||
!(($options['srccourseid'] && $options['dstcourseid'])
|
||||
|| ($options['srccmid'] && $options['dstcourseid']))) {
|
||||
$help = <<<EOL
|
||||
Import course or course module into provided course.
|
||||
|
||||
Options:
|
||||
--srccourseid=INT Source course ID to backup.
|
||||
--srccmid=INT Source course module ID to backup.
|
||||
--dstcourseid=INT Destination course ID to restore.
|
||||
-s, --showdebugging Show developer level debugging information
|
||||
-h, --help Print out this help.
|
||||
|
||||
Example1: (Import from a course into another course)
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/import.php --srccourseid=12 --dstcourseid=13\n
|
||||
|
||||
Example2: (Import from a course module into a course)
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/import.php --srccmid=21 --dstcourseid=11\n
|
||||
EOL;
|
||||
|
||||
echo $help;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($options['showdebugging']) {
|
||||
set_debugging(DEBUG_DEVELOPER, true);
|
||||
}
|
||||
|
||||
if (!$admin = get_admin()) {
|
||||
throw new \moodle_exception('noadmins');
|
||||
}
|
||||
|
||||
if (!empty($options['srccourseid']) && !empty($options['dstcourseid'])) {
|
||||
// Import from course into course.
|
||||
if (!$srccourse = $DB->get_record('course', ['id' => $options['srccourseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
if (!$dstcourse = $DB->get_record('course', ['id' => $options['dstcourseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
|
||||
cli_heading(get_string('importfromcoursetocourse', 'backup', (object)$options));
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $srccourse->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id);
|
||||
$backupid = $bc->get_backupid();
|
||||
$bc->execute_plan();
|
||||
$bc->destroy();
|
||||
cli_heading(get_string('backupthenrestore', 'backup'));
|
||||
|
||||
$rc = new restore_controller($backupid, $dstcourse->id, backup::INTERACTIVE_NO,
|
||||
backup::MODE_SAMESITE, $admin->id, backup::TARGET_EXISTING_ADDING);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
cli_heading(get_string('restoredcourseid', 'backup', $dstcourse->id));
|
||||
} else if (!empty($options['srccmid']) && !empty($options['dstcourseid'])) {
|
||||
// Import from course module into course.
|
||||
if (!$cm = $DB->get_record('course_modules', ['id' => $options['srccmid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcoursemoduleid', 'error', '', $options['srccmid']);
|
||||
}
|
||||
if (!$course = $DB->get_record('course', ['id' => $options['dstcourseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
|
||||
cli_heading(get_string('importfromccmidtocourse', 'backup', (object)$options));
|
||||
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_NO, backup::MODE_IMPORT, $admin->id);
|
||||
$backupid = $bc->get_backupid();
|
||||
$bc->execute_plan();
|
||||
$bc->destroy();
|
||||
cli_heading(get_string('backupthenrestore', 'backup'));
|
||||
|
||||
$rc = new restore_controller($backupid, $course->id, backup::INTERACTIVE_NO,
|
||||
backup::MODE_SAMESITE, $admin->id, backup::TARGET_EXISTING_ADDING);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
cli_heading(get_string('restoredcourseid', 'backup', $course->id));
|
||||
} else {
|
||||
throw new \moodle_exception('invalidoption');
|
||||
}
|
||||
|
||||
exit(0);
|
||||
+11
-7
@@ -172,16 +172,11 @@ $CFG->debugdeveloper = true;
|
||||
$parts = explode('/', str_replace('\\', '/', dirname(__DIR__)));
|
||||
$CFG->admin = array_pop($parts);
|
||||
|
||||
// Point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
|
||||
// the problem is that we need specific version of quickforms and hacked excel files :-(.
|
||||
//point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
|
||||
//the problem is that we need specific version of quickforms and hacked excel files :-(
|
||||
ini_set('include_path', $CFG->libdir.'/pear' . PATH_SEPARATOR . ini_get('include_path'));
|
||||
|
||||
// The core_component class can be used in any scripts, it does not need anything else.
|
||||
require_once($CFG->libdir.'/classes/component.php');
|
||||
|
||||
// Register our classloader.
|
||||
\core\component::register_autoloader();
|
||||
|
||||
require_once($CFG->libdir.'/classes/text.php');
|
||||
require_once($CFG->libdir.'/classes/string_manager.php');
|
||||
require_once($CFG->libdir.'/classes/string_manager_install.php');
|
||||
@@ -195,6 +190,15 @@ require_once($CFG->libdir.'/moodlelib.php');
|
||||
require_once($CFG->libdir.'/deprecatedlib.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir.'/componentlib.class.php');
|
||||
require_once($CFG->dirroot.'/cache/lib.php');
|
||||
|
||||
// Register our classloader, in theory somebody might want to replace it to load other hacked core classes.
|
||||
// Required because the database checks below lead to session interaction which is going to lead us to requiring autoloaded classes.
|
||||
if (defined('COMPONENT_CLASSLOADER')) {
|
||||
spl_autoload_register(COMPONENT_CLASSLOADER);
|
||||
} else {
|
||||
spl_autoload_register('core_component::classloader');
|
||||
}
|
||||
|
||||
require($CFG->dirroot.'/version.php');
|
||||
$CFG->target_release = $release;
|
||||
|
||||
@@ -31,7 +31,6 @@ require_once($CFG->libdir.'/clilib.php');
|
||||
$longoptions = [
|
||||
'help' => false,
|
||||
'muc' => false,
|
||||
'courses' => false,
|
||||
'theme' => false,
|
||||
'lang' => false,
|
||||
'js' => false,
|
||||
@@ -56,8 +55,6 @@ all caches will be purged.
|
||||
Options:
|
||||
-h, --help Print out this help
|
||||
--muc Purge all MUC caches (includes lang cache)
|
||||
--courses Purge all course caches (or only those specified by a comma-separated list).
|
||||
e.g. --courses=4,67,145
|
||||
--theme Purge theme cache
|
||||
--lang Purge language string cache
|
||||
--js Purge JavaScript cache
|
||||
|
||||
@@ -32,13 +32,11 @@ require_once($CFG->dirroot . "/backup/util/includes/restore_includes.php");
|
||||
list($options, $unrecognized) = cli_get_params([
|
||||
'file' => '',
|
||||
'categoryid' => '',
|
||||
'courseid' => '',
|
||||
'showdebugging' => false,
|
||||
'help' => false,
|
||||
], [
|
||||
'f' => 'file',
|
||||
'c' => 'categoryid',
|
||||
'C' => 'courseid',
|
||||
's' => 'showdebugging',
|
||||
'h' => 'help',
|
||||
]);
|
||||
@@ -48,17 +46,15 @@ if ($unrecognized) {
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['help'] || !($options['file']) || !($options['categoryid'] || $options['courseid'])) {
|
||||
if ($options['help'] || !($options['file']) || !($options['categoryid'])) {
|
||||
$help = <<<EOL
|
||||
Restore backup into provided category or course.
|
||||
If courseid is set, course module/s will be added into the course.
|
||||
Restore backup into provided category.
|
||||
|
||||
Options:
|
||||
-f, --file=STRING Path to the backup file.
|
||||
-c, --categoryid=INT ID of the course category to restore to.
|
||||
-C, --courseid=INT ID of the course to restore to. This option is ignored if categoryid is set.
|
||||
-s, --showdebugging Show developer level debugging information
|
||||
-h, --help Print out this help.
|
||||
-f, --file=STRING Path to the backup file.
|
||||
-c, --categoryid=INT ID of the category to restore too.
|
||||
-s, --showdebugging Show developer level debugging information
|
||||
-h, --help Print out this help.
|
||||
|
||||
Example:
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/restore_backup.php --file=/path/to/backup/file.mbz --categoryid=1\n
|
||||
@@ -80,16 +76,8 @@ if (!file_exists($options['file'])) {
|
||||
throw new \moodle_exception('filenotfound');
|
||||
}
|
||||
|
||||
if ($options['categoryid']) {
|
||||
if (!$category = $DB->get_record('course_categories', ['id' => $options['categoryid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcategoryid');
|
||||
}
|
||||
} else if ($options['courseid']) {
|
||||
if (!$course = $DB->get_record('course', ['id' => $options['courseid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcourseid');
|
||||
}
|
||||
} else {
|
||||
throw new \moodle_exception('invalidoption');
|
||||
if (!$category = $DB->get_record('course_categories', ['id' => $options['categoryid']], 'id')) {
|
||||
throw new \moodle_exception('invalidcategoryid');
|
||||
}
|
||||
|
||||
$backupdir = restore_controller::get_tempdir_name(SITEID, $USER->id);
|
||||
@@ -104,29 +92,13 @@ try {
|
||||
list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoringcourse', 'backup'),
|
||||
get_string('restoringcourseshortname', 'backup'));
|
||||
|
||||
if (!empty($course)) {
|
||||
$courseid = $course->id;
|
||||
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $admin->id, backup::TARGET_EXISTING_ADDING);
|
||||
} else {
|
||||
$courseid = restore_dbops::create_new_course($fullname, $shortname, $category->id);
|
||||
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $admin->id, backup::TARGET_NEW_COURSE);
|
||||
}
|
||||
$courseid = restore_dbops::create_new_course($fullname, $shortname, $category->id);
|
||||
|
||||
$rc = new restore_controller($backupdir, $courseid, backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL, $admin->id, backup::TARGET_NEW_COURSE);
|
||||
$rc->execute_precheck();
|
||||
$rc->execute_plan();
|
||||
$rc->destroy();
|
||||
|
||||
// Rename course name if the backup is from course module and restore to category.
|
||||
if (empty($course)) {
|
||||
$course = get_course($courseid);
|
||||
list($fullname, $shortname) = restore_dbops::calculate_course_names(0, get_string('restoretonewcourse', 'backup'),
|
||||
get_string('newcourse'));
|
||||
$course->fullname = $fullname;
|
||||
$course->shortname = $shortname;
|
||||
$course->visible = 1;
|
||||
$DB->update_record('course', $course);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
cli_heading(get_string('cleaningtempdata'));
|
||||
fulldelete($path);
|
||||
|
||||
@@ -189,7 +189,11 @@ if ($disable = $options['disable']) {
|
||||
}
|
||||
|
||||
$task->set_lock($lock);
|
||||
$cronlock->release();
|
||||
if (!$task->is_blocking()) {
|
||||
$cronlock->release();
|
||||
} else {
|
||||
$task->set_cron_lock($cronlock);
|
||||
}
|
||||
|
||||
\core\cron::run_inner_scheduled_task($task);
|
||||
}
|
||||
|
||||
@@ -3923,8 +3923,6 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_xmlrpc_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.2" requires="3.11.8">
|
||||
@@ -3941,7 +3939,6 @@
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_83" message="unsupportedphpversion83" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -4115,8 +4112,6 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.3" requires="3.11.8">
|
||||
@@ -4133,7 +4128,6 @@
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_83" message="unsupportedphpversion83" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -4309,8 +4303,6 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.4" requires="4.1.2">
|
||||
@@ -4502,205 +4494,6 @@
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="4.5" requires="4.1.2">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.6.7" />
|
||||
<VENDOR name="mysql" version="8.0" />
|
||||
<VENDOR name="postgres" version="13" />
|
||||
<VENDOR name="mssql" version="14.0" />
|
||||
<VENDOR name="oracle" version="19" />
|
||||
</DATABASE>
|
||||
<PHP version="8.1.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="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="required"/>
|
||||
<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="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="sixtyfourbitsrequired" />
|
||||
</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_CHECK file="lib/upgradelib.php" function="check_mod_assignment" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_db_prefix_length" level="required">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_oracle_usage" level="optional">
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_async_backup" level="recommended">
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ require_once($CFG->libdir . '/tablelib.php');
|
||||
admin_externalpage_setup('hooksoverview');
|
||||
require_capability('moodle/site:config', \core\context\system::instance());
|
||||
|
||||
$hookmanager = \core\di::get(\core\hook\manager::class);
|
||||
$hookmanager = \core\hook\manager::get_instance();
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('hooksoverview', 'core_admin'));
|
||||
|
||||
@@ -40,6 +40,13 @@ if (!function_exists('iconv')) {
|
||||
die();
|
||||
}
|
||||
|
||||
// Make sure php5-json is available.
|
||||
if (!function_exists('json_encode') || !function_exists('json_decode')) {
|
||||
// This also shouldn't happen.
|
||||
echo 'Moodle requires the json PHP extension. Please install or enable the json extension.';
|
||||
die();
|
||||
}
|
||||
|
||||
// Make sure xml extension is available.
|
||||
if (!extension_loaded('xml')) {
|
||||
echo 'Moodle requires the xml PHP extension. Please install or enable the xml extension.';
|
||||
|
||||
+7
-4
@@ -29,6 +29,8 @@ require_once($CFG->libdir . '/adminlib.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
$fetchupdates = optional_param('fetchupdates', false, PARAM_BOOL); // Check for available plugins updates.
|
||||
$updatesonly = optional_param('updatesonly', false, PARAM_BOOL); // Show updateable plugins only.
|
||||
$contribonly = optional_param('contribonly', false, PARAM_BOOL); // Show additional plugins only.
|
||||
$uninstall = optional_param('uninstall', '', PARAM_COMPONENT); // Uninstall the plugin.
|
||||
$delete = optional_param('delete', '', PARAM_COMPONENT); // Delete the plugin folder after it is uninstalled.
|
||||
$confirmed = optional_param('confirm', false, PARAM_BOOL); // Confirm the uninstall/delete action.
|
||||
@@ -45,7 +47,8 @@ require_admin();
|
||||
$syscontext = context_system::instance();
|
||||
|
||||
// URL params we want to maintain on redirects.
|
||||
$pageurl = new moodle_url('/admin/plugins.php');
|
||||
$pageparams = array('updatesonly' => $updatesonly, 'contribonly' => $contribonly);
|
||||
$pageurl = new moodle_url('/admin/plugins.php', $pageparams);
|
||||
|
||||
$pluginman = core_plugin_manager::instance();
|
||||
|
||||
@@ -54,7 +57,7 @@ $PAGE->set_primary_active_tab('siteadminnode');
|
||||
if ($uninstall) {
|
||||
|
||||
if (!$confirmed) {
|
||||
admin_externalpage_setup('pluginsoverview');
|
||||
admin_externalpage_setup('pluginsoverview', '', $pageparams);
|
||||
} else {
|
||||
$PAGE->set_url($pageurl);
|
||||
$PAGE->set_context($syscontext);
|
||||
@@ -198,7 +201,7 @@ if ($installupdate and $installupdateversion) {
|
||||
}
|
||||
}
|
||||
|
||||
admin_externalpage_setup('pluginsoverview');
|
||||
admin_externalpage_setup('pluginsoverview', '', $pageparams);
|
||||
|
||||
/** @var core_admin_renderer $output */
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
@@ -211,4 +214,4 @@ if ($fetchupdates) {
|
||||
redirect($PAGE->url);
|
||||
}
|
||||
|
||||
echo $output->plugin_management_page($pluginman, $checker);
|
||||
echo $output->plugin_management_page($pluginman, $checker, $pageparams);
|
||||
|
||||
@@ -301,12 +301,14 @@ class helper {
|
||||
// Set Activity chooser tabs to the default value ("Starred, Recommended, All, Activities, Resources").
|
||||
static::add_item($presetid, 'activitychoosertabmode', '3');
|
||||
|
||||
// Modules: Enable database, external tool (lti), IMS content package (imscp), lesson, SCORM, wiki, workshop.
|
||||
// Modules: Enable chat, database, external tool (lti), IMS content package (imscp), lesson, SCORM, survey, wiki, workshop.
|
||||
static::add_plugin($presetid, 'mod', 'chat', true);
|
||||
static::add_plugin($presetid, 'mod', 'data', true);
|
||||
static::add_plugin($presetid, 'mod', 'lti', true);
|
||||
static::add_plugin($presetid, 'mod', 'imscp', true);
|
||||
static::add_plugin($presetid, 'mod', 'lesson', true);
|
||||
static::add_plugin($presetid, 'mod', 'scorm', true);
|
||||
static::add_plugin($presetid, 'mod', 'survey', true);
|
||||
static::add_plugin($presetid, 'mod', 'wiki', true);
|
||||
static::add_plugin($presetid, 'mod', 'workshop', true);
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class provider implements
|
||||
* @param collection $collection A list of information about this component
|
||||
* @return collection The collection object filled out with information about this component.
|
||||
*/
|
||||
public static function get_metadata(collection $collection): collection {
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
// These tables are really data about site configuration and not user data.
|
||||
|
||||
// The adminpresets includes information about which user performed a configuration change using the admin_presets
|
||||
@@ -74,7 +74,7 @@ class provider implements
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid): contextlist {
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
return new contextlist();
|
||||
}
|
||||
|
||||
|
||||
@@ -227,6 +227,7 @@ class core_adminpresets_generator extends \component_generator_base {
|
||||
public function access_protected($object, string $property) {
|
||||
$reflection = new ReflectionClass($object);
|
||||
$property = $reflection->getProperty($property);
|
||||
$property->setAccessible(true);
|
||||
return $property->getValue($object);
|
||||
}
|
||||
|
||||
@@ -243,6 +244,7 @@ class core_adminpresets_generator extends \component_generator_base {
|
||||
|
||||
// Set method accessibility.
|
||||
$method = new ReflectionMethod(manager::class, 'get_setting');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Get the proper adminpresets_setting instance.
|
||||
$settingpage = $adminroot->locate($category);
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace core_adminpresets;
|
||||
* @category test
|
||||
* @copyright 2021 Sara Arjona (sara@moodle.com)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_adminpresets\helper
|
||||
* @coversDefaultClass helper
|
||||
*/
|
||||
class helper_test extends \advanced_testcase {
|
||||
|
||||
@@ -344,7 +344,7 @@ class helper_test extends \advanced_testcase {
|
||||
],
|
||||
'plugins' => [
|
||||
'assign' => 1,
|
||||
'book' => 1,
|
||||
'chat' => 1,
|
||||
'data' => 1,
|
||||
'lesson' => 1,
|
||||
],
|
||||
|
||||
@@ -29,15 +29,6 @@ use stdClass;
|
||||
* @coversDefaultClass \core_adminpresets\manager
|
||||
*/
|
||||
class manager_test extends \advanced_testcase {
|
||||
/**
|
||||
* Include required libraries.
|
||||
*/
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
parent::setUpBeforeClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of protected get_site_settings method.
|
||||
*
|
||||
@@ -424,7 +415,7 @@ class manager_test extends \advanced_testcase {
|
||||
* @param string|null $expectedpresetname Expected preset name.
|
||||
*/
|
||||
public function test_import_preset(string $filecontents, bool $expectedpreset, bool $expectedsettings = false,
|
||||
bool $expectedplugins = false, bool $expecteddebugging = false, ?string $expectedexception = null,
|
||||
bool $expectedplugins = false, bool $expecteddebugging = false, string $expectedexception = null,
|
||||
string $expectedpresetname = 'Imported preset'): void {
|
||||
global $DB;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::get_metadata().
|
||||
* @covers ::get_metadata
|
||||
*/
|
||||
public function test_get_metadata(): void {
|
||||
public function test_get_metadata() {
|
||||
$collection = new collection('core_adminpresets');
|
||||
$newcollection = provider::get_metadata($collection);
|
||||
$itemcollection = $newcollection->get_collection();
|
||||
@@ -49,7 +49,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::get_contexts_for_userid() doesn't return any context.
|
||||
* @covers ::get_contexts_for_userid
|
||||
*/
|
||||
public function test_get_contexts_for_userid(): void {
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -67,7 +67,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::get_users_in_context() doesn't return any user.
|
||||
* @covers ::get_users_in_context
|
||||
*/
|
||||
public function test_get_users_in_context(): void {
|
||||
public function test_get_users_in_context() {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -88,7 +88,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::export_user_data().
|
||||
* @covers ::export_user_data
|
||||
*/
|
||||
public function test_export_user_data(): void {
|
||||
public function test_export_user_data() {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -121,7 +121,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
* @covers ::delete_data_for_all_users_in_context
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context(): void {
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -146,7 +146,7 @@ class privacy_provider_test extends provider_testcase {
|
||||
* Test for provider::delete_data_for_user().
|
||||
* @covers ::delete_data_for_user
|
||||
*/
|
||||
public function test_delete_data_for_user(): void {
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
@@ -125,7 +125,4 @@ if (\core\hub\registration::is_registered()) {
|
||||
echo html_writer::div(html_writer::link(new moodle_url($returnurl), get_string('skipregistration', 'hub')),
|
||||
'skipregistration mt-2');
|
||||
}
|
||||
|
||||
$PAGE->requires->js_call_amd('core_admin/expand_hash', 'init');
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
+103
-126
@@ -295,7 +295,6 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
$output .= $this->header();
|
||||
$output .= $this->output->heading(get_string('notifications', 'admin'));
|
||||
$output .= $this->upgrade_news_message();
|
||||
$output .= $this->maturity_info($maturity);
|
||||
$output .= empty($CFG->disableupdatenotifications) ? $this->available_updates($availableupdates, $availableupdatesfetch) : '';
|
||||
$output .= $this->insecure_dataroot_warning($insecuredataroot);
|
||||
@@ -618,7 +617,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @param bool $croninfrequent
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function cron_infrequent_warning(bool $croninfrequent): string {
|
||||
public function cron_infrequent_warning(bool $croninfrequent) : string {
|
||||
global $CFG;
|
||||
|
||||
if (!$croninfrequent) {
|
||||
@@ -755,22 +754,6 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
return $this->box($copyrighttext, 'copyright');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a transient notification for important upgrades messages for
|
||||
* specific releases.
|
||||
*
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
protected function upgrade_news_message() {
|
||||
return $this->notification(
|
||||
get_string('importantupdates_content', 'admin'),
|
||||
'info',
|
||||
false,
|
||||
get_string('importantupdates_title', 'admin'),
|
||||
'i/circleinfo, core'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning about installing development code if necesary.
|
||||
* @param int $maturity
|
||||
@@ -860,14 +843,14 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$registerbutton = $this->single_button(new moodle_url('/admin/registration/index.php'),
|
||||
get_string('register', 'admin'));
|
||||
$str = 'registrationwarning';
|
||||
$type = 'error alert alert-danger';
|
||||
} else {
|
||||
$registerbutton = '';
|
||||
$str = 'registrationwarningcontactadmin';
|
||||
$type = 'info';
|
||||
}
|
||||
|
||||
return $this->warning( get_string($str, 'admin') . ' ' . $registerbutton , $type);
|
||||
return $this->warning( get_string($str, 'admin')
|
||||
. ' ' . $this->help_icon('registration', 'admin') . $registerbutton ,
|
||||
'error alert alert-danger');
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -891,8 +874,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
protected function mobile_configuration_warning($mobileconfigured) {
|
||||
$output = '';
|
||||
if (!$mobileconfigured) {
|
||||
$settingslink = new moodle_url('/admin/search.php', ['query' => 'enablemobilewebservice']);
|
||||
$configurebutton = $this->single_button($settingslink, get_string('enablemobilewebservice', 'admin'), 'get');
|
||||
$settingslink = new moodle_url('/admin/settings.php', ['section' => 'mobilesettings']);
|
||||
$configurebutton = $this->single_button($settingslink, get_string('enablemobilewebservice', 'admin'));
|
||||
$output .= $this->warning(get_string('mobilenotconfiguredwarning', 'admin') . ' ' . $configurebutton);
|
||||
}
|
||||
|
||||
@@ -1021,7 +1004,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
function upgrade_reload($url) {
|
||||
return html_writer::empty_tag('br') .
|
||||
html_writer::tag('div',
|
||||
html_writer::link($url, $this->pix_icon('i/reload', '', '', ['class' => 'icon']) .
|
||||
html_writer::link($url, $this->pix_icon('i/reload', '', '', array('class' => 'icon icon-pre')) .
|
||||
get_string('reload'), array('title' => get_string('reload'))),
|
||||
array('class' => 'continuebutton')) . html_writer::empty_tag('br');
|
||||
}
|
||||
@@ -1128,11 +1111,10 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
if ($isstandard = $plugin->is_standard()) {
|
||||
$row->attributes['class'] .= ' standard';
|
||||
$sourcelabel = html_writer::span(get_string('sourcestd', 'core_plugin'),
|
||||
'sourcetext badge bg-secondary text-dark');
|
||||
$sourcelabel = html_writer::span(get_string('sourcestd', 'core_plugin'), 'sourcetext badge badge-secondary');
|
||||
} else {
|
||||
$row->attributes['class'] .= ' extension';
|
||||
$sourcelabel = html_writer::span(get_string('sourceext', 'core_plugin'), 'sourcetext badge bg-info text-white');
|
||||
$sourcelabel = html_writer::span(get_string('sourceext', 'core_plugin'), 'sourcetext badge badge-info');
|
||||
}
|
||||
|
||||
$coredependency = $plugin->is_core_dependency_satisfied($version);
|
||||
@@ -1146,19 +1128,19 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$statusclass = 'statustext badge ';
|
||||
switch ($statuscode) {
|
||||
case core_plugin_manager::PLUGIN_STATUS_NEW:
|
||||
$statusclass .= $dependenciesok ? 'bg-success text-white' : 'bg-warning text-dark';
|
||||
$statusclass .= $dependenciesok ? 'badge-success' : 'badge-warning';
|
||||
break;
|
||||
case core_plugin_manager::PLUGIN_STATUS_UPGRADE:
|
||||
$statusclass .= $dependenciesok ? 'bg-info text-white' : 'bg-warning text-dark';
|
||||
$statusclass .= $dependenciesok ? 'badge-info' : 'badge-warning';
|
||||
break;
|
||||
case core_plugin_manager::PLUGIN_STATUS_MISSING:
|
||||
case core_plugin_manager::PLUGIN_STATUS_DOWNGRADE:
|
||||
case core_plugin_manager::PLUGIN_STATUS_DELETE:
|
||||
$statusclass .= 'bg-danger text-white';
|
||||
$statusclass .= 'badge-danger';
|
||||
break;
|
||||
case core_plugin_manager::PLUGIN_STATUS_NODB:
|
||||
case core_plugin_manager::PLUGIN_STATUS_UPTODATE:
|
||||
$statusclass .= $dependenciesok ? 'bg-light text-dark' : 'bg-warning text-dark';
|
||||
$statusclass .= $dependenciesok ? 'badge-light' : 'badge-warning';
|
||||
break;
|
||||
}
|
||||
$status = html_writer::span(get_string('status_' . $statuscode, 'core_plugin'), $statusclass);
|
||||
@@ -1258,7 +1240,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('installupdatex' => 1)),
|
||||
get_string('updateavailableinstallall', 'core_admin', count($installableupdates)),
|
||||
'post',
|
||||
array('class' => 'singlebutton updateavailableinstallall me-1')
|
||||
array('class' => 'singlebutton updateavailableinstallall mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1267,7 +1249,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('abortinstallx' => 1, 'confirmplugincheck' => 0)),
|
||||
get_string('cancelinstallall', 'core_plugin', count($installabortable)),
|
||||
'post',
|
||||
array('class' => 'singlebutton cancelinstallall me-1')
|
||||
array('class' => 'singlebutton cancelinstallall mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1276,17 +1258,17 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('abortupgradex' => 1)),
|
||||
get_string('cancelupgradeall', 'core_plugin', count($upgradeabortable)),
|
||||
'post',
|
||||
array('class' => 'singlebutton cancelupgradeall me-1')
|
||||
array('class' => 'singlebutton cancelupgradeall mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
$out .= html_writer::div(html_writer::link(new moodle_url($this->page->url, array('showallplugins' => 0)),
|
||||
get_string('plugincheckattention', 'core_plugin')).' '.html_writer::span($sumattention, 'badge bg-light text-dark'),
|
||||
'btn btn-link me-1');
|
||||
get_string('plugincheckattention', 'core_plugin')).' '.html_writer::span($sumattention, 'badge badge-light'),
|
||||
'btn btn-link mr-1');
|
||||
|
||||
$out .= html_writer::div(html_writer::link(new moodle_url($this->page->url, array('showallplugins' => 1)),
|
||||
get_string('plugincheckall', 'core_plugin')).' '.html_writer::span($sumtotal, 'badge bg-light text-dark'),
|
||||
'btn btn-link me-1');
|
||||
get_string('plugincheckall', 'core_plugin')).' '.html_writer::span($sumtotal, 'badge badge-light'),
|
||||
'btn btn-link mr-1');
|
||||
|
||||
$out .= $this->output->container_end(); // End of .actions container.
|
||||
$out .= $this->output->container_end(); // End of #plugins-check-info container.
|
||||
@@ -1305,7 +1287,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @param null|moodle_url $cancel URL for the cancel link, defaults to the current page
|
||||
* @return string HTML
|
||||
*/
|
||||
public function plugins_management_confirm_buttons(?moodle_url $continue=null, ?moodle_url $cancel=null) {
|
||||
public function plugins_management_confirm_buttons(moodle_url $continue=null, moodle_url $cancel=null) {
|
||||
|
||||
$out = html_writer::start_div('plugins-management-confirm-buttons');
|
||||
|
||||
@@ -1393,13 +1375,13 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
new moodle_url($this->page->url, array('installdepx' => 1)),
|
||||
get_string('dependencyinstallmissing', 'core_plugin', count($installable)),
|
||||
'post',
|
||||
array('class' => 'singlebutton dependencyinstallmissing d-inline-block me-1')
|
||||
array('class' => 'singlebutton dependencyinstallmissing d-inline-block mr-1')
|
||||
);
|
||||
}
|
||||
|
||||
$out .= html_writer::div(html_writer::link(new moodle_url('/admin/tool/installaddon/'),
|
||||
get_string('dependencyuploadmissing', 'core_plugin'), array('class' => 'btn btn-link')),
|
||||
'dependencyuploadmissing d-inline-block me-1');
|
||||
'dependencyuploadmissing d-inline-block mr-1');
|
||||
|
||||
$out .= $this->output->container_end(); // End of .plugins-check-dependencies-actions container.
|
||||
|
||||
@@ -1438,9 +1420,9 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$supportedmoodles = array();
|
||||
foreach ($plugin->version->supportedmoodles as $moodle) {
|
||||
if ($CFG->branch == str_replace('.', '', $moodle->release)) {
|
||||
$supportedmoodles[] = html_writer::span($moodle->release, 'badge bg-success text-white');
|
||||
$supportedmoodles[] = html_writer::span($moodle->release, 'badge badge-success');
|
||||
} else {
|
||||
$supportedmoodles[] = html_writer::span($moodle->release, 'badge bg-light text-dark');
|
||||
$supportedmoodles[] = html_writer::span($moodle->release, 'badge badge-light');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1465,29 +1447,29 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$info .= html_writer::div(
|
||||
html_writer::link('https://moodle.org/plugins/view.php?plugin='.$plugin->component,
|
||||
get_string('misdepinfoplugin', 'core_plugin')),
|
||||
'misdepinfoplugin d-inline-block me-3 mb-1'
|
||||
'misdepinfoplugin d-inline-block mr-3 mb-1'
|
||||
);
|
||||
|
||||
$info .= html_writer::div(
|
||||
html_writer::link('https://moodle.org/plugins/pluginversion.php?id='.$plugin->version->id,
|
||||
get_string('misdepinfoversion', 'core_plugin')),
|
||||
'misdepinfoversion d-inline-block me-3 mb-1'
|
||||
'misdepinfoversion d-inline-block mr-3 mb-1'
|
||||
);
|
||||
|
||||
$info .= html_writer::div(html_writer::link($plugin->version->downloadurl, get_string('download')),
|
||||
'misdepdownload d-inline-block me-3 mb-1');
|
||||
'misdepdownload d-inline-block mr-3 mb-1');
|
||||
|
||||
if ($pluginman->is_remote_plugin_installable($plugin->component, $plugin->version->version, $reason)) {
|
||||
$info .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('installdep' => $plugin->component)),
|
||||
get_string('dependencyinstall', 'core_plugin'),
|
||||
'post',
|
||||
array('class' => 'singlebutton dependencyinstall me-3 mb-1')
|
||||
array('class' => 'singlebutton dependencyinstall mr-3 mb-1')
|
||||
);
|
||||
} else {
|
||||
$reasonhelp = $this->info_remote_plugin_not_installable($reason);
|
||||
if ($reasonhelp) {
|
||||
$info .= html_writer::div($reasonhelp, 'reasonhelp dependencyinstall d-inline-block me-3 mb-1');
|
||||
$info .= html_writer::div($reasonhelp, 'reasonhelp dependencyinstall d-inline-block mr-3 mb-1');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1546,7 +1528,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$label = '';
|
||||
} else {
|
||||
$class = 'requires-failed';
|
||||
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge bg-danger text-white');
|
||||
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-danger');
|
||||
}
|
||||
|
||||
if ($branch != null && !$plugin->is_core_compatible_satisfied($branch)) {
|
||||
@@ -1575,10 +1557,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
} else if ($reqinfo->status == $pluginman::REQUIREMENT_STATUS_MISSING) {
|
||||
if ($reqinfo->availability == $pluginman::REQUIREMENT_AVAILABLE) {
|
||||
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'),
|
||||
'badge bg-warning text-dark');
|
||||
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'),
|
||||
'badge bg-warning text-dark');
|
||||
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'), 'badge badge-warning');
|
||||
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'), 'badge badge-warning');
|
||||
$class = 'requires-failed requires-missing requires-available';
|
||||
$actions[] = html_writer::link(
|
||||
new moodle_url('https://moodle.org/plugins/view.php', array('plugin' => $reqname)),
|
||||
@@ -1586,25 +1566,24 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
);
|
||||
|
||||
} else {
|
||||
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'), 'badge bg-danger text-white');
|
||||
$label = html_writer::span(get_string('dependencymissing', 'core_plugin'), 'badge badge-danger');
|
||||
$label .= ' '.html_writer::span(get_string('dependencyunavailable', 'core_plugin'),
|
||||
'badge bg-danger text-white');
|
||||
'badge badge-danger');
|
||||
$class = 'requires-failed requires-missing requires-unavailable';
|
||||
}
|
||||
$displayuploadlink = true;
|
||||
|
||||
} else if ($reqinfo->status == $pluginman::REQUIREMENT_STATUS_OUTDATED) {
|
||||
if ($reqinfo->availability == $pluginman::REQUIREMENT_AVAILABLE) {
|
||||
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge bg-warning text-dark');
|
||||
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'),
|
||||
'badge bg-warning text-dark');
|
||||
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-warning');
|
||||
$label .= ' '.html_writer::span(get_string('dependencyavailable', 'core_plugin'), 'badge badge-warning');
|
||||
$class = 'requires-failed requires-outdated requires-available';
|
||||
$displayupdateslink = true;
|
||||
|
||||
} else {
|
||||
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge bg-danger text-white');
|
||||
$label = html_writer::span(get_string('dependencyfails', 'core_plugin'), 'badge badge-danger');
|
||||
$label .= ' '.html_writer::span(get_string('dependencyunavailable', 'core_plugin'),
|
||||
'badge bg-danger text-white');
|
||||
'badge badge-danger');
|
||||
$class = 'requires-failed requires-outdated requires-unavailable';
|
||||
}
|
||||
$displayuploadlink = true;
|
||||
@@ -1673,9 +1652,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
public function plugins_overview_panel(core_plugin_manager $pluginman, array $options = array()) {
|
||||
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
$this->page->requires->js_call_amd('core_admin/plugins_overview', 'init');
|
||||
|
||||
$numtotal = $numextension = $numupdatable = $numinstallable = $nummissing = $numnew = 0;
|
||||
$numtotal = $numextension = $numupdatable = $numinstallable = 0;
|
||||
|
||||
foreach ($plugininfo as $type => $plugins) {
|
||||
foreach ($plugins as $name => $plugin) {
|
||||
@@ -1689,12 +1667,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
}
|
||||
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
|
||||
$nummissing++;
|
||||
continue;
|
||||
}
|
||||
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_NEW) {
|
||||
$numnew++;
|
||||
}
|
||||
$numtotal++;
|
||||
if (!$plugin->is_standard()) {
|
||||
$numextension++;
|
||||
@@ -1703,50 +1677,36 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$infoall = html_writer::link(
|
||||
$this->page->url,
|
||||
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 0)),
|
||||
get_string('overviewall', 'core_plugin'),
|
||||
['title' => get_string('filterall', 'core_plugin'), 'data-filterby' => 'all', 'class' => 'active']
|
||||
array('title' => get_string('filterall', 'core_plugin'))
|
||||
).' '.html_writer::span($numtotal, 'badge number number-all');
|
||||
|
||||
$infoext = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'additional'),
|
||||
new moodle_url($this->page->url, array('contribonly' => 1, 'updatesonly' => 0)),
|
||||
get_string('overviewext', 'core_plugin'),
|
||||
['title' => get_string('filtercontribonly', 'core_plugin'), 'data-filterby' => 'additional']
|
||||
array('title' => get_string('filtercontribonly', 'core_plugin'))
|
||||
).' '.html_writer::span($numextension, 'badge number number-additional');
|
||||
|
||||
if ($numupdatable) {
|
||||
$infoupdatable = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'updatable'),
|
||||
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 1)),
|
||||
get_string('overviewupdatable', 'core_plugin'),
|
||||
['title' => get_string('filterupdatesonly', 'core_plugin'), 'data-filterby' => 'updatable']
|
||||
).' '.html_writer::span($numupdatable, 'badge bg-info text-white number number-updatable');
|
||||
array('title' => get_string('filterupdatesonly', 'core_plugin'))
|
||||
).' '.html_writer::span($numupdatable, 'badge badge-info number number-updatable');
|
||||
} else {
|
||||
// No updates, or the notifications disabled.
|
||||
$infoupdatable = '';
|
||||
}
|
||||
|
||||
if ($numnew) {
|
||||
$infonew = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'newplugin'),
|
||||
get_string('status_new', 'plugin'),
|
||||
['title' => get_string('filternewpluginsonly', 'core_plugin'), 'data-filterby' => 'newplugin']
|
||||
).' '.html_writer::span($numnew, 'badge bg-success text-white number number-newplugin');
|
||||
} else {
|
||||
$infonew = '';
|
||||
}
|
||||
$out = html_writer::start_div('', array('id' => 'plugins-overview-panel'));
|
||||
|
||||
if ($nummissing) {
|
||||
$infomissing = html_writer::link(
|
||||
new moodle_url($this->page->url, [], 'missing'),
|
||||
get_string('status_missing', 'plugin'),
|
||||
['title' => get_string('filtermissingonly', 'core_plugin'), 'data-filterby' => 'missing']
|
||||
).' '.html_writer::span($nummissing, 'badge bg-danger text-white number number-missing');
|
||||
} else {
|
||||
$infomissing = '';
|
||||
if (!empty($options['updatesonly'])) {
|
||||
$out .= $this->output->heading(get_string('overviewupdatable', 'core_plugin'), 3);
|
||||
} else if (!empty($options['contribonly'])) {
|
||||
$out .= $this->output->heading(get_string('overviewext', 'core_plugin'), 3);
|
||||
}
|
||||
|
||||
$out = html_writer::start_div('', ['id' => 'plugins-overview-panel']);
|
||||
|
||||
if ($numinstallable) {
|
||||
$out .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('installupdatex' => 1)),
|
||||
@@ -1757,10 +1717,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$out .= html_writer::div($infoall, 'info info-all').
|
||||
html_writer::div($infoext, 'info info-additional').
|
||||
html_writer::div($infoupdatable, 'info info-updatable').
|
||||
html_writer::div($infonew, 'info info-newplugin').
|
||||
html_writer::div($infomissing, 'info info-missing');
|
||||
html_writer::div($infoext, 'info info-ext').
|
||||
html_writer::div($infoupdatable, 'info info-updatable');
|
||||
|
||||
$out .= html_writer::end_div(); // End of #plugins-overview-panel block.
|
||||
|
||||
@@ -1780,6 +1738,38 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
|
||||
// Filter the list of plugins according the options.
|
||||
if (!empty($options['updatesonly'])) {
|
||||
$updateable = array();
|
||||
foreach ($plugininfo as $plugintype => $pluginnames) {
|
||||
foreach ($pluginnames as $pluginname => $pluginfo) {
|
||||
$pluginavailableupdates = $pluginfo->available_updates();
|
||||
if (!empty($pluginavailableupdates)) {
|
||||
foreach ($pluginavailableupdates as $pluginavailableupdate) {
|
||||
$updateable[$plugintype][$pluginname] = $pluginfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$plugininfo = $updateable;
|
||||
}
|
||||
|
||||
if (!empty($options['contribonly'])) {
|
||||
$contribs = array();
|
||||
foreach ($plugininfo as $plugintype => $pluginnames) {
|
||||
foreach ($pluginnames as $pluginname => $pluginfo) {
|
||||
if (!$pluginfo->is_standard()) {
|
||||
$contribs[$plugintype][$pluginname] = $pluginfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
$plugininfo = $contribs;
|
||||
}
|
||||
|
||||
if (empty($plugininfo)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$table = new html_table();
|
||||
$table->id = 'plugins-control-panel';
|
||||
$table->head = array(
|
||||
@@ -1877,16 +1867,14 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$row->attributes['class'] .= ' standard';
|
||||
$source = '';
|
||||
} else {
|
||||
$row->attributes['class'] .= ' additional';
|
||||
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge mr-1 bg-info text-white');
|
||||
$row->attributes['class'] .= ' extension';
|
||||
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge badge-info');
|
||||
}
|
||||
|
||||
if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
|
||||
$row->attributes['class'] .= ' missing';
|
||||
$msg = html_writer::div(get_string('status_missing', 'core_plugin'), 'statusmsg badge bg-danger text-white');
|
||||
$msg = html_writer::div(get_string('status_missing', 'core_plugin'), 'statusmsg badge badge-danger');
|
||||
} else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
|
||||
$row->attributes['class'] .= ' newplugin';
|
||||
$msg = html_writer::div(get_string('status_new', 'core_plugin'), 'statusmsg badge bg-success text-white');
|
||||
$msg = html_writer::div(get_string('status_new', 'core_plugin'), 'statusmsg badge badge-success');
|
||||
} else {
|
||||
$msg = '';
|
||||
}
|
||||
@@ -1901,7 +1889,6 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
$updateinfo = '';
|
||||
if (is_array($plugin->available_updates())) {
|
||||
$row->attributes['class'] .= ' updatable';
|
||||
foreach ($plugin->available_updates() as $availableupdate) {
|
||||
$updateinfo .= $this->plugin_available_update_info($pluginman, $availableupdate);
|
||||
}
|
||||
@@ -2064,10 +2051,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
// We are checking installed & enabled things
|
||||
if ($environment_result->getLevel() == 'required') {
|
||||
$stringtouse = 'environmentrequirecustomcheck';
|
||||
} else if ($environment_result->getLevel() == 'optional') {
|
||||
$stringtouse = 'environmentrecommendcustomcheck';
|
||||
} else {
|
||||
$stringtouse = 'environmentshouldfixcustomcheck';
|
||||
$stringtouse = 'environmentrecommendcustomcheck';
|
||||
}
|
||||
|
||||
} else if ($environment_result->getPart() == 'php_setting') {
|
||||
@@ -2098,8 +2083,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
if ($status) { //Handle ok result (ok)
|
||||
$status = get_string('statusok');
|
||||
} else {
|
||||
// Handle check result (warning).
|
||||
if (in_array($environment_result->getLevel(), ['optional', 'recommended'])) {
|
||||
if ($environment_result->getLevel() == 'optional') {//Handle check result (warning)
|
||||
$status = get_string('check');
|
||||
$warningline = true;
|
||||
} else { //Handle error result (error)
|
||||
@@ -2129,13 +2113,13 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
// Format error or warning line
|
||||
if ($errorline) {
|
||||
$messagetype = 'error';
|
||||
$statusclass = 'bg-danger text-white';
|
||||
$statusclass = 'badge-danger';
|
||||
} else if ($warningline) {
|
||||
$messagetype = 'warn';
|
||||
$statusclass = 'bg-warning text-dark';
|
||||
$statusclass = 'badge-warning';
|
||||
} else {
|
||||
$messagetype = 'ok';
|
||||
$statusclass = 'bg-success text-white';
|
||||
$statusclass = 'badge-success';
|
||||
}
|
||||
$status = html_writer::span($status, 'badge ' . $statusclass);
|
||||
// Here we'll store all the feedback found
|
||||
@@ -2215,10 +2199,14 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
$a = new stdClass();
|
||||
$a->moreinformation = '#id_sitestats'; // More information anchor.
|
||||
$a->moodleapp = HUB_MOODLEORGHUBURL . '/solutions/moodle-app/';
|
||||
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub', $a), FORMAT_MARKDOWN);
|
||||
|
||||
$out = format_text(get_string('registerwithmoodleorginfo', 'core_hub'), FORMAT_MARKDOWN);
|
||||
|
||||
$out .= html_writer::link(
|
||||
new moodle_url('/admin/settings.php', ['section' => 'moodleservices']),
|
||||
$this->output->pix_icon('i/info', '').' '.get_string('registerwithmoodleorginfoapp', 'core_hub'),
|
||||
['class' => 'btn btn-link', 'role' => 'opener', 'target' => '_href']
|
||||
);
|
||||
|
||||
$out .= html_writer::link(
|
||||
HUB_MOODLEORGHUBURL,
|
||||
@@ -2265,15 +2253,4 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
return $this->warning($xmlrpcwarning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the theme selector list.
|
||||
*
|
||||
* @param core_admin\output\theme_selector $themeselector
|
||||
* @return string HTML
|
||||
*/
|
||||
public function theme_selector_list(core_admin\output\theme_selector $themeselector): string {
|
||||
$renderable = $themeselector->export_for_template($this);
|
||||
return $this->render_from_template('core_admin/themeselector/theme_selector', $renderable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# core_role (subsystem) Upgrade notes
|
||||
|
||||
## 4.5
|
||||
|
||||
### Added
|
||||
|
||||
- All session management has been moved to the `\core\session\manager` class.
|
||||
This removes the dependancy to use the `sessions` table.
|
||||
|
||||
Session management plugins (like Redis) should now inherit
|
||||
the base `\core\session\handler` class, which implements
|
||||
`SessionHandlerInterface`, and override methods as required.
|
||||
|
||||
The following methods in `\core\session\manager` have been deprecated:
|
||||
| Old method name | New method name |
|
||||
| --- | --- |
|
||||
| `kill_all_sessions` | `destroy_all` |
|
||||
| `kill_session` | `destroy` |
|
||||
| `kill_sessions_for_auth_plugin` | `destroy_by_auth_plugin` |
|
||||
| `kill_user_sessions` | `destroy_user_sessions` |
|
||||
|
||||
For more information see [MDL-66151](https://tracker.moodle.org/browse/MDL-66151)
|
||||
@@ -38,19 +38,6 @@ if (!is_siteadmin()) {
|
||||
}
|
||||
|
||||
$admisselector = new core_role_admins_existing_selector();
|
||||
|
||||
if (array_key_exists('siteadmins', $CFG->config_php_settings)) {
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('manageadmins', 'core_role'), 3);
|
||||
echo $OUTPUT->notification(get_string('siteadministratorsconfigphp', 'core_role'), \core\output\notification::NOTIFY_INFO);
|
||||
echo $OUTPUT->box_start();
|
||||
echo $OUTPUT->paragraph(get_string('existingadmins', 'core_role'));
|
||||
$admisselector->display();
|
||||
echo $OUTPUT->box_end();
|
||||
echo $OUTPUT->footer();
|
||||
die();
|
||||
}
|
||||
|
||||
$potentialadmisselector = new core_role_admins_potential_selector();
|
||||
|
||||
if ($addusersaction) {
|
||||
|
||||
@@ -99,7 +99,7 @@ abstract class core_role_allow_role_page {
|
||||
* @param int $fromroleid
|
||||
* @param int $targetroleid
|
||||
*/
|
||||
abstract protected function set_allow($fromroleid, $targetroleid);
|
||||
protected abstract function set_allow($fromroleid, $targetroleid);
|
||||
|
||||
/**
|
||||
* Load the current allows from the database.
|
||||
@@ -183,11 +183,11 @@ abstract class core_role_allow_role_page {
|
||||
* Snippet of text displayed above the table, telling the admin what to do.
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_intro_text();
|
||||
public abstract function get_intro_text();
|
||||
|
||||
/**
|
||||
* Returns the allow class respective event class name.
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_eventclass();
|
||||
protected abstract function get_eventclass();
|
||||
}
|
||||
|
||||
@@ -142,12 +142,12 @@ abstract class core_role_capability_table_base {
|
||||
/**
|
||||
* For subclasses to override, output header cells, after the initial capability one.
|
||||
*/
|
||||
abstract protected function add_header_cells();
|
||||
protected abstract function add_header_cells();
|
||||
|
||||
/**
|
||||
* For subclasses to override, return the number of cells that add_header_cells/add_row_cells output.
|
||||
*/
|
||||
abstract protected function num_extra_columns();
|
||||
protected abstract function num_extra_columns();
|
||||
|
||||
/**
|
||||
* For subclasses to override. Allows certain capabilties
|
||||
@@ -191,5 +191,5 @@ abstract class core_role_capability_table_base {
|
||||
* @param stdClass $capability the capability this row relates to.
|
||||
* @return string html of row cells
|
||||
*/
|
||||
abstract protected function add_row_cells($capability);
|
||||
protected abstract function add_row_cells($capability);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ abstract class core_role_capability_table_with_risks extends core_role_capabilit
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function load_parent_permissions();
|
||||
protected abstract function load_parent_permissions();
|
||||
|
||||
/**
|
||||
* Update $this->permissions based on submitted data, while making a list of
|
||||
@@ -156,7 +156,7 @@ abstract class core_role_capability_table_with_risks extends core_role_capabilit
|
||||
return $rowclasses;
|
||||
}
|
||||
|
||||
abstract protected function add_permission_cells($capability);
|
||||
protected abstract function add_permission_cells($capability);
|
||||
|
||||
protected function add_row_cells($capability) {
|
||||
$cells = $this->add_permission_cells($capability);
|
||||
|
||||
@@ -645,7 +645,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
|
||||
protected function print_field($name, $caption, $field, $helpicon = null) {
|
||||
global $OUTPUT;
|
||||
// Attempt to generate HTML like formslib.
|
||||
echo '<div class="fitem row mb-3">';
|
||||
echo '<div class="fitem row form-group">';
|
||||
echo '<div class="fitemtitle col-md-3">';
|
||||
if ($name) {
|
||||
echo '<label for="' . $name . '">';
|
||||
@@ -655,7 +655,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
|
||||
echo "</label>\n";
|
||||
}
|
||||
if ($helpicon) {
|
||||
echo '<span class="float-sm-end text-nowrap">'.$helpicon.'</span>';
|
||||
echo '<span class="float-sm-right text-nowrap">'.$helpicon.'</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
if (isset($this->errors[$name])) {
|
||||
@@ -663,7 +663,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
|
||||
} else {
|
||||
$extraclass = '';
|
||||
}
|
||||
echo '<div class="felement col-md-9 d-flex flex-wrap align-items-center' . $extraclass . '">';
|
||||
echo '<div class="felement col-md-9 form-inline' . $extraclass . '">';
|
||||
echo $field;
|
||||
if (isset($this->errors[$name])) {
|
||||
echo $OUTPUT->error_text($this->errors[$name]);
|
||||
|
||||
@@ -53,7 +53,7 @@ class provider implements
|
||||
* @param collection $collection An object for storing metadata.
|
||||
* @return collection The metadata.
|
||||
*/
|
||||
public static function get_metadata(collection $collection): collection {
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$rolecapabilities = [
|
||||
'roleid' => 'privacy:metadata:role_capabilities:roleid',
|
||||
'capability' => 'privacy:metadata:role_capabilities:capability',
|
||||
@@ -100,7 +100,7 @@ class provider implements
|
||||
* @param int $userid The user ID.
|
||||
* @return contextlist The list of context IDs.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid): contextlist {
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
global $DB;
|
||||
|
||||
$contextlist = new contextlist();
|
||||
|
||||
@@ -1,124 +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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_role\reportbuilder\datasource;
|
||||
|
||||
use core\reportbuilder\local\entities\context;
|
||||
use core_reportbuilder\datasource;
|
||||
use core_reportbuilder\local\entities\user;
|
||||
use core_role\reportbuilder\local\entities\{role, role_assignment};
|
||||
|
||||
/**
|
||||
* Roles datasource
|
||||
*
|
||||
* @package core_role
|
||||
* @copyright 2024 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class roles extends datasource {
|
||||
|
||||
/**
|
||||
* Return user friendly name of the report source
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name(): string {
|
||||
return get_string('roles', 'core_role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise report
|
||||
*/
|
||||
protected function initialise(): void {
|
||||
$contextentity = new context();
|
||||
$contextalias = $contextentity->get_table_alias('context');
|
||||
|
||||
$roleentity = new role();
|
||||
$rolealias = $roleentity->get_table_alias('role');
|
||||
|
||||
// Role table.
|
||||
$this->add_entity($roleentity->set_table_alias('context', $contextalias));
|
||||
$this->set_main_table('role', $rolealias);
|
||||
|
||||
// Join role assignments.
|
||||
$roleassignmententity = new role_assignment();
|
||||
$roleassignmentalias = $roleassignmententity->get_table_alias('role_assignments');
|
||||
$this->add_entity($roleassignmententity);
|
||||
$this->add_join("JOIN {role_assignments} {$roleassignmentalias} ON {$roleassignmentalias}.roleid = {$rolealias}.id");
|
||||
|
||||
// Join context.
|
||||
$this->add_entity($contextentity);
|
||||
$this->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$roleassignmentalias}.contextid");
|
||||
|
||||
// Join user.
|
||||
$userentity = new user();
|
||||
$useralias = $userentity->get_table_alias('user');
|
||||
$this->add_entity($userentity
|
||||
->add_join("LEFT JOIN {user} {$useralias} ON {$useralias}.id = {$roleassignmentalias}.userid"));
|
||||
|
||||
$this->add_all_from_entities();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns that will be added to the report upon creation
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_default_columns(): array {
|
||||
return [
|
||||
'context:link',
|
||||
'role:originalname',
|
||||
'user:fullnamewithlink',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column sorting that will be added to the report upon creation
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function get_default_column_sorting(): array {
|
||||
return [
|
||||
'context:link' => SORT_ASC,
|
||||
'role:originalname' => SORT_ASC,
|
||||
'user:fullnamewithlink' => SORT_ASC,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filters that will be added to the report upon creation
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_default_filters(): array {
|
||||
return [
|
||||
'context:level',
|
||||
'role:name',
|
||||
'user:fullname',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the conditions that will be added to the report upon creation
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_default_conditions(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,15 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_role\reportbuilder\local\entities;
|
||||
|
||||
use core\{context, context_helper};
|
||||
use core\lang_string;
|
||||
use context;
|
||||
use context_helper;
|
||||
use lang_string;
|
||||
use stdClass;
|
||||
use core_reportbuilder\local\entities\base;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\report\{column, filter};
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Role entity
|
||||
@@ -35,14 +34,14 @@ use stdClass;
|
||||
class role extends base {
|
||||
|
||||
/**
|
||||
* Database tables that this entity uses
|
||||
* Database tables that this entity uses and their default aliases
|
||||
*
|
||||
* @return string[]
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_tables(): array {
|
||||
protected function get_default_table_aliases(): array {
|
||||
return [
|
||||
'context',
|
||||
'role',
|
||||
'context' => 'rctx',
|
||||
'role' => 'r',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -95,16 +94,11 @@ class role extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_fields("{$rolealias}.name, {$rolealias}.shortname, {$rolealias}.id, {$contextalias}.id AS contextid")
|
||||
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
|
||||
// The sorting is on name, unless empty (determined by single space - thanks Oracle) then we use shortname.
|
||||
->set_is_sortable(true, [
|
||||
"CASE WHEN " . $DB->sql_concat("{$rolealias}.name", "' '") . " = ' '
|
||||
THEN {$rolealias}.shortname
|
||||
ELSE {$rolealias}.name
|
||||
END",
|
||||
])
|
||||
->add_callback(static function(?string $name, stdClass $role): string {
|
||||
->set_is_sortable(true, ["CASE WHEN {$rolealias}.name = '' THEN {$rolealias}.shortname ELSE {$rolealias}.name END"])
|
||||
->set_callback(static function($name, stdClass $role): string {
|
||||
if ($name === null) {
|
||||
return '';
|
||||
}
|
||||
@@ -115,26 +109,6 @@ class role extends base {
|
||||
return role_get_name($role, $context, ROLENAME_BOTH);
|
||||
});
|
||||
|
||||
// Original name column.
|
||||
$columns[] = (new column(
|
||||
'originalname',
|
||||
new lang_string('roleoriginalname', 'core_role'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_fields("{$rolealias}.name, {$rolealias}.shortname")
|
||||
// The sorting is on name, unless empty (determined by single space - thanks Oracle) then we use shortname.
|
||||
->set_is_sortable(true, [
|
||||
"CASE WHEN " . $DB->sql_concat("{$rolealias}.name", "' '") . " = ' '
|
||||
THEN {$rolealias}.shortname
|
||||
ELSE {$rolealias}.name
|
||||
END",
|
||||
])
|
||||
->add_callback(fn(?string $name, stdClass $role) => match ($name) {
|
||||
null => '',
|
||||
default => role_get_name($role, null, ROLENAME_ORIGINAL),
|
||||
});
|
||||
|
||||
// Short name column.
|
||||
$columns[] = (new column(
|
||||
'shortname',
|
||||
@@ -142,22 +116,8 @@ class role extends base {
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field("{$rolealias}.shortname")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Archetype column.
|
||||
$columns[] = (new column(
|
||||
'archetype',
|
||||
new lang_string('archetype', 'core_role'),
|
||||
$this->get_entity_name(),
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field("{$rolealias}.archetype")
|
||||
->add_callback(fn(?string $archetype) => match ($archetype) {
|
||||
null => '',
|
||||
'' => get_string('none'),
|
||||
default => get_string("archetype{$archetype}", 'core_role'),
|
||||
})
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_fields("{$rolealias}.shortname")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Description column.
|
||||
@@ -174,9 +134,12 @@ class role extends base {
|
||||
->set_type(column::TYPE_LONGTEXT)
|
||||
->add_field($descriptionfieldsql, 'description')
|
||||
->add_field("{$rolealias}.shortname")
|
||||
->add_callback(fn(?string $description, stdClass $role) => match ($description) {
|
||||
null => '',
|
||||
default => role_get_description($role),
|
||||
->set_callback(static function($description, stdClass $role): string {
|
||||
if ($description === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return role_get_description($role);
|
||||
});
|
||||
|
||||
return $columns;
|
||||
@@ -203,22 +166,6 @@ class role extends base {
|
||||
return role_get_names(null, ROLENAME_ORIGINAL, true);
|
||||
});
|
||||
|
||||
// Archetype filter.
|
||||
$filters[] = (new filter(
|
||||
select::class,
|
||||
'archetype',
|
||||
new lang_string('archetype', 'core_role'),
|
||||
$this->get_entity_name(),
|
||||
"{$rolealias}.archetype",
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_options_callback(static function(): array {
|
||||
return array_map(
|
||||
fn(string $archetype) => get_string("archetype{$archetype}", 'core_role'),
|
||||
get_role_archetypes(),
|
||||
);
|
||||
});
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_role\reportbuilder\local\entities;
|
||||
|
||||
use lang_string;
|
||||
use core_reportbuilder\local\entities\base;
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use core_reportbuilder\local\helpers\format;
|
||||
use core_reportbuilder\local\report\{column, filter};
|
||||
|
||||
/**
|
||||
* Role assignment entity
|
||||
*
|
||||
* @package core_role
|
||||
* @copyright 2024 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class role_assignment extends base {
|
||||
|
||||
/**
|
||||
* Database tables that this entity uses
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_default_tables(): array {
|
||||
return [
|
||||
'role_assignments',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The default title for this entity
|
||||
*
|
||||
* @return lang_string
|
||||
*/
|
||||
protected function get_default_entity_title(): lang_string {
|
||||
return new lang_string('roleassignment', 'core_role');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the entity
|
||||
*
|
||||
* @return base
|
||||
*/
|
||||
public function initialise(): base {
|
||||
$columns = $this->get_all_columns();
|
||||
foreach ($columns as $column) {
|
||||
$this->add_column($column);
|
||||
}
|
||||
|
||||
// All the filters defined by the entity can also be used as conditions.
|
||||
$filters = $this->get_all_filters();
|
||||
foreach ($filters as $filter) {
|
||||
$this
|
||||
->add_filter($filter)
|
||||
->add_condition($filter);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all available columns
|
||||
*
|
||||
* @return column[]
|
||||
*/
|
||||
protected function get_all_columns(): array {
|
||||
$raalias = $this->get_table_alias('role_assignments');
|
||||
|
||||
// Time modified column.
|
||||
$columns[] = (new column(
|
||||
'timemodified',
|
||||
new lang_string('timemodified', 'core_reportbuilder'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TIMESTAMP)
|
||||
->add_field("{$raalias}.timemodified")
|
||||
->set_is_sortable(true)
|
||||
->set_callback([format::class, 'userdate']);
|
||||
|
||||
// Component column.
|
||||
$columns[] = (new column(
|
||||
'component',
|
||||
new lang_string('plugin'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field("{$raalias}.component")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Item ID column.
|
||||
$columns[] = (new column(
|
||||
'itemid',
|
||||
new lang_string('pluginitemid'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->add_field("{$raalias}.itemid")
|
||||
->set_is_sortable(true);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all available filters
|
||||
*
|
||||
* @return filter[]
|
||||
*/
|
||||
protected function get_all_filters(): array {
|
||||
$raalias = $this->get_table_alias('role_assignments');
|
||||
|
||||
// Time modified filter.
|
||||
$filters[] = (new filter(
|
||||
date::class,
|
||||
'timemodified',
|
||||
new lang_string('timemodified', 'core_reportbuilder'),
|
||||
$this->get_entity_name(),
|
||||
"{$raalias}.timemodified"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ M.core_role.init_cap_table_filter = function(Y, tableid, contextid) {
|
||||
this.table = Y.one('#'+this.tableid);
|
||||
|
||||
// Create a div to hold the search UI.
|
||||
this.div = Y.Node.create('<div class="capabilitysearchui d-flex flex-wrap align-items-center"></div>').setStyles({
|
||||
this.div = Y.Node.create('<div class="capabilitysearchui form-inline"></div>').setStyles({
|
||||
width : this.table.get('offsetWidth'),
|
||||
marginLeft : 'auto',
|
||||
marginRight : 'auto'
|
||||
|
||||
@@ -165,7 +165,7 @@ if (!empty($capabilities)) {
|
||||
$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 ms-1');
|
||||
'class' => 'btn btn-secondary ml-1');
|
||||
echo html_writer::empty_tag('input', $attrs);
|
||||
echo html_writer::end_tag('div');
|
||||
echo html_writer::end_tag('div');
|
||||
|
||||
@@ -27,7 +27,7 @@ use core_role_preset;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class preset_test extends \advanced_testcase {
|
||||
public function test_xml(): void {
|
||||
public function test_xml() {
|
||||
global $DB;
|
||||
|
||||
$roles = $DB->get_records('role');
|
||||
@@ -81,7 +81,7 @@ class preset_test extends \advanced_testcase {
|
||||
* Tests covered method.
|
||||
* @covers \core_role_preset::parse_preset
|
||||
*/
|
||||
public function test_mixed_levels(): void {
|
||||
public function test_mixed_levels() {
|
||||
// The problem here is that we cannot guarantee plugin contexts
|
||||
// have unique short names, so we have to also support level numbers.
|
||||
$xml = file_get_contents(__DIR__ . '/fixtures/mixed_levels.xml');
|
||||
|
||||
@@ -45,7 +45,7 @@ class provider_test extends provider_testcase {
|
||||
* Test to check export_user_preferences.
|
||||
* returns user preferences data.
|
||||
*/
|
||||
public function test_export_user_preferences(): void {
|
||||
public function test_export_user_preferences() {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
@@ -64,7 +64,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Check all contexts are returned if there is any user data for this user.
|
||||
*/
|
||||
public function test_get_contexts_for_userid(): void {
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -114,7 +114,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test that user data is exported correctly.
|
||||
*/
|
||||
public function test_export_user_data(): void {
|
||||
public function test_export_user_data() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -243,7 +243,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context(): void {
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -329,7 +329,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test for provider::delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user(): void {
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -387,7 +387,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Export for a user with a key against a script where no instance is specified.
|
||||
*/
|
||||
public function test_export_user_role_to_cohort(): void {
|
||||
public function test_export_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -443,7 +443,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test for provider::delete_user_role_to_cohort().
|
||||
*/
|
||||
public function test_delete_user_role_to_cohort(): void {
|
||||
public function test_delete_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -478,7 +478,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test that only users within a course context are fetched.
|
||||
*/
|
||||
public function test_get_users_in_context(): void {
|
||||
public function test_get_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@@ -599,7 +599,7 @@ class provider_test extends provider_testcase {
|
||||
/**
|
||||
* Test that data for users in approved userlist is deleted.
|
||||
*/
|
||||
public function test_delete_data_for_users(): void {
|
||||
public function test_delete_data_for_users() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
@@ -1,257 +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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_role\reportbuilder\datasource;
|
||||
|
||||
use core\context\course;
|
||||
use core_reportbuilder_generator;
|
||||
use core_reportbuilder_testcase;
|
||||
use core_reportbuilder\local\filters\{date, select, text};
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
|
||||
|
||||
/**
|
||||
* Unit tests for roles datasource
|
||||
*
|
||||
* @package core_role
|
||||
* @covers \core_role\reportbuilder\datasource\roles
|
||||
* @copyright 2024 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class roles_test extends core_reportbuilder_testcase {
|
||||
|
||||
/**
|
||||
* Test default datasource
|
||||
*/
|
||||
public function test_datasource_default(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$context = course::instance($course->id);
|
||||
|
||||
$studentone = $this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Zoe']);
|
||||
$studenttwo = $this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Amy']);
|
||||
$manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
$report = $generator->create_report(['name' => 'Roles', 'source' => roles::class, 'default' => 1]);
|
||||
|
||||
$content = $this->get_custom_report_content($report->get('id'));
|
||||
$this->assertCount(3, $content);
|
||||
|
||||
// Default columns are context link, original role name and user link. Sorted by each.
|
||||
[$contextlink, $rolename, $userlink] = array_values($content[0]);
|
||||
$this->assertStringContainsString($context->get_context_name(), $contextlink);
|
||||
$this->assertEquals('Manager', $rolename);
|
||||
$this->assertStringContainsString(fullname($manager), $userlink);
|
||||
|
||||
[$contextlink, $rolename, $userlink] = array_values($content[1]);
|
||||
$this->assertStringContainsString($context->get_context_name(), $contextlink);
|
||||
$this->assertEquals('Student', $rolename);
|
||||
$this->assertStringContainsString(fullname($studenttwo), $userlink);
|
||||
|
||||
[$contextlink, $rolename, $userlink] = array_values($content[2]);
|
||||
$this->assertStringContainsString($context->get_context_name(), $contextlink);
|
||||
$this->assertEquals('Student', $rolename);
|
||||
$this->assertStringContainsString(fullname($studentone), $userlink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test datasource columns that aren't added by default
|
||||
*/
|
||||
public function test_datasource_non_default_columns(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$context = course::instance($course->id);
|
||||
|
||||
// Create an alias for our role.
|
||||
$roleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
|
||||
$DB->insert_record('role_names', (object) [
|
||||
'contextid' => $context->id,
|
||||
'roleid' => $roleid,
|
||||
'name' => 'Moocher',
|
||||
]);
|
||||
|
||||
$manager = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($manager->id, $course->id, $roleid);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
$report = $generator->create_report(['name' => 'Roles', 'source' => roles::class, 'default' => 0]);
|
||||
|
||||
// Role.
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:name']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:shortname']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:archetype']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:description']);
|
||||
|
||||
// Role assignment.
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role_assignment:timemodified']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role_assignment:component']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role_assignment:itemid']);
|
||||
|
||||
$content = $this->get_custom_report_content($report->get('id'));
|
||||
$this->assertCount(1, $content);
|
||||
|
||||
[
|
||||
$rolename,
|
||||
$roleshortname,
|
||||
$rolearchetype,
|
||||
$roledescription,
|
||||
$timemodified,
|
||||
$component,
|
||||
$itemid,
|
||||
] = array_values($content[0]);
|
||||
|
||||
// Role.
|
||||
$this->assertEquals('Moocher (Manager)', $rolename);
|
||||
$this->assertEquals('manager', $roleshortname);
|
||||
$this->assertEquals('ARCHETYPE: Manager', $rolearchetype);
|
||||
$this->assertEquals('Managers can access courses and modify them, but usually do not participate in them.',
|
||||
$roledescription);
|
||||
|
||||
// Role assignment.
|
||||
$this->assertNotEmpty($timemodified);
|
||||
$this->assertEquals('', $component);
|
||||
$this->assertEquals(0, $itemid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_datasource_filters}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function datasource_filters_provider(): array {
|
||||
global $DB;
|
||||
|
||||
return [
|
||||
// Role.
|
||||
'Filter role name' => ['role:name', [
|
||||
'role:name_operator' => select::EQUAL_TO,
|
||||
'role:name_value' => $DB->get_field('role', 'id', ['shortname' => 'student']),
|
||||
], true],
|
||||
'Filter role name (no match)' => ['role:name', [
|
||||
'role:name_operator' => select::EQUAL_TO,
|
||||
'role:name_value' => -1,
|
||||
], false],
|
||||
'Filter role archetype' => ['role:archetype', [
|
||||
'role:archetype_operator' => select::EQUAL_TO,
|
||||
'role:archetype_value' => 'student',
|
||||
], true],
|
||||
'Filter role archetype (no match)' => ['role:archetype', [
|
||||
'role:archetype_operator' => select::EQUAL_TO,
|
||||
'role:archetype_value' => 'teacher',
|
||||
], false],
|
||||
|
||||
// Role assignment.
|
||||
'Filter role assignment time modified' => ['role_assignment:timemodified', [
|
||||
'role_assignment:timemodified_operator' => date::DATE_RANGE,
|
||||
'role_assignment:timemodified_from' => 1622502000,
|
||||
], true],
|
||||
'Filter role assignment time modified (no match)' => ['role_assignment:timemodified', [
|
||||
'role_assignment:timemodified_operator' => date::DATE_RANGE,
|
||||
'role_assignment:timemodified_to' => 1622502000,
|
||||
], false],
|
||||
|
||||
// Context.
|
||||
'Filter context level' => ['context:level', [
|
||||
'context:level_operator' => select::EQUAL_TO,
|
||||
'context:level_value' => CONTEXT_COURSE,
|
||||
], true],
|
||||
'Filter context level (no match)' => ['context:level', [
|
||||
'context:level_operator' => select::EQUAL_TO,
|
||||
'context:level_value' => CONTEXT_COURSECAT,
|
||||
], false],
|
||||
|
||||
// User.
|
||||
'Filter user firstname' => ['user:firstname', [
|
||||
'user:firstname_operator' => text::IS_EQUAL_TO,
|
||||
'user:firstname_value' => 'Zoe',
|
||||
], true],
|
||||
'Filter user firstname (no match)' => ['user:firstname', [
|
||||
'user:firstname_operator' => text::IS_EQUAL_TO,
|
||||
'user:firstname_value' => 'Amy',
|
||||
], false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test datasource filters
|
||||
*
|
||||
* @param string $filtername
|
||||
* @param array $filtervalues
|
||||
* @param bool $expectmatch
|
||||
*
|
||||
* @dataProvider datasource_filters_provider
|
||||
*/
|
||||
public function test_datasource_filters(
|
||||
string $filtername,
|
||||
array $filtervalues,
|
||||
bool $expectmatch,
|
||||
): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Zoe']);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
|
||||
// Create report containing single column, and given filter.
|
||||
$report = $generator->create_report(['name' => 'Roles', 'source' => roles::class, 'default' => 0]);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:shortname']);
|
||||
|
||||
// Add filter, set it's values.
|
||||
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
|
||||
$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
|
||||
|
||||
if ($expectmatch) {
|
||||
$this->assertCount(1, $content);
|
||||
$this->assertEquals('student', reset($content[0]));
|
||||
} else {
|
||||
$this->assertEmpty($content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stress test datasource
|
||||
*
|
||||
* In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
|
||||
*/
|
||||
public function test_stress_datasource(): void {
|
||||
if (!PHPUNIT_LONGTEST) {
|
||||
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
|
||||
}
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$this->getDataGenerator()->create_and_enrol($course);
|
||||
|
||||
$this->datasource_stress_test_columns(roles::class);
|
||||
$this->datasource_stress_test_columns_aggregation(roles::class);
|
||||
$this->datasource_stress_test_conditions(roles::class, 'role:shortname');
|
||||
}
|
||||
}
|
||||
@@ -1,69 +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/>.
|
||||
|
||||
/**
|
||||
* Adds settings links to admin tree.
|
||||
*
|
||||
* AI gets top billing in general because it's the future.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
// Add settings page for AI provider settings.
|
||||
$providers = new admin_settingpage('aiprovider', new lang_string('aiproviders', 'ai'));
|
||||
$providers->add(new admin_setting_heading('availableproviders',
|
||||
get_string('availableproviders', 'core_ai'),
|
||||
get_string('availableproviders_desc', 'core_ai')));
|
||||
$providers->add(new \core_admin\admin\admin_setting_plugin_manager(
|
||||
'aiprovider',
|
||||
\core_ai\table\aiprovider_management_table::class,
|
||||
'manageaiproviders',
|
||||
new lang_string('manageaiproviders', 'core_ai'),
|
||||
));
|
||||
$ADMIN->add('ai', $providers);
|
||||
|
||||
// Load settings for all AI providers.
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('aiprovider');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\aiprovider $plugin */
|
||||
$plugin->load_settings($ADMIN, 'ai', $hassiteconfig);
|
||||
}
|
||||
|
||||
// Add settings page for AI placement settings.
|
||||
$placements = new admin_settingpage('aiplacement', new lang_string('aiplacements', 'ai'));
|
||||
$placements->add(new admin_setting_heading('availableplacements',
|
||||
get_string('availableplacements', 'core_ai'),
|
||||
get_string('availableplacements_desc', 'core_ai')));
|
||||
$placements->add(new \core_admin\admin\admin_setting_plugin_manager(
|
||||
'aiplacement',
|
||||
\core_ai\table\aiplacement_management_table::class,
|
||||
'manageaiplacements',
|
||||
new lang_string('manageaiplacements', 'core_ai'),
|
||||
));
|
||||
$ADMIN->add('ai', $placements);
|
||||
|
||||
// Load settings for all placements.
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('aiplacement');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\aiprovider $plugin */
|
||||
$plugin->load_settings($ADMIN, 'ai', $hassiteconfig);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,59 @@ $capabilities = array(
|
||||
);
|
||||
|
||||
if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // speedup for non-admins, add all caps used on this page
|
||||
|
||||
$ADMIN->add('appearance', new admin_category('themes', new lang_string('themes')));
|
||||
// "themesettings" settingpage
|
||||
$temp = new admin_settingpage('themesettings', new lang_string('themesettings', 'admin'));
|
||||
$setting = new admin_setting_configtext('themelist', new lang_string('themelist', 'admin'),
|
||||
new lang_string('configthemelist', 'admin'), '', PARAM_NOTAGS);
|
||||
$setting->set_force_ltr(true);
|
||||
$temp->add($setting);
|
||||
$setting = new admin_setting_configcheckbox('themedesignermode', new lang_string('themedesignermode', 'admin'), new lang_string('configthemedesignermode', 'admin'), 0);
|
||||
$setting->set_updatedcallback('theme_reset_all_caches');
|
||||
$temp->add($setting);
|
||||
$temp->add(new admin_setting_configcheckbox('allowuserthemes', new lang_string('allowuserthemes', 'admin'), new lang_string('configallowuserthemes', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('allowcoursethemes', new lang_string('allowcoursethemes', 'admin'), new lang_string('configallowcoursethemes', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('allowcategorythemes', new lang_string('allowcategorythemes', 'admin'), new lang_string('configallowcategorythemes', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('allowcohortthemes', new lang_string('allowcohortthemes', 'admin'), new lang_string('configallowcohortthemes', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('allowthemechangeonurl', new lang_string('allowthemechangeonurl', 'admin'), new lang_string('configallowthemechangeonurl', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('allowuserblockhiding', new lang_string('allowuserblockhiding', 'admin'), new lang_string('configallowuserblockhiding', 'admin'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('langmenuinsecurelayout',
|
||||
new lang_string('langmenuinsecurelayout', 'admin'),
|
||||
new lang_string('langmenuinsecurelayout_desc', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('logininfoinsecurelayout',
|
||||
new lang_string('logininfoinsecurelayout', 'admin'),
|
||||
new lang_string('logininfoinsecurelayout_desc', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configtextarea('custommenuitems', new lang_string('custommenuitems', 'admin'),
|
||||
new lang_string('configcustommenuitems', 'admin'), '', PARAM_RAW, '50', '10'));
|
||||
$temp->add(new admin_setting_configtextarea(
|
||||
'customusermenuitems',
|
||||
new lang_string('customusermenuitems', 'admin'),
|
||||
new lang_string('configcustomusermenuitems', 'admin'),
|
||||
'profile,moodle|/user/profile.php
|
||||
grades,grades|/grade/report/mygrades.php
|
||||
calendar,core_calendar|/calendar/view.php?view=month
|
||||
privatefiles,moodle|/user/files.php
|
||||
reports,core_reportbuilder|/reportbuilder/index.php',
|
||||
PARAM_RAW,
|
||||
'50',
|
||||
'10'
|
||||
));
|
||||
$ADMIN->add('themes', $temp);
|
||||
$ADMIN->add('themes', new admin_externalpage('themeselector', new lang_string('themeselector','admin'), $CFG->wwwroot . '/theme/index.php'));
|
||||
|
||||
// settings for each theme
|
||||
foreach (core_component::get_plugin_list('theme') as $theme => $themedir) {
|
||||
$settings_path = "$themedir/settings.php";
|
||||
if (file_exists($settings_path)) {
|
||||
$settings = new admin_settingpage('themesetting'.$theme, new lang_string('pluginname', 'theme_'.$theme));
|
||||
include($settings_path);
|
||||
if ($settings) {
|
||||
$ADMIN->add('themes', $settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logos section.
|
||||
$temp = new admin_settingpage('logos', new lang_string('logossettings', 'admin'));
|
||||
|
||||
@@ -152,12 +205,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
|
||||
}
|
||||
$choices[HOMEPAGE_MYCOURSES] = new lang_string('mycourses', 'admin');
|
||||
$choices[HOMEPAGE_USER] = new lang_string('userpreference', 'admin');
|
||||
|
||||
// Allow hook callbacks to extend options.
|
||||
$hook = new \core_user\hook\extend_default_homepage();
|
||||
\core\di::get(\core\hook\manager::class)->dispatch($hook);
|
||||
$choices += $hook->get_options();
|
||||
|
||||
$temp->add(new admin_setting_configselect('defaulthomepage', new lang_string('defaulthomepage', 'admin'),
|
||||
new lang_string('configdefaulthomepage', 'admin'), get_default_home_page(), $choices));
|
||||
if (!isset($CFG->enabledashboard) || $CFG->enabledashboard) {
|
||||
@@ -187,6 +234,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
|
||||
new lang_string('confignavcourselimit', 'admin'), 10, PARAM_INT));
|
||||
$temp->add(new admin_setting_configcheckbox('usesitenameforsitepages', new lang_string('usesitenameforsitepages', 'admin'), new lang_string('configusesitenameforsitepages', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('linkadmincategories', new lang_string('linkadmincategories', 'admin'), new lang_string('linkadmincategories_help', 'admin'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('linkcoursesections', new lang_string('linkcoursesections', 'admin'), new lang_string('linkcoursesections_help', 'admin'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('navshowfrontpagemods', new lang_string('navshowfrontpagemods', 'admin'), new lang_string('navshowfrontpagemods_help', 'admin'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('navadduserpostslinks', new lang_string('navadduserpostslinks', 'admin'), new lang_string('navadduserpostslinks_help', 'admin'), 1));
|
||||
|
||||
@@ -288,85 +336,4 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
|
||||
$temp = new admin_settingpage('templates', new lang_string('templates', 'admin'));
|
||||
$temp->add($setting);
|
||||
$ADMIN->add('appearance', $temp);
|
||||
|
||||
// Advanced theme settings page.
|
||||
$temp = new admin_settingpage('themesettingsadvanced', new lang_string('themesettingsadvanced', 'admin'));
|
||||
$setting = new admin_setting_configtext('themelist', new lang_string('themelist', 'admin'),
|
||||
new lang_string('configthemelist', 'admin'), '', PARAM_NOTAGS);
|
||||
$setting->set_force_ltr(true);
|
||||
$temp->add($setting);
|
||||
$setting = new admin_setting_configcheckbox('themedesignermode', new lang_string('themedesignermode', 'admin'),
|
||||
new lang_string('configthemedesignermode', 'admin'), 0);
|
||||
$setting->set_updatedcallback('theme_reset_all_caches');
|
||||
$temp->add($setting);
|
||||
|
||||
$setting = new admin_setting_configcheckbox('allowuserthemes', new lang_string('allowuserthemes', 'admin'),
|
||||
new lang_string('configallowuserthemes', 'admin'), 0);
|
||||
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
|
||||
$temp->add($setting);
|
||||
|
||||
$setting = new admin_setting_configcheckbox('allowcoursethemes', new lang_string('allowcoursethemes', 'admin'),
|
||||
new lang_string('configallowcoursethemes', 'admin'), 0);
|
||||
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
|
||||
$temp->add($setting);
|
||||
|
||||
$setting = new admin_setting_configcheckbox('allowcategorythemes', new lang_string('allowcategorythemes', 'admin'),
|
||||
new lang_string('configallowcategorythemes', 'admin'), 0);
|
||||
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
|
||||
$temp->add($setting);
|
||||
|
||||
$setting = new admin_setting_configcheckbox('allowcohortthemes', new lang_string('allowcohortthemes', 'admin'),
|
||||
new lang_string('configallowcohortthemes', 'admin'), 0);
|
||||
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
|
||||
$temp->add($setting);
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('allowthemechangeonurl', new lang_string('allowthemechangeonurl', 'admin'),
|
||||
new lang_string('configallowthemechangeonurl', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('allowuserblockhiding', new lang_string('allowuserblockhiding', 'admin'),
|
||||
new lang_string('configallowuserblockhiding', 'admin'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('langmenuinsecurelayout',
|
||||
new lang_string('langmenuinsecurelayout', 'admin'),
|
||||
new lang_string('langmenuinsecurelayout_desc', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('logininfoinsecurelayout',
|
||||
new lang_string('logininfoinsecurelayout', 'admin'),
|
||||
new lang_string('logininfoinsecurelayout_desc', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configtextarea('custommenuitems', new lang_string('custommenuitems', 'admin'),
|
||||
new lang_string('configcustommenuitems', 'admin'), '', PARAM_RAW, '50', '10'));
|
||||
$defaultsettingcustomusermenuitems = [
|
||||
'profile,moodle|/user/profile.php',
|
||||
'grades,grades|/grade/report/mygrades.php',
|
||||
'calendar,core_calendar|/calendar/view.php?view=month',
|
||||
'privatefiles,moodle|/user/files.php',
|
||||
'reports,core_reportbuilder|/reportbuilder/index.php',
|
||||
];
|
||||
$temp->add(new admin_setting_configtextarea(
|
||||
'customusermenuitems',
|
||||
new lang_string('customusermenuitems', 'admin'),
|
||||
new lang_string('configcustomusermenuitems', 'admin'),
|
||||
implode("\n", $defaultsettingcustomusermenuitems),
|
||||
PARAM_RAW,
|
||||
'50',
|
||||
'10'
|
||||
));
|
||||
$ADMIN->add('appearance', $temp);
|
||||
|
||||
// Theme selector page.
|
||||
$ADMIN->add('appearance', new admin_externalpage('themeselector',
|
||||
new lang_string('themeselector', 'admin'), $CFG->wwwroot . '/admin/themeselector.php'));
|
||||
|
||||
// Settings page for each theme.
|
||||
$ADMIN->add('appearance', new admin_category('themes', new lang_string('themesettingscustom', 'admin')));
|
||||
foreach (core_component::get_plugin_list('theme') as $theme => $themedir) {
|
||||
$settingspath = "$themedir/settings.php";
|
||||
if (file_exists($settingspath)) {
|
||||
$settings = new admin_settingpage("themesetting$theme", new lang_string('pluginname', "theme_$theme"),
|
||||
'moodle/site:config', true
|
||||
);
|
||||
include($settingspath);
|
||||
// Add settings if not hidden (to avoid displaying the section if it appears empty in the UI).
|
||||
if ($settings && !$settings->hidden) {
|
||||
$ADMIN->add('themes', $settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end of speedup
|
||||
|
||||
@@ -87,7 +87,7 @@ if (($hassiteconfig || has_any_capability(array(
|
||||
$ADMIN->add('badges',
|
||||
new admin_externalpage('newbadge',
|
||||
new lang_string('newbadge', 'badges'),
|
||||
new moodle_url('/badges/edit.php', ['action' => 'new']),
|
||||
new moodle_url('/badges/newbadge.php', array('type' => BADGE_TYPE_SITE)),
|
||||
array('moodle/badges:createbadge'), empty($CFG->enablebadges)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -727,12 +727,12 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp = new admin_settingpage('asyncgeneralsettings', new lang_string('asyncgeneralsettings', 'backup'));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('enableasyncbackup', new lang_string('enableasyncbackup', 'backup'),
|
||||
new lang_string('enableasyncbackup_help', 'backup'), 1, 1, 0));
|
||||
new lang_string('enableasyncbackup_help', 'backup'), 0, 1, 0));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox(
|
||||
'backup/backup_async_message_users',
|
||||
new lang_string('asyncemailenable', 'backup'),
|
||||
new lang_string('asyncemailenabledetail', 'backup'), 1));
|
||||
new lang_string('asyncemailenabledetail', 'backup'), 0));
|
||||
$temp->hide_if('backup/backup_async_message_users', 'enableasyncbackup');
|
||||
|
||||
$temp->add(new admin_setting_configtext(
|
||||
|
||||
@@ -109,16 +109,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
// Add the 'profiling' page to admin block.
|
||||
$ADMIN->add('development', $temp);
|
||||
|
||||
$ADMIN->add(
|
||||
parentname: 'development',
|
||||
something: new admin_externalpage(
|
||||
name: 'swaggerui',
|
||||
visiblename: new lang_string('swaggerui', 'admin'),
|
||||
url: "$CFG->wwwroot/admin/swaggerui.php",
|
||||
),
|
||||
);
|
||||
|
||||
// Web service test clients DO NOT COMMIT : THE EXTERNAL WEB PAGE IS NOT AN ADMIN PAGE !!!!!
|
||||
// Web service test clients DO NOT COMMIT : THE EXTERNAL WEB PAGE IS NOT AN ADMIN PAGE !!!!!
|
||||
$ADMIN->add('development', new admin_externalpage('testclient', new lang_string('testclient', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/testclient.php"));
|
||||
|
||||
|
||||
|
||||
@@ -1,44 +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/>.
|
||||
|
||||
/**
|
||||
* Configure the settings for file redaction service.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
if (!$ADMIN->locate('file_redactor')) {
|
||||
$ADMIN->add('server', new admin_category('file_redactor', get_string('redactor', 'core_files')));
|
||||
}
|
||||
|
||||
$manager = \core\di::get(\core_files\redactor\manager::class);
|
||||
|
||||
// Get settings from each service.
|
||||
foreach ($manager->get_service_classnames() as $servicename => $service) {
|
||||
$servicesettings = new admin_settingpage(
|
||||
$servicename,
|
||||
new lang_string("redactor:{$servicename}", 'core_files'),
|
||||
);
|
||||
$service::add_settings($servicesettings);
|
||||
|
||||
$ADMIN->add('file_redactor', $servicesettings);
|
||||
}
|
||||
}
|
||||
@@ -74,8 +74,6 @@ if (has_capability('moodle/grade:manage', $systemcontext)
|
||||
|
||||
$temp->add(new admin_setting_special_gradeexport());
|
||||
|
||||
$temp->add(new admin_setting_special_gradeexportdefault());
|
||||
|
||||
$temp->add(new admin_setting_special_gradelimiting());
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('grade_report_showmin',
|
||||
@@ -115,7 +113,7 @@ if (has_capability('moodle/grade:manage', $systemcontext)
|
||||
|
||||
$defaultvisible = array(GRADE_AGGREGATE_SUM);
|
||||
|
||||
$defaults = array('value' => GRADE_AGGREGATE_SUM, 'forced' => false);
|
||||
$defaults = array('value' => GRADE_AGGREGATE_SUM, 'forced' => false, 'adv' => false);
|
||||
$temp->add(new admin_setting_gradecat_combo('grade_aggregation', new lang_string('aggregation', 'grades'), new lang_string('aggregation_help', 'grades'), $defaults, $options));
|
||||
|
||||
$temp->add(new admin_setting_configmultiselect('grade_aggregations_visible', new lang_string('aggregationsvisible', 'grades'),
|
||||
@@ -123,10 +121,10 @@ if (has_capability('moodle/grade:manage', $systemcontext)
|
||||
|
||||
$options = array(0 => new lang_string('no'), 1 => new lang_string('yes'));
|
||||
|
||||
$defaults = array('value' => 1, 'forced' => false);
|
||||
$defaults = array('value'=>1, 'forced'=>false, 'adv'=>true);
|
||||
$temp->add(new admin_setting_gradecat_combo('grade_aggregateonlygraded', new lang_string('aggregateonlygraded', 'grades'),
|
||||
new lang_string('aggregateonlygraded_help', 'grades'), $defaults, $options));
|
||||
$defaults = array('value' => 0, 'forced' => false);
|
||||
$defaults = array('value'=>0, 'forced'=>false, 'adv'=>true);
|
||||
$temp->add(new admin_setting_gradecat_combo('grade_aggregateoutcomes', new lang_string('aggregateoutcomes', 'grades'),
|
||||
new lang_string('aggregateoutcomes_help', 'grades'), $defaults, $options));
|
||||
|
||||
@@ -163,6 +161,26 @@ if (has_capability('moodle/grade:manage', $systemcontext)
|
||||
'3' => '3',
|
||||
'4' => '4',
|
||||
'5' => '5')));
|
||||
|
||||
$temp->add(new admin_setting_configmultiselect('grade_item_advanced', new lang_string('gradeitemadvanced', 'grades'), new lang_string('gradeitemadvanced_help', 'grades'),
|
||||
array('iteminfo', 'idnumber', 'gradepass', 'plusfactor', 'multfactor', 'display', 'decimals', 'hiddenuntil', 'locktime'),
|
||||
array('iteminfo' => new lang_string('iteminfo', 'grades'),
|
||||
'idnumber' => new lang_string('idnumbermod'),
|
||||
'gradetype' => new lang_string('gradetype', 'grades'),
|
||||
'scaleid' => new lang_string('scale'),
|
||||
'grademin' => new lang_string('grademin', 'grades'),
|
||||
'grademax' => new lang_string('grademax', 'grades'),
|
||||
'gradepass' => new lang_string('gradepass', 'grades'),
|
||||
'plusfactor' => new lang_string('plusfactor', 'grades'),
|
||||
'multfactor' => new lang_string('multfactor', 'grades'),
|
||||
'display' => new lang_string('gradedisplaytype', 'grades'),
|
||||
'decimals' => new lang_string('decimalpoints', 'grades'),
|
||||
'hidden' => new lang_string('hidden', 'grades'),
|
||||
'hiddenuntil' => new lang_string('hiddenuntil', 'grades'),
|
||||
'locked' => new lang_string('locked', 'grades'),
|
||||
'locktime' => new lang_string('locktime', 'grades'),
|
||||
'aggregationcoef' => new lang_string('aggregationcoef', 'grades'),
|
||||
'parentcategory' => new lang_string('parentcategory', 'grades'))));
|
||||
}
|
||||
$ADMIN->add('grades', $temp);
|
||||
|
||||
|
||||
@@ -41,18 +41,4 @@ if (!empty($defaulth5plib)) {
|
||||
|
||||
$settings->add(new admin_settings_h5plib_handler_select('h5plibraryhandler', new lang_string('h5plibraryhandler', 'core_h5p'),
|
||||
new lang_string('h5plibraryhandler_help', 'core_h5p'), $defaulth5plib));
|
||||
|
||||
$setting = new admin_setting_configtextarea(
|
||||
'core_h5p/h5pcustomcss',
|
||||
new lang_string('h5pcustomcss', 'core_h5p'),
|
||||
new lang_string('h5pcustomcss_help', 'core_h5p'),
|
||||
'',
|
||||
PARAM_NOTAGS
|
||||
);
|
||||
$setting->set_updatedcallback(function () {
|
||||
// Enables use of file_storage constants.
|
||||
\core_h5p\local\library\autoloader::register();
|
||||
\core_h5p\file_storage::generate_custom_styles();
|
||||
});
|
||||
$settings->add($setting);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user