Compare commits
1188 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd27666c42 | |||
| d1787723db | |||
| 7ba9f14fe0 | |||
| 0d70e9b8e9 | |||
| 432d3cc846 | |||
| 59de100b8b | |||
| f1e0f1c194 | |||
| 444d046f9e | |||
| 64722afd45 | |||
| be092b7309 | |||
| 005b2bc8a0 | |||
| f4bd5f5e1a | |||
| 1a8342d9d4 | |||
| 6a87753e12 | |||
| 1d16496ee6 | |||
| e7dc32ad80 | |||
| fde541a1eb | |||
| 95ee02b253 | |||
| f562290f78 | |||
| 1273a6665b | |||
| 74ea4a91e1 | |||
| d035bebb1e | |||
| 4c51211800 | |||
| 61edefee12 | |||
| 304bd3b26b | |||
| 57c5494741 | |||
| f1019f4616 | |||
| bbdb62fffe | |||
| cce2977770 | |||
| 60357a5551 | |||
| ce5ea7e187 | |||
| 36b7d0471c | |||
| 580a0918b4 | |||
| 6aaf582982 | |||
| d6cbf8b8fb | |||
| e760287046 | |||
| f0b924f980 | |||
| 1260ed9e33 | |||
| 79df6aee8c | |||
| ee9c931987 | |||
| 01389839df | |||
| 88a78e9307 | |||
| 99e51855a6 | |||
| 5263019b3a | |||
| 442dc8270a | |||
| dab8d7a0dc | |||
| 9f256a8380 | |||
| 6f0614ef19 | |||
| 43dfc84482 | |||
| 5a7e921f70 | |||
| 0220cc4bdf | |||
| d470ef4c27 | |||
| efecaa34a4 | |||
| 11694360bd | |||
| 060e39220c | |||
| e016281821 | |||
| 2267c9fe7f | |||
| 8bcca194ce | |||
| 9bb1a66c3c | |||
| 627b0a7a45 | |||
| f873ff9ad2 | |||
| 0dfef1313f | |||
| e647f49fe4 | |||
| 088f6b973a | |||
| 3c613eb0f9 | |||
| 05403f4989 | |||
| a0a10c4d4c | |||
| f92552d372 | |||
| 769987cf28 | |||
| 3bee541afc | |||
| 069bf5a9dd | |||
| b5c2fef399 | |||
| 6c5b701bb5 | |||
| 696e063e65 | |||
| 0807b60603 | |||
| 2e7dbfaabb | |||
| d6e31537bc | |||
| d01d3d8eef | |||
| 7ac4ad9a8b | |||
| 9d349b8170 | |||
| bd20ed1a7c | |||
| f5d6dcfd06 | |||
| 466ae79141 | |||
| 821001ce9c | |||
| ac7cf1cfeb | |||
| f2057a1115 | |||
| 61c36e125e | |||
| 629c7f75b3 | |||
| bdefa64956 | |||
| bfc047cef0 | |||
| 9c9227995c | |||
| 3a69718c58 | |||
| cb8a553af9 | |||
| 83dc898b00 | |||
| 3e6e80f96f | |||
| 1b052996ff | |||
| e516332576 | |||
| 50615d7105 | |||
| 867e35d77f | |||
| 72337efdf7 | |||
| c0d8a1c3e1 | |||
| f6be98b942 | |||
| d694bf2435 | |||
| 2f58da96bb | |||
| 66f63a3bf6 | |||
| 349ac5cbd7 | |||
| 7985a77474 | |||
| 9df6a3972d | |||
| 5e26c462d3 | |||
| bd547eb6dd | |||
| b4db1fd8ad | |||
| 8cdf03c5a9 | |||
| 17567f33c9 | |||
| 8509759768 | |||
| 20eb1297ef | |||
| ddf605c655 | |||
| 6efc731aab | |||
| 6ea7ce8acb | |||
| 74a30640d6 | |||
| 105bfe1d95 | |||
| e87bbfbe94 | |||
| 5450a19dec | |||
| 094730c7d2 | |||
| 76f2ed9c6a | |||
| 0af0d1d7f6 | |||
| 6415201009 | |||
| 99566b5a60 | |||
| 06a92988fa | |||
| 4202a6935e | |||
| 7c730dfe01 | |||
| 6e0f59fe32 | |||
| 6ed9356daf | |||
| 8539f6168e | |||
| 9e6ea43d93 | |||
| 6b65d96d70 | |||
| 98e96df381 | |||
| 127b7446a0 | |||
| a032e58de6 | |||
| 4db39b4571 | |||
| a9b13cfac3 | |||
| c1d4386dd7 | |||
| b3065a69ed | |||
| aba27d5317 | |||
| 7391dfc208 | |||
| 6d2fedaa47 | |||
| 02f31bb65b | |||
| 1e2f780503 | |||
| 57bebf7c4b | |||
| f940b5dcd9 | |||
| 7cccc7e03a | |||
| 76483d4f45 | |||
| d3ef856e79 | |||
| 5c4aef2664 | |||
| 4bd4b46070 | |||
| 2392c10734 | |||
| f86bd7ece0 | |||
| 805417c33d | |||
| 127b54ffbe | |||
| 8450d9948a | |||
| 3eca9bf311 | |||
| 6b3738bc20 | |||
| 1974156194 | |||
| f0a4839154 | |||
| 3ab6d8ba2b | |||
| 1d2b6c84a2 | |||
| 266744a6f9 | |||
| c3a1ccc244 | |||
| 96df578e8e | |||
| 6cf6523cc7 | |||
| 43eadce6fc | |||
| 98fe6eee32 | |||
| 671ad6b497 | |||
| 49bfb1e5f9 | |||
| 000a5d0bec | |||
| 936ed6067f | |||
| 238006f649 | |||
| d11038dd43 | |||
| 6b8b06911b | |||
| fe1ae23939 | |||
| 64b5d44abc | |||
| 526e3e8c97 | |||
| 8668391630 | |||
| f35e7a0eeb | |||
| 949f522c13 | |||
| ac6bef4e38 | |||
| 72bb7ac302 | |||
| 1b4cd6be30 | |||
| 10dae695ec | |||
| fc12d8d459 | |||
| c46c85055b | |||
| f4411ba700 | |||
| 37002deeda | |||
| 06e4325bda | |||
| 8aa12f5bff | |||
| 84bef5413c | |||
| df26945105 | |||
| b9b8d94716 | |||
| ec92a0762d | |||
| 9bd851f39c | |||
| 502b17bebd | |||
| 2857ca0c9f | |||
| 5a1aa729df | |||
| 6020745280 | |||
| 948485dbd6 | |||
| 1da88f2c6a | |||
| ab38db069e | |||
| c1a29fc252 | |||
| a1d8976005 | |||
| c0171e6090 | |||
| bdf7f7613e | |||
| b42457028a | |||
| 783f2a8b8e | |||
| ac496f22af | |||
| a1c3aedf5b | |||
| a2c9e82c42 | |||
| 82d2d27cd3 | |||
| e8d8bccb25 | |||
| d5859dacc8 | |||
| d738ae91e7 | |||
| daff43e9c7 | |||
| 6aedf45bee | |||
| fa9fffdc9a | |||
| 7ad1d28554 | |||
| 3f3385c46a | |||
| 11ebc149ed | |||
| c985ad6263 | |||
| 6a6d683283 | |||
| ab9f7ab790 | |||
| 3248a578e9 | |||
| 3cd9d0a689 | |||
| 8ee6b2a201 | |||
| 692049a9c1 | |||
| 36895ee702 | |||
| 68e2d67939 | |||
| dd6f34e334 | |||
| 310eff99fc | |||
| 5047c83b9c | |||
| 5b531aabee | |||
| 5723130d84 | |||
| f490505b23 | |||
| ed90c88864 | |||
| faabcf6d76 | |||
| 8837262d2c | |||
| e30562a127 | |||
| b43f63c11f | |||
| 78ccc2c951 | |||
| 9835dc02dd | |||
| 5420485076 | |||
| e69a3f20ee | |||
| 5e29146127 | |||
| 421619e6e3 | |||
| 39ffa8c6e3 | |||
| 53e622ba88 | |||
| 3f70323ba7 | |||
| 99d1a03857 | |||
| a2ddeab20e | |||
| bb05597274 | |||
| cb8c503098 | |||
| dfe3389cf5 | |||
| aac5333538 | |||
| c9038ad429 | |||
| 6d0a80dfc1 | |||
| f22286dd83 | |||
| c4030220a4 | |||
| e088a1f198 | |||
| b6f35bf63e | |||
| b5282bfb67 | |||
| 5721a09e82 | |||
| 21e4f90ca8 | |||
| 9cb0f99c37 | |||
| 4c66ff31c8 | |||
| 2542cb94e4 | |||
| 97be43646f | |||
| 4b1adeef8d | |||
| 0ef5bc0b18 | |||
| 8632026272 | |||
| 706ba3f3af | |||
| c2c97dee39 | |||
| c74949b071 | |||
| 6dc3988fa0 | |||
| 1d59ae4286 | |||
| 38d309036c | |||
| 3c2fa60af3 | |||
| a9b2db5880 | |||
| 3482ea90a6 | |||
| c50377f55f | |||
| 2a9787beee | |||
| 73df15dd67 | |||
| 3fe9524aaf | |||
| 0ec785bfa5 | |||
| c6fa9f2ea2 | |||
| c485c0c443 | |||
| 474ec7e94b | |||
| d7b6ef5729 | |||
| fb6ae10135 | |||
| cadff587be | |||
| 74fbce7560 | |||
| 6ede900c49 | |||
| 4f7cf4088d | |||
| 76486ab639 | |||
| a3809c4fc6 | |||
| b80a22f5ba | |||
| ef6bdbae8e | |||
| 85abbf80e2 | |||
| f2757d51a9 | |||
| 24e83b2807 | |||
| 4e37604528 | |||
| 8b2ae94442 | |||
| 4dec82b25b | |||
| e3796a0678 | |||
| 104c9ffae9 | |||
| c5a4536bb8 | |||
| 740db2e5c3 | |||
| 9fc60dfeb1 | |||
| c270b92ddb | |||
| 835d570210 | |||
| 7a81bcdb34 | |||
| 9818499ed8 | |||
| 5e0c2c9815 | |||
| 50e48b78e9 | |||
| f3a11484ad | |||
| af9b4f1f3b | |||
| 25c946f628 | |||
| 110f3a3d68 | |||
| b20a4bc15c | |||
| d4315bf53e | |||
| f31105a9f5 | |||
| 771c8455cb | |||
| 8297de9fa3 | |||
| e336f07cd7 | |||
| 2ff2c49c85 | |||
| 2f7aaf1d53 | |||
| 551f57926a | |||
| b8daa178de | |||
| 8405e6f1cc | |||
| 89076b49fb | |||
| 8fc16d9e6a | |||
| 5136441985 | |||
| 1afa3903ae | |||
| 4c32762ca7 | |||
| 313531b030 | |||
| 2b37ee6049 | |||
| a5f3967e57 | |||
| 5b78584fdb | |||
| ce390c4725 | |||
| 942d4df55b | |||
| 23628a98fc | |||
| 2daceaf000 | |||
| 8675c2c908 | |||
| 70ffd8bb2b | |||
| 6c1aa273f2 | |||
| 8cc2d39207 | |||
| cac9523423 | |||
| cb4bb6efc5 | |||
| 750121698f | |||
| a78f6cd123 | |||
| c2fea8ae43 | |||
| 0a520053cb | |||
| 1da4edbaf0 | |||
| 9eff7d9535 | |||
| e154204728 | |||
| 2d90a7d91a | |||
| 8ac7c623d3 | |||
| caa9881c0d | |||
| b01258d7f0 | |||
| f0188d7b66 | |||
| 4e52235a23 | |||
| ab94fee038 | |||
| 2d64d63373 | |||
| 6cb165d681 | |||
| 55fc303a5f | |||
| c453ea21e1 | |||
| 24e4ec9d60 | |||
| 25e9be8ea9 | |||
| 9ffcaa7db7 | |||
| 936d21d2dd | |||
| d20b4b2f23 | |||
| d587c4bd90 | |||
| 3828033e98 | |||
| 4892662238 | |||
| ccbd142a63 | |||
| 3b52f52351 | |||
| 173968b25d | |||
| 4cdd9e77dc | |||
| 7805012e59 | |||
| 483d8fefa8 | |||
| 654eac4f13 | |||
| 86699bedb0 | |||
| 914e26d916 | |||
| ced6691934 | |||
| 07e1fa272f | |||
| ff1fbf2992 | |||
| 49e435c00a | |||
| b67ae5a0ad | |||
| b2b1bb2d29 | |||
| 5a13884a0f | |||
| d42328e223 | |||
| 3879b520b7 | |||
| 6a3cffd3c6 | |||
| 40790755ce | |||
| 310a1f0895 | |||
| ed3468e40b | |||
| 9bedb84419 | |||
| c843630aee | |||
| 94f5d062bd | |||
| 233847bdb7 | |||
| f29d518ba8 | |||
| 749c7cc5a9 | |||
| bb3ce79c7e | |||
| 4b52a5b2c6 | |||
| af344834e1 | |||
| cdc9e56198 | |||
| 0e0fd72d98 | |||
| 543005a342 | |||
| 5c33665a74 | |||
| 362518ef34 | |||
| ff246f2260 | |||
| fc1b13ede9 | |||
| 458b0fb900 | |||
| 5df53c769e | |||
| 380a437428 | |||
| 5f6539e379 | |||
| b44ddd4a7f | |||
| 833e2398ef | |||
| df9443db71 | |||
| 4d23edfb37 | |||
| 11489b20a5 | |||
| b3483e7ede | |||
| dd05e0d29e | |||
| 1b37fe78f9 | |||
| 82f21031a3 | |||
| 6545e50f62 | |||
| 3d7630a158 | |||
| 2588126d66 | |||
| de71bef7cb | |||
| e310f10818 | |||
| 9b6938f99b | |||
| 9c42327dd1 | |||
| 28631a1455 | |||
| aa7deee9cc | |||
| 009a9ca500 | |||
| 7de4759799 | |||
| 33433bb8a1 | |||
| 9efe4b84ce | |||
| 12d4fe9ee6 | |||
| 8793e120b9 | |||
| 43f742e1e5 | |||
| 3f4db3e89e | |||
| bea7b33840 | |||
| 4900f375d7 | |||
| a94b1fed30 | |||
| 9337d85eb3 | |||
| 2a8f28520d | |||
| e972b9e909 | |||
| 74c35a9133 | |||
| b4b5124c73 | |||
| af963ad2ae | |||
| 2341e8695e | |||
| f55516fd46 | |||
| 36e337b176 | |||
| 23e9fea9a5 | |||
| 1408aa8bc8 | |||
| 2f86cad04e | |||
| fd77e38fc9 | |||
| 0191113d24 | |||
| 016e029ea6 | |||
| 8bc0bbf1ee | |||
| 1dc4c34d07 | |||
| 5339d26259 | |||
| bc7a379947 | |||
| e9553d973a | |||
| 4f7fa18201 | |||
| 697a28aadf | |||
| 26f5750319 | |||
| d4fea9b20f | |||
| af280e5929 | |||
| fa8d508f36 | |||
| e7bf43ba0d | |||
| 793da897d1 | |||
| e1aa99e99d | |||
| 06b8272791 | |||
| 15703fb9f1 | |||
| 20d8c489d8 | |||
| 1d97a7818e | |||
| 7c64406d4d | |||
| f71b39b960 | |||
| 44dd58756b | |||
| 1dbbcaf9ba | |||
| d0e1b2921d | |||
| 72107631d7 | |||
| 888cfdc75f | |||
| 9886790075 | |||
| f3323c22fe | |||
| d8e011f721 | |||
| 3b63cb559e | |||
| f5189e3aa3 | |||
| 2c643bb988 | |||
| 54e6ea018d | |||
| c5fd53af5d | |||
| e275b0c4a6 | |||
| 9482a77a04 | |||
| fab105578b | |||
| b4dcfe9a06 | |||
| a31a20a5c6 | |||
| e6e7a85087 | |||
| 612c718696 | |||
| 180988c999 | |||
| 59380a9a64 | |||
| d2593fadb6 | |||
| 904aec69a0 | |||
| d851bb5c2a | |||
| 6fa61e69a7 | |||
| c0e65fbbf8 | |||
| cc0b367755 | |||
| 1dc3e88cca | |||
| d04e88b78f | |||
| d77209e8c5 | |||
| 3f27598353 | |||
| 334ced71a1 | |||
| f3a817e959 | |||
| 9712975595 | |||
| 4656db12e1 | |||
| a1ec9e389e | |||
| 3714b3d104 | |||
| 251358b364 | |||
| d2b6ce1555 | |||
| 3c22418e0a | |||
| 94269e4505 | |||
| 0f272f06ea | |||
| 08ca997c76 | |||
| 7d52baba45 | |||
| 72f73d595d | |||
| bf67536031 | |||
| 7a78ae6c10 | |||
| 2cdbfd1dca | |||
| d8924ce166 | |||
| 9ccbf4386c | |||
| fe9d8a7274 | |||
| 9ec5fa2208 | |||
| bffc50ae9d | |||
| a634c88ced | |||
| 2eb7c6d6cb | |||
| 8ae9044ef4 | |||
| eef776a0b4 | |||
| ef57748905 | |||
| 9903e25871 | |||
| 557a9b5674 | |||
| 0ff993ffe3 | |||
| a8a9876c1d | |||
| fd308528cd | |||
| b0f0c2af7e | |||
| ba5dde0347 | |||
| 22cab09558 | |||
| b02f319b78 | |||
| e03ef397fc | |||
| cddd2b461b | |||
| ec41f1ce1b | |||
| 7ce29a90af | |||
| 4dda9579b3 | |||
| 9b77f93fa0 | |||
| 4fc3adb35b | |||
| d3e3f0f109 | |||
| 8bc2ee1a4a | |||
| 93e51e74b4 | |||
| 148ef43015 | |||
| 8b5dfd0b06 | |||
| 1ebd063f4e | |||
| 40579dc143 | |||
| f26629e665 | |||
| ab96a452c6 | |||
| 50e1bbb583 | |||
| 09b6fbdaca | |||
| 8f9c8a5826 | |||
| 9c607c0b8c | |||
| 6dc178e448 | |||
| 187d2a26c0 | |||
| c6755d6f34 | |||
| bea2c40f53 | |||
| 363622d03e | |||
| 649c96c259 | |||
| ac32f752d9 | |||
| db79038541 | |||
| c390ae4f8d | |||
| e0a8cfdb6d | |||
| 52f03c6fc6 | |||
| 9f2c4a9277 | |||
| 575e209b93 | |||
| 0b363e6c4b | |||
| 5d8821ae05 | |||
| 512a916207 | |||
| 86a7cf0909 | |||
| f93f81a9f6 | |||
| bf6f0f03cf | |||
| 903182e4a3 | |||
| e7554e4b67 | |||
| 167420bd2e | |||
| f479887c77 | |||
| 5441c8fef4 | |||
| b104cede75 | |||
| c3c8223ca2 | |||
| d4773c32d6 | |||
| c8511e2808 | |||
| b23e6519fe | |||
| dc7c48a9d7 | |||
| a210233bfb | |||
| 9dcf28af7d | |||
| c030a94b68 | |||
| bd8b3a91e0 | |||
| 582eb6e6e2 | |||
| 9b127bcb70 | |||
| 171a764049 | |||
| 0da8ef69a8 | |||
| 815be4e003 | |||
| d897846115 | |||
| 5427855d50 | |||
| 6d7093e370 | |||
| d7c805a9f0 | |||
| 2032603ae9 | |||
| e7e91b8bae | |||
| 30cbfbdd61 | |||
| ab7694d82e | |||
| 61ee9a304e | |||
| f59bc1b152 | |||
| 3aa57f20af | |||
| b875ad18b5 | |||
| 2d69b0fbe7 | |||
| c8663da1a9 | |||
| 1de2735b16 | |||
| 4a5adabe95 | |||
| b566d8002a | |||
| 2e404995a0 | |||
| 654ad71526 | |||
| 2b50975563 | |||
| 81fb99ed66 | |||
| deb358f16c | |||
| 46574904af | |||
| 4c8005a0ff | |||
| 57cc5dc750 | |||
| 897a492729 | |||
| a6c45af354 | |||
| 36960958ad | |||
| 798f0f1da9 | |||
| 6c32ea4631 | |||
| 554e6a6562 | |||
| 372ea5502c | |||
| 14f7296436 | |||
| 004fd4259e | |||
| 39da8c1a35 | |||
| d1620c57f9 | |||
| dc69734963 | |||
| 8da25ca04c | |||
| ad7f143897 | |||
| 0e156326cc | |||
| 8760b7335b | |||
| 22c0a30888 | |||
| 3e3e5cdbb4 | |||
| 72580c0832 | |||
| 37e1069289 | |||
| 3f18d2af9a | |||
| 65abf2a37c | |||
| 51c17c381d | |||
| 3811659bc7 | |||
| cd0f1aa403 | |||
| 12ef4a1219 | |||
| 9d594541b6 | |||
| 68ed91b7af | |||
| 5e84f521fa | |||
| f63745eabd | |||
| 827c8e98ac | |||
| bcd677bc6a | |||
| 85218c28e9 | |||
| 7f9b187994 | |||
| b188407bdc | |||
| dc71a8b50e | |||
| 096b3723bb | |||
| 76578cf3f6 | |||
| 7831f2785d | |||
| 8880510d40 | |||
| 2f48e08c46 | |||
| 0061a6ae67 | |||
| d060e0b80f | |||
| 63b0f6276c | |||
| 05c87e7bb4 | |||
| 3a959aa800 | |||
| 641d878d08 | |||
| 014335fb2b | |||
| 0668ba779a | |||
| a61cd7efef | |||
| 502344a904 | |||
| df1714f25a | |||
| 3960ad53a7 | |||
| 4557b4e065 | |||
| 05dcf35796 | |||
| 267effaaa2 | |||
| 30f3ea7fe1 | |||
| 3558897fc3 | |||
| b5f402ad73 | |||
| 62b94b486e | |||
| ef0f37ca16 | |||
| b5b86c298c | |||
| 66f2da2e0a | |||
| 04b1bea112 | |||
| 0bffcfccdd | |||
| 8e24041287 | |||
| f8630a8170 | |||
| cea21312f9 | |||
| c7a2ed73b7 | |||
| 882d2787ba | |||
| 54ef15dac8 | |||
| e995166863 | |||
| a890f2ecfa | |||
| af09390773 | |||
| 24bd0bc3b3 | |||
| 5bf7f59a19 | |||
| 15ed7d938e | |||
| 28ec28e026 | |||
| 7bdb9d877d | |||
| 4023f84b29 | |||
| 0f7fb98747 | |||
| 563852791d | |||
| a2d976dd03 | |||
| 823e08bf9d | |||
| ab65b87f3d | |||
| 8b15175347 | |||
| f946d87564 | |||
| 2389ce39ac | |||
| 1906a73574 | |||
| 5aa93d1b1f | |||
| 9fa74e9374 | |||
| 526491f2f7 | |||
| 9eb19cdf94 | |||
| 4f510bea17 | |||
| 614ebf05ea | |||
| 28e06c2a17 | |||
| e63a4ae540 | |||
| 881098f39c | |||
| 76fc9a4f5a | |||
| 5da250d027 | |||
| ffc148ae39 | |||
| 61496b764a | |||
| 776b4fef19 | |||
| 7b4fa14f36 | |||
| 3d8c0fb776 | |||
| aaa1140d9a | |||
| 5e08a28f9b | |||
| b96a064f14 | |||
| ee66104098 | |||
| cef2c013d2 | |||
| 868fc2aba1 | |||
| b513b36cfd | |||
| 0ab6c0d546 | |||
| 0697a52218 | |||
| 20f3b33b24 | |||
| 3e642074ee | |||
| 44d89dfa7a | |||
| ebe7417f94 | |||
| ddb1a13573 | |||
| 61b1cc4dd7 | |||
| 28f3776451 | |||
| b3252ecdc1 | |||
| 6f213a2fa2 | |||
| 195f6fe37e | |||
| b1add54344 | |||
| 239c6202e2 | |||
| 932ee52ba7 | |||
| 38e6852140 | |||
| a339908497 | |||
| f6021ae0af | |||
| 09f9af4729 | |||
| 037027230a | |||
| d04b8d74a7 | |||
| 212d7fef05 | |||
| 5a329d81f9 | |||
| d8eec7ee38 | |||
| 8a5b84cf8b | |||
| e4ad3ee714 | |||
| 75aa674b99 | |||
| 575b2eaa2d | |||
| a7a5d10319 | |||
| 00293f90a4 | |||
| 1f000f071e | |||
| 4033c2a11b | |||
| 74e82e4684 | |||
| 7b6c74431b | |||
| a319808cfb | |||
| f01734122a | |||
| c459409c1b | |||
| 8614257135 | |||
| 07890336ab | |||
| 44efefcbeb | |||
| c2a38d17a4 | |||
| bdc43d7729 | |||
| 7feac02414 | |||
| 51e53b9f40 | |||
| 4dfafed5d9 | |||
| 8e8ae80550 | |||
| 4f41ae2763 | |||
| d2d150b21d | |||
| da3c76158e | |||
| b1f1bd2e54 | |||
| eac66ff63c | |||
| 7258bf0bcf | |||
| f181a24a0b | |||
| 91037dfb29 | |||
| 782b242ccb | |||
| f662082974 | |||
| 4a70fd9973 | |||
| f5b667f1c5 | |||
| efc508c39c | |||
| 1be5d6f4fe | |||
| 84bc2245f7 | |||
| 6c0c9d5d08 | |||
| dfa16c06a6 | |||
| 34dd6c69ab | |||
| 24a12d96e5 | |||
| 3f02b87df0 | |||
| c3da16f13d | |||
| 51b3c29b92 | |||
| a83b43a118 | |||
| 9d56e7925b | |||
| 7140f67a98 | |||
| 337673949b | |||
| 06ede94f07 | |||
| 1b764a2fde | |||
| 99a080f12a | |||
| 0139b8828f | |||
| 0f125a45f4 | |||
| 270cefc4b0 | |||
| a49a438536 | |||
| 6fe7510ae0 | |||
| 8a4b725f6d | |||
| aea028ff29 | |||
| f60b216de0 | |||
| d8609cb545 | |||
| 5b89ff626d | |||
| 4191032993 | |||
| 178bb8c22e | |||
| 9f358de241 | |||
| f7ca19afaa | |||
| 820c234934 | |||
| 5b98200ff4 | |||
| 2d83a76ed7 | |||
| db4754e98c | |||
| 6416e87f26 | |||
| 14e0ab3536 | |||
| a5591614d5 | |||
| 5b399ba107 | |||
| b197ab3566 | |||
| 0f41e8a2a1 | |||
| 468c8d39c3 | |||
| b6b7c2636f | |||
| 7cc21cb7bd | |||
| 5d5881f0f8 | |||
| 7e4bffd836 | |||
| 94522f8dd0 | |||
| 74fc7d3120 | |||
| 59a8a5c0b8 | |||
| 4b8d462ee6 | |||
| fe745a0a84 | |||
| 84a57322c2 | |||
| e4a37fbef1 | |||
| fd7174d03f | |||
| 3e95e09b67 | |||
| caef6489ae | |||
| 2de389aa05 | |||
| be5cc0ba03 | |||
| 5c87353833 | |||
| 55c1865638 | |||
| fe80a5ab66 | |||
| a92bbd867d | |||
| c8e4922194 | |||
| 9faf51a7ca | |||
| fd9c70c43f | |||
| ce3f804683 | |||
| 12ed3e2716 | |||
| 57ea6753d3 | |||
| 4864a25baf | |||
| 451f4a284c | |||
| 93ba31b27c | |||
| 40cd9b66a6 | |||
| 5d2dd881e3 | |||
| 95d7c77b28 | |||
| 02c7769422 | |||
| 69a0a8edef | |||
| 127f9c8274 | |||
| a69de9ba05 | |||
| 7c7ace009a | |||
| ea6dfd78ce | |||
| 800c81dc38 | |||
| 9f7aeac5e5 | |||
| ae3b1c0048 | |||
| b781f58e91 | |||
| ab78499bbd | |||
| 4f2bc5bc8f | |||
| 1144ae255f | |||
| e3a1b34376 | |||
| e151fdad92 | |||
| 2d9d4a1d88 | |||
| 8dd8486355 | |||
| c49f309204 | |||
| a9be2f18bd | |||
| 44d8324247 | |||
| 2207b0fa9f | |||
| 31fd863bd9 | |||
| 630a72f19f | |||
| b043073143 | |||
| fc8cc24c85 | |||
| e2ede0b5c3 | |||
| d0ce3439fe | |||
| 3560cf5f82 | |||
| 29587cc9da | |||
| a22f7bbb3a | |||
| 775db1409e | |||
| 54cbfbca8a | |||
| b175d43408 | |||
| ea083b0a5e | |||
| 966dd82d90 | |||
| 461b1931c0 | |||
| 53a58dda1c | |||
| 9846d710fe | |||
| aad32d11f5 | |||
| 6d3b250b4f | |||
| ebab932f71 | |||
| e9e2ef7da7 | |||
| 0fed66ad0f | |||
| c9b29e6b98 | |||
| 2963a540ef | |||
| 8852faea91 | |||
| 910b62a3d0 | |||
| b03c67e978 | |||
| 23fbd252db | |||
| 97f1c1f424 | |||
| fbd7daf50e | |||
| ea80f967b8 | |||
| a5c2308443 | |||
| fc5c5306c9 | |||
| 70e8515ea8 | |||
| bfe00e67a3 | |||
| fcb7459569 | |||
| 62c9d0877a | |||
| 9967731256 | |||
| 7529031368 | |||
| bdf271df80 | |||
| 209f6e1eda | |||
| 9b3159246f | |||
| cb596d823f | |||
| ab803d697c | |||
| 1c4b87cb97 | |||
| e5ec530a44 | |||
| 70703d4c51 | |||
| a7a6be90c8 | |||
| ed54f4e11e | |||
| 2cae92c6c3 | |||
| 28b870b4de | |||
| 66156ad245 | |||
| 1c73a11a20 | |||
| 6ef8a82599 | |||
| e46c60db2a | |||
| 2566b18a31 | |||
| 4fb76c0fac | |||
| 67e2ee0b65 | |||
| 6c0b4929c6 | |||
| 3149bea2c4 | |||
| 6a23fb2171 | |||
| fa7297aff0 | |||
| bf694d4724 | |||
| 6474044464 | |||
| 97b0a6cbfc | |||
| 04cd1b3f31 | |||
| 3d34aa5c29 | |||
| 545e2b4ec7 | |||
| 9ab7a315d1 | |||
| ae0629d284 | |||
| 46cb408e30 | |||
| c166b700d8 | |||
| 23fe1e2bed | |||
| fd4c7d6461 | |||
| 3128529784 | |||
| 50f5a097a2 | |||
| 09a9b8f1ac | |||
| 2e85118fb3 | |||
| 05014132b6 | |||
| 80b0c733f2 | |||
| fa9eba7d84 | |||
| 4c729982c7 | |||
| d44064cdd6 | |||
| 4e7232b82e | |||
| 205994bafe | |||
| cf6a77fbbb | |||
| a15712ef6e | |||
| f2cb3952af | |||
| 5e92cf33c5 | |||
| 8ddb08c9d9 | |||
| 7a3707797d | |||
| 76b61aa064 | |||
| 2bb0f8923c | |||
| 76cb5a56ea | |||
| 2891577f61 | |||
| 198c797a73 | |||
| d28429859e | |||
| bacaac3a12 | |||
| bb35eb7da7 | |||
| 3127fe10b8 | |||
| d16be43c05 | |||
| 27013d397c | |||
| 159f057751 | |||
| 350fb7829a | |||
| 3cfbce12a6 | |||
| ede6125a08 | |||
| 54659fd14d | |||
| cc98914e85 | |||
| 04507546ec | |||
| 7ee1f3dede | |||
| b3359e2a3b | |||
| 000355f223 | |||
| 028fb5c1f6 | |||
| 61547a4d68 | |||
| fc6cf1802e | |||
| 0f3bcbb92e | |||
| ab01e8a970 | |||
| 2bc753db41 | |||
| cee06411b5 | |||
| ea2ede2cf6 | |||
| 0188d5b5b4 | |||
| a97af76c0b | |||
| 1b35627570 | |||
| c39c3edb8e | |||
| fa43449aad | |||
| f7bac7ac8d | |||
| d1430a1e83 | |||
| 475fddc99a | |||
| f43d811e96 | |||
| 88ab001b25 | |||
| db65b09188 | |||
| 030c58dcb9 | |||
| 3041fe6faa | |||
| cd305dc26b | |||
| 499f2ee9ce | |||
| e1ceb10ee8 | |||
| f4524f8da3 | |||
| 850dfdeadb | |||
| 2a17a616a3 | |||
| 0fc5b4e17a | |||
| 446d8483a3 | |||
| 40938ae2d8 | |||
| 048b266f14 | |||
| 91161a6325 | |||
| a3893e1b4d | |||
| 16f18cb984 | |||
| bfb57d2c16 | |||
| 7085e286c0 | |||
| d42139ace6 | |||
| 1de2a1598e | |||
| 39b17e9933 | |||
| cb3cd93b39 | |||
| 3dee4ca6d9 | |||
| c9591cdde6 | |||
| 06cab84176 | |||
| 3dd3ed3af2 | |||
| 3fcfc19743 | |||
| 1891ed7bb0 | |||
| c5d720ab51 | |||
| d35d17a14c | |||
| 98a2f802c4 | |||
| 1582d54bb0 | |||
| 15729d9ffa | |||
| 1e096d96a2 | |||
| 3b1d98ba73 | |||
| 732ebbe0fe | |||
| 9b7527defc | |||
| 264b7dffb4 | |||
| 3467e8ce3d | |||
| 7816cac9fe | |||
| edbee4c4b6 | |||
| 13fadc8767 | |||
| 897a6e824f | |||
| 583f84d7cb | |||
| 03d6e06400 | |||
| 2b3c4538f1 | |||
| 8f0f0a5903 | |||
| 31b76f4f59 | |||
| 81fa241788 | |||
| 970be4496e | |||
| 3f86772ae2 | |||
| 6c88a36f8f | |||
| cd7baa11c9 | |||
| 97b05c8f1e | |||
| c287f0fdd9 | |||
| 26bb44e5f4 | |||
| 2203ef4f74 | |||
| c9f95d296a | |||
| 7092ba71de | |||
| a88287c67b | |||
| e054f43f5b | |||
| 52f9b15d64 | |||
| a924b2616a | |||
| bebff35401 | |||
| 11dc37dacc | |||
| 11446e8dfc | |||
| 915fd9e6c3 | |||
| 3d110a5f6f | |||
| 1c42b1d6ed | |||
| 2750523534 | |||
| d21c5e384d | |||
| ebdfc3c935 | |||
| 15ce5b7e9b | |||
| af099b484c | |||
| c4a5c8440a | |||
| 739b59610a | |||
| f8a6a533c2 | |||
| d498287fd7 | |||
| d8844b3f8c | |||
| 9827740b01 | |||
| 33a258b573 | |||
| a52770f010 | |||
| a76032ce8d | |||
| 06f49a5eb2 | |||
| e6c1e22197 | |||
| e754bce399 | |||
| df7cc2cc7f | |||
| b078358fa7 | |||
| 3127efef0c | |||
| 353f1bfb37 | |||
| 0b01f6f391 | |||
| bb874345cf | |||
| 75bf06128f | |||
| 540ea1bc7f | |||
| c3f2290d11 | |||
| e2afdc9c21 | |||
| 9fd2b9ac5f | |||
| 5726577fd4 | |||
| c7522f838c | |||
| 7dc966479c | |||
| 49d6fb0379 | |||
| 78d77c4223 | |||
| 97fcf562ba | |||
| 716b5ffa09 | |||
| 07b203db88 | |||
| 5f29d191d9 | |||
| 8c9a196409 | |||
| 258bf5c4e8 | |||
| 10b67ef9f1 | |||
| 46e7b6eecf | |||
| aad67f481a | |||
| f8650bb7a3 | |||
| 5843543338 | |||
| 5c41cd77f8 | |||
| 697bed6bb7 | |||
| 790c89b34d | |||
| 25e213ad8f | |||
| 38ffa48cf5 | |||
| 6ef65b8a85 | |||
| 8e82081176 | |||
| e55b7082be | |||
| 079114d70b | |||
| 1b4983fc90 | |||
| 7bff0ee145 | |||
| c8fc9c61a2 | |||
| e971596371 | |||
| 969d649b79 | |||
| e06a28b539 | |||
| 6fb429dd79 | |||
| 0d1707f053 | |||
| 30e44bfcc4 | |||
| ed0e21c0ef | |||
| a24bf64ffd | |||
| d24fab1182 | |||
| e58362ed1e | |||
| b6ebda707f | |||
| 60ecb39fb6 | |||
| 970c1a4bf1 | |||
| 31c04fec7c | |||
| 04df75ceeb | |||
| acee831165 | |||
| 323ce3952b | |||
| 10acff4ada | |||
| e709853ee6 | |||
| 8e0bebd1cd | |||
| f9a7a98a4a | |||
| 7d5667ee57 | |||
| c706699a0e | |||
| 5a86bf1448 | |||
| dde3d58eb1 | |||
| 83ef2b2cf8 | |||
| 9f9d7bbe87 | |||
| bcc08d3b5e | |||
| c182f060fa | |||
| b197da8770 |
@@ -2,6 +2,7 @@
|
||||
theme/bootstrapbase/style/
|
||||
theme/clean/style/custom.css
|
||||
theme/more/style/custom.css
|
||||
theme/boost/style/moodle.css
|
||||
node_modules/
|
||||
vendor/
|
||||
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ matrix:
|
||||
include:
|
||||
# Run grunt/npm install on highest version ('node' is an alias for the latest node.js version.)
|
||||
- php: 7.2
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='8.9'
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
|
||||
|
||||
exclude:
|
||||
# MySQL - it's just too slow.
|
||||
@@ -207,7 +207,7 @@ before_script:
|
||||
# We need the official upstream for comparison
|
||||
git remote add upstream https://github.com/moodle/moodle.git;
|
||||
|
||||
git fetch upstream master;
|
||||
git fetch upstream MOODLE_35_STABLE;
|
||||
export GIT_PREVIOUS_COMMIT="`git merge-base FETCH_HEAD $TRAVIS_COMMIT`";
|
||||
export GIT_COMMIT="$TRAVIS_COMMIT";
|
||||
export UPSTREAM_FETCH_HEAD=`git rev-parse FETCH_HEAD`
|
||||
|
||||
+14
-2
@@ -137,6 +137,16 @@ module.exports = function(grunt) {
|
||||
}
|
||||
}
|
||||
},
|
||||
sass: {
|
||||
dist: {
|
||||
files: {
|
||||
"theme/boost/style/moodle.css": "theme/boost/scss/preset/default.scss"
|
||||
}
|
||||
},
|
||||
options: {
|
||||
includePaths: ["theme/boost/scss/"]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options: {
|
||||
nospawn: true // We need not to spawn so config can be changed dynamically.
|
||||
@@ -214,7 +224,8 @@ module.exports = function(grunt) {
|
||||
'# Generated by "grunt ignorefiles"',
|
||||
'theme/bootstrapbase/style/',
|
||||
'theme/clean/style/custom.css',
|
||||
'theme/more/style/custom.css'
|
||||
'theme/more/style/custom.css',
|
||||
'theme/boost/style/moodle.css'
|
||||
].concat(thirdPartyPaths);
|
||||
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
|
||||
};
|
||||
@@ -362,6 +373,7 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-less');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-sass');
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-stylelint');
|
||||
|
||||
@@ -374,7 +386,7 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('js', ['amd', 'yui']);
|
||||
|
||||
// Register CSS taks.
|
||||
grunt.registerTask('css', ['stylelint:scss', 'stylelint:less', 'less:bootstrapbase', 'stylelint:css']);
|
||||
grunt.registerTask('css', ['stylelint:scss', 'sass', 'stylelint:less', 'less:bootstrapbase', 'stylelint:css']);
|
||||
|
||||
// Register the startup task.
|
||||
grunt.registerTask('startup', 'Run the correct tasks for the current directory', tasks.startup);
|
||||
|
||||
+5
-5
@@ -14,16 +14,16 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
/**
|
||||
* Privacy Subsystem implementation for core_register.
|
||||
* Privacy Subsystem implementation for core_admin.
|
||||
*
|
||||
* @package core_register
|
||||
* @package core_admin
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_register\privacy;
|
||||
namespace core_admin\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
/**
|
||||
* Privacy Subsystem for core_register implementing null_provider.
|
||||
* Privacy Subsystem for core_admin implementing null_provider.
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
@@ -38,4 +38,4 @@ class provider implements \core_privacy\local\metadata\null_provider {
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2435,4 +2435,185 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.6" requires="3.1">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="5.5.31" />
|
||||
<VENDOR name="mysql" version="5.6" />
|
||||
<VENDOR name="postgres" version="9.4" />
|
||||
<VENDOR name="mssql" version="10.0" />
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.0.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="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="mbstringrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -40,8 +40,9 @@ if ($unregistration && \core\hub\registration::is_registered()) {
|
||||
if ($siteunregistrationform->is_cancelled()) {
|
||||
redirect(new moodle_url('/admin/registration/index.php'));
|
||||
} else if ($data = $siteunregistrationform->get_data()) {
|
||||
if (\core\hub\registration::unregister($data->unpublishalladvertisedcourses,
|
||||
$data->unpublishalluploadedcourses)) {
|
||||
\core\hub\registration::unregister($data->unpublishalladvertisedcourses,
|
||||
$data->unpublishalluploadedcourses);
|
||||
if (!\core\hub\registration::is_registered()) {
|
||||
redirect(new moodle_url('/admin/registration/index.php'));
|
||||
}
|
||||
}
|
||||
@@ -110,7 +111,7 @@ if (\core\hub\registration::is_registered()) {
|
||||
echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'admin'));
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('core', 'register');
|
||||
$renderer = $PAGE->get_renderer('core', 'admin');
|
||||
echo $renderer->moodleorg_registration_message();
|
||||
|
||||
$siteregistrationform->display();
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// This file is part of Moodle - http://moodle.org/ //
|
||||
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
|
||||
// //
|
||||
// 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/>. //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Registration renderer.
|
||||
* @package moodle
|
||||
* @subpackage registration
|
||||
* @copyright 2010 Moodle Pty Ltd (http://moodle.com)
|
||||
* @author Jerome Mouneyrac
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_register_renderer extends plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Display message about the benefits of registering on Moodle.org
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_HTML, ['noclean' => true]);
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -1990,7 +1990,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
if (empty($CFG->docroot) or $environment_result->plugin) {
|
||||
$report = get_string($stringtouse, 'admin', $rec);
|
||||
} else {
|
||||
$report = $this->doc_link(join($linkparts, '/'), get_string($stringtouse, 'admin', $rec));
|
||||
$report = $this->doc_link(join($linkparts, '/'), get_string($stringtouse, 'admin', $rec), true);
|
||||
}
|
||||
// Enclose report text in div so feedback text will be displayed underneath it.
|
||||
$report = html_writer::div($report);
|
||||
@@ -2084,4 +2084,13 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
return $this->warning(get_string('legacylogginginuse'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display message about the benefits of registering on Moodle.org
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_HTML, ['noclean' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,9 +528,11 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
|
||||
if (!$this->disabled) {
|
||||
$output .= '<input type="hidden" name="contextlevel' . $cl . '" value="0" />';
|
||||
}
|
||||
$output .= '<input type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
|
||||
$output .= '<div class="form-check justify-content-start w-100">';
|
||||
$output .= '<input class="form-check-input" type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
|
||||
'" value="1" ' . $extraarguments . '/> ';
|
||||
$output .= '<label for="cl' . $cl . '">' . $clname . "</label><br />\n";
|
||||
$output .= '<label class="form-check-label" for="cl' . $cl . '">' . $clname . "</label>\n";
|
||||
$output .= '</div>';
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Privacy Subsystem implementation for core_role.
|
||||
*
|
||||
* @package core_role
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_role\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_privacy\local\metadata\collection;
|
||||
use \core_privacy\local\request\contextlist;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\transform;
|
||||
use \core_privacy\local\request\writer;
|
||||
|
||||
/**
|
||||
* Privacy provider for core_role.
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\core_privacy\local\request\subsystem\provider,
|
||||
\core_privacy\local\request\subsystem\plugin_provider,
|
||||
\core_privacy\local\request\user_preference_provider {
|
||||
|
||||
/**
|
||||
* Get information about the user data stored by this plugin.
|
||||
*
|
||||
* @param collection $collection An object for storing metadata.
|
||||
* @return collection The metadata.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$rolecapabilities = [
|
||||
'roleid' => 'privacy:metadata:role_capabilities:roleid',
|
||||
'capability' => 'privacy:metadata:role_capabilities:capability',
|
||||
'permission' => 'privacy:metadata:role_capabilities:permission',
|
||||
'timemodified' => 'privacy:metadata:role_capabilities:timemodified',
|
||||
'modifierid' => 'privacy:metadata:role_capabilities:modifierid'
|
||||
];
|
||||
$roleassignments = [
|
||||
'roleid' => 'privacy:metadata:role_assignments:roleid',
|
||||
'userid' => 'privacy:metadata:role_assignments:userid',
|
||||
'timemodified' => 'privacy:metadata:role_assignments:timemodified',
|
||||
'modifierid' => 'privacy:metadata:role_assignments:modifierid',
|
||||
'component' => 'privacy:metadata:role_assignments:component',
|
||||
'itemid' => 'privacy:metadata:role_assignments:itemid'
|
||||
];
|
||||
$collection->add_database_table('role_capabilities', $rolecapabilities,
|
||||
'privacy:metadata:role_capabilities:tableexplanation');
|
||||
$collection->add_database_table('role_assignments', $roleassignments,
|
||||
'privacy:metadata:role_assignments:tableexplanation');
|
||||
|
||||
$collection->add_user_preference('definerole_showadvanced',
|
||||
'privacy:metadata:preference:showadvanced');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
*
|
||||
* @param int $userid The userid of the user whose data is to be exported.
|
||||
*/
|
||||
public static function export_user_preferences(int $userid) {
|
||||
$showadvanced = get_user_preferences('definerole_showadvanced', null, $userid);
|
||||
if ($showadvanced !== null) {
|
||||
writer::export_user_preference('core_role',
|
||||
'definerole_showadvanced',
|
||||
transform::yesno($showadvanced),
|
||||
get_string('privacy:metadata:preference:showadvanced', 'core_role')
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return all contexts for this userid.
|
||||
*
|
||||
* @param int $userid The user ID.
|
||||
* @return contextlist The list of context IDs.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
global $DB;
|
||||
|
||||
$contextlist = new contextlist();
|
||||
|
||||
// The role_capabilities table contains user data.
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {role_capabilities} rc
|
||||
ON rc.contextid = ctx.id
|
||||
AND ((ctx.contextlevel {$insql} AND rc.modifierid = :modifierid)
|
||||
OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))";
|
||||
$params = [
|
||||
'modifierid' => $userid,
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'userid' => $userid
|
||||
];
|
||||
$params += $inparams;
|
||||
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// The role_assignments table contains user data.
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'modifierid' => $userid
|
||||
];
|
||||
$params += $inparams;
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = ra.contextid
|
||||
AND ctx.contextlevel {$insql}
|
||||
WHERE (ra.userid = :userid
|
||||
OR ra.modifierid = :modifierid)
|
||||
AND ra.component != 'tool_cohortroles'";
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The list of approved contexts for a user.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
if (empty($contextlist)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rolesnames = self::get_roles_name();
|
||||
$userid = $contextlist->get_user()->id;
|
||||
$ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
|
||||
// Role Assignments export data.
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, $ctxfields
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = ra.contextid
|
||||
AND ctx.contextlevel {$inctxsql}
|
||||
AND (ra.userid = :userid OR ra.modifierid = :modifierid)
|
||||
AND ra.component != 'tool_cohortroles'
|
||||
JOIN {role} r
|
||||
ON r.id = ra.roleid
|
||||
WHERE ctx.id {$insql}";
|
||||
$params = ['userid' => $userid, 'modifierid' => $userid];
|
||||
$params += $inparams;
|
||||
$params += $ctxparams;
|
||||
$assignments = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($assignments as $assignment) {
|
||||
\context_helper::preload_from_record($assignment);
|
||||
$alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
|
||||
'timemodified' => transform::datetime($assignment->timemodified),
|
||||
'userid' => transform::user($assignment->userid),
|
||||
'modifierid' => transform::user($assignment->modifierid)
|
||||
];
|
||||
}
|
||||
$assignments->close();
|
||||
if (!empty($alldata)) {
|
||||
array_walk($alldata, function($roledata, $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
array_walk($roledata, function($data, $rolename) use ($context) {
|
||||
writer::with_context($context)->export_data(
|
||||
[get_string('privacy:metadata:role_assignments', 'core_role'), $rolename],
|
||||
(object)$data);
|
||||
});
|
||||
});
|
||||
unset($alldata);
|
||||
}
|
||||
|
||||
// Role Capabilities export data.
|
||||
$strpermissions = self::get_permissions_name();
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT rc.id, rc.contextid, rc.capability, rc.permission, rc.timemodified, rc.roleid, $ctxfields
|
||||
FROM {context} ctx
|
||||
JOIN {role_capabilities} rc
|
||||
ON rc.contextid = ctx.id
|
||||
AND ((ctx.contextlevel {$inctxsql} AND rc.modifierid = :modifierid)
|
||||
OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))
|
||||
WHERE ctx.id {$insql}";
|
||||
$params = [
|
||||
'modifierid' => $userid,
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'userid' => $userid
|
||||
];
|
||||
$params += $inparams;
|
||||
$params += $ctxparams;
|
||||
$capabilities = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($capabilities as $capability) {
|
||||
\context_helper::preload_from_record($capability);
|
||||
$alldata[$capability->contextid][$rolesnames[$capability->roleid]][] = (object)[
|
||||
'timemodified' => transform::datetime($capability->timemodified),
|
||||
'capability' => $capability->capability,
|
||||
'permission' => $strpermissions[$capability->permission]
|
||||
];
|
||||
}
|
||||
$capabilities->close();
|
||||
if (!empty($alldata)) {
|
||||
array_walk($alldata, function($capdata, $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
array_walk($capdata, function($data, $rolename) use ($context) {
|
||||
writer::with_context($context)->export_data(
|
||||
[get_string('privacy:metadata:role_capabilities', 'core_role'), $rolename],
|
||||
(object)$data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Exports the data relating to tool_cohortroles component on role assignments by
|
||||
* Assign user roles to cohort feature.
|
||||
*
|
||||
* @param int $userid The user ID.
|
||||
*/
|
||||
public static function export_user_role_to_cohort(int $userid) {
|
||||
global $DB;
|
||||
|
||||
$rolesnames = self::get_roles_name();
|
||||
$sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, r.id as roleid
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = ra.contextid
|
||||
AND ctx.contextlevel = :contextlevel
|
||||
AND ra.component = 'tool_cohortroles'
|
||||
JOIN {role} r
|
||||
ON r.id = ra.roleid
|
||||
WHERE ctx.instanceid = :instanceid
|
||||
OR ra.userid = :userid";
|
||||
$params = ['userid' => $userid, 'instanceid' => $userid, 'contextlevel' => CONTEXT_USER];
|
||||
$assignments = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($assignments as $assignment) {
|
||||
$alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
|
||||
'timemodified' => transform::datetime($assignment->timemodified),
|
||||
'userid' => transform::user($assignment->userid),
|
||||
'modifierid' => transform::user($assignment->modifierid)
|
||||
];
|
||||
}
|
||||
$assignments->close();
|
||||
if (!empty($alldata)) {
|
||||
array_walk($alldata, function($roledata, $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
array_walk($roledata, function($data, $rolename) use ($context) {
|
||||
writer::with_context($context)->export_related_data(
|
||||
[get_string('privacy:metadata:role_cohortroles', 'core_role'), $rolename], 'cohortroles',
|
||||
(object)$data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete all user data for this context.
|
||||
*
|
||||
* @param \context $context The context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
global $DB;
|
||||
|
||||
// Don't remove data from role_capabilities.
|
||||
// Because this data affects the whole Moodle, there are override capabilities.
|
||||
// Don't belong to the modifier user.
|
||||
|
||||
// Remove data from role_assignments.
|
||||
$DB->delete_records('role_assignments', ['contextid' => $context->id]);
|
||||
}
|
||||
/**
|
||||
* Delete all user data for this user only.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The list of approved contexts for a user.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
// Don't remove data from role_capabilities.
|
||||
// Because this data affects the whole Moodle, there are override capabilities.
|
||||
// Don't belong to the modifier user.
|
||||
|
||||
// Remove data from role_assignments.
|
||||
if (empty($contextlist->count())) {
|
||||
return;
|
||||
}
|
||||
$userid = $contextlist->get_user()->id;
|
||||
foreach ($contextlist->get_contexts() as $context) {
|
||||
// Only delete the roles assignments where the user is assigned in all contexts.
|
||||
$DB->delete_records('role_assignments', ['userid' => $userid, 'contextid' => $context->id]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete user entries in role_assignments related to the feature
|
||||
* Assign user roles to cohort feature.
|
||||
*
|
||||
* @param int $userid The user ID.
|
||||
*/
|
||||
public static function delete_user_role_to_cohort(int $userid) {
|
||||
global $DB;
|
||||
|
||||
// Delete entries where userid is a mentor by tool_cohortroles.
|
||||
$DB->delete_records('role_assignments', ['userid' => $userid, 'component' => 'tool_cohortroles']);
|
||||
}
|
||||
/**
|
||||
* Get all the localised roles name in a simple array.
|
||||
*
|
||||
* @return array Array of name of the roles by roleid.
|
||||
*/
|
||||
protected static function get_roles_name() {
|
||||
$roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
|
||||
$rolesnames = array();
|
||||
foreach ($roles as $role) {
|
||||
$rolesnames[$role->id] = $role->localname;
|
||||
}
|
||||
return $rolesnames;
|
||||
}
|
||||
/**
|
||||
* Get all the permissions name in a simple array.
|
||||
*
|
||||
* @return array Array of permissions name.
|
||||
*/
|
||||
protected static function get_permissions_name() {
|
||||
$strpermissions = array(
|
||||
CAP_INHERIT => get_string('inherit', 'role'),
|
||||
CAP_ALLOW => get_string('allow', 'role'),
|
||||
CAP_PREVENT => get_string('prevent', 'role'),
|
||||
CAP_PROHIBIT => get_string('prohibit', 'role')
|
||||
);
|
||||
return $strpermissions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Privacy test for core_role
|
||||
*
|
||||
* @package core_role
|
||||
* @category test
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use \core_role\privacy\provider;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\tests\provider_testcase;
|
||||
use \core_privacy\local\request\transform;
|
||||
use \tool_cohortroles\api;
|
||||
|
||||
/**
|
||||
* Privacy test for core_role
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_role_privacy_testcase extends provider_testcase {
|
||||
/**
|
||||
* Test to check export_user_preferences.
|
||||
* returns user preferences data.
|
||||
*/
|
||||
public function test_export_user_preferences() {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$showadvanced = 1;
|
||||
set_user_preference('definerole_showadvanced', $showadvanced);
|
||||
provider::export_user_preferences($user->id);
|
||||
$writer = writer::with_context(\context_system::instance());
|
||||
$prefs = $writer->get_user_preferences('core_role');
|
||||
$this->assertEquals(transform::yesno($showadvanced), transform::yesno($prefs->definerole_showadvanced->value));
|
||||
$this->assertEquals(get_string('privacy:metadata:preference:showadvanced', 'core_role'),
|
||||
$prefs->definerole_showadvanced->description);
|
||||
}
|
||||
/**
|
||||
* Check all contexts are returned if there is any user data for this user.
|
||||
*/
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->assertEmpty(provider::get_contexts_for_userid($user->id));
|
||||
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
|
||||
$cmcontext2 = \context_module::instance($page->cmid);
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
|
||||
// Role assignments, where the user is assigned.
|
||||
role_assign($student->id, $user->id, $cmcontext2->id);
|
||||
role_assign($student->id, $user->id, $coursecontext2->id);
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
// Role assignments, where the user makes assignments.
|
||||
$this->setUser($user);
|
||||
role_assign($student->id, $user2->id, $coursecontext->id);
|
||||
role_assign($manager->id, $user2->id, $coursecatcontext->id);
|
||||
role_assign($manager->id, $user2->id, $systemcontext->id);
|
||||
|
||||
// Role capabilities.
|
||||
$this->setUser($user);
|
||||
$result = assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $cmcontext->id);
|
||||
|
||||
$contextlist = provider::get_contexts_for_userid($user->id)->get_contextids();
|
||||
$this->assertCount(8, $contextlist);
|
||||
$this->assertTrue(in_array($cmcontext->id, $contextlist));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that user data is exported correctly.
|
||||
*/
|
||||
public function test_export_user_data() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
|
||||
$cmcontext2 = \context_module::instance($page->cmid);
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
$rolesnames = self::get_roles_name();
|
||||
|
||||
$subcontextstudent = [
|
||||
get_string('privacy:metadata:role_assignments', 'core_role'),
|
||||
$rolesnames[$student->id]
|
||||
];
|
||||
$subcontextmanager = [
|
||||
get_string('privacy:metadata:role_assignments', 'core_role'),
|
||||
$rolesnames[$manager->id]
|
||||
];
|
||||
$subcontextrc = [
|
||||
get_string('privacy:metadata:role_capabilities', 'core_role'),
|
||||
$rolesnames[$student->id]
|
||||
];
|
||||
|
||||
// Test over role assignments.
|
||||
// Where the user is assigned.
|
||||
role_assign($student->id, $user->id, $cmcontext2->id);
|
||||
role_assign($student->id, $user->id, $coursecontext2->id);
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
// Where the user makes assignments.
|
||||
$this->setUser($user);
|
||||
role_assign($manager->id, $user2->id, $coursecatcontext->id);
|
||||
role_assign($manager->id, $user2->id, $systemcontext->id);
|
||||
|
||||
// Test overridable roles in module, course, category, user, system and block.
|
||||
assign_capability('moodle/backup:backupactivity', CAP_ALLOW, $student->id, $cmcontext->id, true);
|
||||
assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $coursecontext->id, true);
|
||||
assign_capability('moodle/category:manage', CAP_ALLOW, $student->id, $coursecatcontext->id, true);
|
||||
assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $systemcontext->id, true);
|
||||
assign_capability('moodle/block:edit', CAP_ALLOW, $student->id, $blockcontext->id, true);
|
||||
assign_capability('moodle/competency:evidencedelete', CAP_ALLOW, $student->id, $usercontext2->id, true);
|
||||
|
||||
// Retrieve the user's context ids.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
|
||||
|
||||
$strpermissions = array(
|
||||
CAP_INHERIT => get_string('inherit', 'role'),
|
||||
CAP_ALLOW => get_string('allow', 'role'),
|
||||
CAP_PREVENT => get_string('prevent', 'role'),
|
||||
CAP_PROHIBIT => get_string('prohibit', 'role')
|
||||
);
|
||||
// Retrieve role capabilities and role assignments.
|
||||
provider::export_user_data($approvedcontextlist);
|
||||
foreach ($contextlist as $context) {
|
||||
$writer = writer::with_context($context);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
if ($context->contextlevel == CONTEXT_MODULE) {
|
||||
if ($data = $writer->get_data($subcontextstudent)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/backup:backupactivity', reset($data)->capability);
|
||||
$this->assertEquals($strpermissions[CAP_ALLOW], reset($data)->permission);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_COURSE) {
|
||||
if ($data = $writer->get_data($subcontextstudent)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_COURSECAT) {
|
||||
if ($data = $writer->get_data($subcontextmanager)) {
|
||||
$this->assertEquals($user->id, reset($data)->modifierid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/category:manage', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
if ($data = $writer->get_data($subcontextmanager)) {
|
||||
$this->assertEquals($user->id, reset($data)->modifierid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_BLOCK) {
|
||||
if ($data = $writer->get_data($subcontextstudent)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/block:edit', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_USER) {
|
||||
if ($data = $writer->get_data($subcontextmanager)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/competency:evidencedelete', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
// Role assignments CONTEXT_COURSE.
|
||||
role_assign($student->id, $user->id, $coursecontext->id);
|
||||
role_assign($student->id, $user2->id, $coursecontext->id);
|
||||
role_assign($student->id, $user3->id, $coursecontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
|
||||
$this->assertEquals(3, $count);
|
||||
// Role assignments CONTEXT_COURSECAT.
|
||||
role_assign($student->id, $user2->id, $coursecatcontext->id);
|
||||
role_assign($student->id, $user3->id, $coursecatcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
// Role assignments CONTEXT_SYSTEM.
|
||||
role_assign($student->id, $user->id, $systemcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Role assignments CONTEXT_MODULE.
|
||||
role_assign($student->id, $user->id, $cmcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Role assigments CONTEXT_BLOCK.
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $blockcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Role assigments CONTEXT_USER.
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
|
||||
// Delete data based on CONTEXT_COURSE context.
|
||||
provider::delete_data_for_all_users_in_context($coursecontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Check it is not removing data on other contexts.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Delete data based on CONTEXT_COURSECAT context.
|
||||
provider::delete_data_for_all_users_in_context($coursecatcontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Delete data based on CONTEXT_SYSTEM context.
|
||||
provider::delete_data_for_all_users_in_context($systemcontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Delete data based on CONTEXT_MODULE context.
|
||||
provider::delete_data_for_all_users_in_context($cmcontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Delete data based on CONTEXT_BLOCK context.
|
||||
provider::delete_data_for_all_users_in_context($usercontext2);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
$usercontext3 = \context_user::instance($user3->id);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$course3 = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
$coursecontext3 = \context_course::instance($course3->id);
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
// Role assignments, Where the user is assigned.
|
||||
role_assign($student->id, $user->id, $coursecontext->id);
|
||||
role_assign($student->id, $user->id, $coursecontext2->id);
|
||||
role_assign($student->id, $user->id, $coursecatcontext->id);
|
||||
role_assign($student->id, $user->id, $cmcontext->id);
|
||||
role_assign($student->id, $user->id, $systemcontext->id);
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
role_assign($manager->id, $user->id, $usercontext3->id);
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id]);
|
||||
$this->assertEquals(8, $count);
|
||||
// Role assignments, where the user makes assignments.
|
||||
$this->setUser($user);
|
||||
role_assign($student->id, $user2->id, $coursecontext3->id);
|
||||
role_assign($student->id, $user3->id, $coursecontext3->id);
|
||||
$count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
|
||||
provider::delete_data_for_user($approvedcontextlist);
|
||||
// After deletion, the role_assignments assigned to the user should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// After deletion, the role_assignments assigned by the user should not have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
}
|
||||
/**
|
||||
* Export for a user with a key against a script where no instance is specified.
|
||||
*/
|
||||
public function test_export_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
// Assign user roles to cohort.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$contextuser = \context_user::instance($user->id);
|
||||
$teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
|
||||
$cohort = $this->getDataGenerator()->create_cohort();
|
||||
$userassignover = $this->getDataGenerator()->create_user();
|
||||
$contextuserassignover = \context_user::instance($userassignover->id);
|
||||
cohort_add_member($cohort->id, $userassignover->id);
|
||||
$this->setAdminUser();
|
||||
$params = (object) array(
|
||||
'userid' => $user->id,
|
||||
'roleid' => $teacher->id,
|
||||
'cohortid' => $cohort->id
|
||||
);
|
||||
api::create_cohort_role_assignment($params);
|
||||
api::sync_all_cohort_roles();
|
||||
$rolesnames = self::get_roles_name();
|
||||
$subcontextteacher = [
|
||||
get_string('privacy:metadata:role_cohortroles', 'core_role'),
|
||||
$rolesnames[$teacher->id]
|
||||
];
|
||||
// Test User is assigned role teacher to cohort.
|
||||
provider::export_user_role_to_cohort($user->id);
|
||||
$writer = writer::with_context($contextuserassignover);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
|
||||
$this->assertEquals($user->id, reset($exported)->userid);
|
||||
|
||||
// Test User is member of a cohort which User2 is assigned to role to this cohort.
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$cohort2 = $this->getDataGenerator()->create_cohort();
|
||||
cohort_add_member($cohort2->id, $user->id);
|
||||
$params = (object) array(
|
||||
'userid' => $user2->id,
|
||||
'roleid' => $teacher->id,
|
||||
'cohortid' => $cohort2->id
|
||||
);
|
||||
api::create_cohort_role_assignment($params);
|
||||
api::sync_all_cohort_roles();
|
||||
provider::export_user_role_to_cohort($user->id);
|
||||
$writer = writer::with_context($contextuser);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
|
||||
$this->assertEquals($user2->id, reset($exported)->userid);
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_user_role_to_cohort().
|
||||
*/
|
||||
public function test_delete_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
// Assign user roles to cohort.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
$user4 = $this->getDataGenerator()->create_user();
|
||||
$teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
|
||||
$cohort = $this->getDataGenerator()->create_cohort();
|
||||
cohort_add_member($cohort->id, $user2->id);
|
||||
cohort_add_member($cohort->id, $user3->id);
|
||||
cohort_add_member($cohort->id, $user4->id);
|
||||
$this->setAdminUser();
|
||||
$params = (object) array(
|
||||
'userid' => $user->id,
|
||||
'roleid' => $teacher->id,
|
||||
'cohortid' => $cohort->id
|
||||
);
|
||||
api::create_cohort_role_assignment($params);
|
||||
api::sync_all_cohort_roles();
|
||||
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
|
||||
$this->assertEquals(3, $count);
|
||||
|
||||
provider::delete_user_role_to_cohort($user->id);
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
/**
|
||||
* Supoort function to get all the localised roles name
|
||||
* in a simple array for testing.
|
||||
*
|
||||
* @return array Array of name of the roles by roleid.
|
||||
*/
|
||||
protected static function get_roles_name() {
|
||||
$roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
|
||||
$rolesnames = array();
|
||||
foreach ($roles as $role) {
|
||||
$rolesnames[$role->id] = $role->localname;
|
||||
}
|
||||
return $rolesnames;
|
||||
}
|
||||
}
|
||||
+111
-84
@@ -28,113 +28,139 @@ admin_externalpage_setup('searchareas');
|
||||
|
||||
$areaid = optional_param('areaid', null, PARAM_ALPHAEXT);
|
||||
$action = optional_param('action', null, PARAM_ALPHA);
|
||||
$indexingenabled = \core_search\manager::is_indexing_enabled(); // This restricts many of the actions on this page.
|
||||
|
||||
// Get a search manager instance, which we'll need for display and to handle some actions.
|
||||
try {
|
||||
$searchmanager = \core_search\manager::instance();
|
||||
} catch (core_search\engine_exception $searchmanagererror) {
|
||||
// Continue, we return an error later depending on the requested action.
|
||||
// In action cases, we'll throw this exception below. In non-action cases, we produce a lang string error.
|
||||
}
|
||||
|
||||
// Handle all the actions.
|
||||
if ($action) {
|
||||
|
||||
// If dealing with an areaid, we need to check that the area exists.
|
||||
if ($areaid) {
|
||||
// We need to check that the area exists.
|
||||
$area = \core_search\manager::get_search_area($areaid);
|
||||
if ($area === false) {
|
||||
throw new moodle_exception('invalidrequest');
|
||||
}
|
||||
}
|
||||
|
||||
if ($action !== 'enable' && $action !== 'disable') {
|
||||
// All actions but enable/disable need the search engine to be ready.
|
||||
if (!empty($searchmanagererror)) {
|
||||
throw $searchmanagererror;
|
||||
}
|
||||
// All the indexing actions.
|
||||
if (in_array($action, ['delete', 'indexall', 'reindexall', 'deleteall'])) {
|
||||
|
||||
// Show confirm prompt for all these actions as they may be inadvisable, or may cause
|
||||
// an interruption in search functionality, on production systems.
|
||||
if (!optional_param('confirm', 0, PARAM_INT)) {
|
||||
// Display confirmation prompt.
|
||||
$a = null;
|
||||
if ($areaid) {
|
||||
$a = html_writer::tag('strong', $area->get_visible_name());
|
||||
// All of these actions require that indexing is enabled.
|
||||
if ($indexingenabled) {
|
||||
|
||||
// For all of these actions, we strictly need a manager instance.
|
||||
if (isset($searchmanagererror)) {
|
||||
throw $searchmanagererror;
|
||||
}
|
||||
|
||||
$actionparams = ['sesskey' => sesskey(), 'action' => $action, 'confirm' => 1];
|
||||
if ($areaid) {
|
||||
$actionparams['areaid'] = $areaid;
|
||||
}
|
||||
$actionurl = new moodle_url('/admin/searchareas.php', $actionparams);
|
||||
$cancelurl = new moodle_url('/admin/searchareas.php');
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->confirm(get_string('confirm_' . $action, 'search', $a),
|
||||
// Show confirm prompt for all these actions as they may be inadvisable, or may cause
|
||||
// an interruption in search functionality, on production systems.
|
||||
if (!optional_param('confirm', 0, PARAM_INT)) {
|
||||
// Display confirmation prompt.
|
||||
$a = null;
|
||||
if ($areaid) {
|
||||
$a = html_writer::tag('strong', $area->get_visible_name());
|
||||
}
|
||||
|
||||
$actionparams = ['sesskey' => sesskey(), 'action' => $action, 'confirm' => 1];
|
||||
if ($areaid) {
|
||||
$actionparams['areaid'] = $areaid;
|
||||
}
|
||||
$actionurl = new moodle_url('/admin/searchareas.php', $actionparams);
|
||||
$cancelurl = new moodle_url('/admin/searchareas.php');
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->confirm(get_string('confirm_' . $action, 'search', $a),
|
||||
new single_button($actionurl, get_string('continue'), 'post', true),
|
||||
new single_button($cancelurl, get_string('cancel'), 'get'));
|
||||
echo $OUTPUT->footer();
|
||||
exit;
|
||||
echo $OUTPUT->footer();
|
||||
exit;
|
||||
} else {
|
||||
// Confirmed, so run the required action.
|
||||
require_sesskey();
|
||||
|
||||
switch ($action) {
|
||||
case 'delete':
|
||||
$searchmanager->delete_index($areaid);
|
||||
\core\notification::success(get_string('searchindexdeleted', 'admin'));
|
||||
break;
|
||||
case 'indexall':
|
||||
$searchmanager->index();
|
||||
\core\notification::success(get_string('searchindexupdated', 'admin'));
|
||||
break;
|
||||
case 'reindexall':
|
||||
$searchmanager->index(true);
|
||||
\core\notification::success(get_string('searchreindexed', 'admin'));
|
||||
break;
|
||||
case 'deleteall':
|
||||
$searchmanager->delete_index();
|
||||
\core\notification::success(get_string('searchalldeleted', 'admin'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Redirect back to the main page after taking action.
|
||||
redirect(new moodle_url('/admin/searchareas.php'));
|
||||
}
|
||||
}
|
||||
} else if (in_array($action, ['enable', 'disable'])) {
|
||||
// Toggling search areas requires no confirmation.
|
||||
require_sesskey();
|
||||
|
||||
switch ($action) {
|
||||
case 'enable':
|
||||
$area->set_enabled(true);
|
||||
\core\notification::success(get_string('searchareaenabled', 'admin'));
|
||||
break;
|
||||
case 'disable':
|
||||
$area->set_enabled(false);
|
||||
core\notification::success(get_string('searchareadisabled', 'admin'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
redirect(new moodle_url('/admin/searchareas.php'));
|
||||
} else {
|
||||
// Invalid action.
|
||||
throw new moodle_exception('invalidaction');
|
||||
}
|
||||
}
|
||||
|
||||
// We are now taking an actual action, so require sesskey.
|
||||
require_sesskey();
|
||||
|
||||
switch ($action) {
|
||||
case 'enable':
|
||||
$area->set_enabled(true);
|
||||
\core\notification::add(get_string('searchareaenabled', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
|
||||
break;
|
||||
case 'disable':
|
||||
$area->set_enabled(false);
|
||||
\core\notification::add(get_string('searchareadisabled', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
|
||||
break;
|
||||
case 'delete':
|
||||
$search = \core_search\manager::instance();
|
||||
$search->delete_index($areaid);
|
||||
\core\notification::add(get_string('searchindexdeleted', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
|
||||
break;
|
||||
case 'indexall':
|
||||
$searchmanager->index();
|
||||
\core\notification::add(get_string('searchindexupdated', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
|
||||
break;
|
||||
case 'reindexall':
|
||||
$searchmanager->index(true);
|
||||
\core\notification::add(get_string('searchreindexed', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
|
||||
break;
|
||||
case 'deleteall':
|
||||
$searchmanager->delete_index();
|
||||
\core\notification::add(get_string('searchalldeleted', 'admin'), \core\output\notification::NOTIFY_SUCCESS);
|
||||
break;
|
||||
default:
|
||||
throw new moodle_exception('invalidaction');
|
||||
break;
|
||||
}
|
||||
|
||||
// Redirect back to the main page after taking action.
|
||||
redirect(new moodle_url('/admin/searchareas.php'));
|
||||
// Display.
|
||||
if (isset($searchmanager) && $indexingenabled) {
|
||||
\core\notification::info(get_string('indexinginfo', 'admin'));
|
||||
} else if (isset($searchmanager)) {
|
||||
$params = (object) [
|
||||
'url' => (new moodle_url("/admin/settings.php?section=manageglobalsearch#admin-searchindexwhendisabled"))->out(false)
|
||||
];
|
||||
\core\notification::error(get_string('indexwhendisabledfullnotice', 'search', $params));
|
||||
} else {
|
||||
// In non-action cases, init errors are translated and displayed to the user as error notifications.
|
||||
$errorstr = get_string($searchmanagererror->errorcode, $searchmanagererror->module, $searchmanagererror->a);
|
||||
\core\notification::error($errorstr);
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
$searchareas = \core_search\manager::get_search_areas_list();
|
||||
if (empty($searchmanagererror)) {
|
||||
$areasconfig = $searchmanager->get_areas_config($searchareas);
|
||||
} else {
|
||||
$areasconfig = false;
|
||||
}
|
||||
|
||||
if (!empty($searchmanagererror)) {
|
||||
$errorstr = get_string($searchmanagererror->errorcode, $searchmanagererror->module, $searchmanagererror->a);
|
||||
echo $OUTPUT->notification($errorstr, \core\output\notification::NOTIFY_ERROR);
|
||||
} else {
|
||||
echo $OUTPUT->notification(get_string('indexinginfo', 'admin'), \core\output\notification::NOTIFY_INFO);
|
||||
}
|
||||
|
||||
$table = new html_table();
|
||||
$table->id = 'core-search-areas';
|
||||
$table->head = [
|
||||
get_string('searcharea', 'search'),
|
||||
get_string('enable'),
|
||||
get_string('newestdocindexed', 'admin'),
|
||||
get_string('searchlastrun', 'admin'),
|
||||
get_string('searchindexactions', 'admin')
|
||||
];
|
||||
|
||||
$table->head = array(get_string('searcharea', 'search'), get_string('enable'), get_string('newestdocindexed', 'admin'),
|
||||
get_string('searchlastrun', 'admin'), get_string('searchindexactions', 'admin'));
|
||||
|
||||
$searchareas = \core_search\manager::get_search_areas_list();
|
||||
$areasconfig = isset($searchmanager) ? $searchmanager->get_areas_config($searchareas) : false;
|
||||
foreach ($searchareas as $area) {
|
||||
$areaid = $area->get_area_id();
|
||||
$columns = array(new html_table_cell($area->get_visible_name()));
|
||||
@@ -144,7 +170,7 @@ foreach ($searchareas as $area) {
|
||||
new pix_icon('t/hide', get_string('disable'), 'moodle', array('title' => '', 'class' => 'iconsmall')),
|
||||
null, array('title' => get_string('disable')));
|
||||
|
||||
if ($areasconfig) {
|
||||
if ($areasconfig && $indexingenabled) {
|
||||
$columns[] = $areasconfig[$areaid]->lastindexrun;
|
||||
|
||||
if ($areasconfig[$areaid]->indexingstart) {
|
||||
@@ -173,7 +199,11 @@ foreach ($searchareas as $area) {
|
||||
$columns[] = html_writer::alist($actions, ['class' => 'unstyled list-unstyled']);
|
||||
|
||||
} else {
|
||||
$blankrow = new html_table_cell(get_string('searchnotavailable', 'admin'));
|
||||
if (!$areasconfig) {
|
||||
$blankrow = new html_table_cell(get_string('searchnotavailable', 'admin'));
|
||||
} else {
|
||||
$blankrow = new html_table_cell(get_string('indexwhendisabledshortnotice', 'search'));
|
||||
}
|
||||
$blankrow->colspan = 3;
|
||||
$columns[] = $blankrow;
|
||||
}
|
||||
@@ -192,10 +222,7 @@ foreach ($searchareas as $area) {
|
||||
}
|
||||
|
||||
// Cross-search area tasks.
|
||||
$options = array();
|
||||
if (!empty($searchmanagererror)) {
|
||||
$options['disabled'] = true;
|
||||
}
|
||||
$options = (isset($searchmanager) && $indexingenabled) ? [] : ['disabled' => true];
|
||||
echo $OUTPUT->box_start('search-areas-actions');
|
||||
echo $OUTPUT->single_button(admin_searcharea_action_url('indexall'), get_string('searchupdateindex', 'admin'), 'get', $options);
|
||||
echo $OUTPUT->single_button(admin_searcharea_action_url('reindexall'), get_string('searchreindexindex', 'admin'), 'get', $options);
|
||||
@@ -204,7 +231,7 @@ echo $OUTPUT->box_end();
|
||||
|
||||
echo html_writer::table($table);
|
||||
|
||||
if (empty($searchmanagererror)) {
|
||||
if (isset($searchmanager)) {
|
||||
// Show information about queued index requests for specific contexts.
|
||||
$searchrenderer = $PAGE->get_renderer('core_search');
|
||||
echo $searchrenderer->render_index_requests_info($searchmanager->get_index_requests_info());
|
||||
|
||||
+2
-1
@@ -38,7 +38,8 @@ if (!($settingspage->check_access())) {
|
||||
$statusmsg = '';
|
||||
$errormsg = '';
|
||||
|
||||
if ($data = data_submitted() and confirm_sesskey()) {
|
||||
// Form is submitted with changed settings. Do not want to execute when modifying a block.
|
||||
if ($data = data_submitted() and confirm_sesskey() and isset($data->action) and $data->action == 'save-settings') {
|
||||
|
||||
$count = admin_write_settings($data);
|
||||
// Regardless of whether any setting change was written (a positive count), check validation errors for those that didn't.
|
||||
|
||||
@@ -16,6 +16,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
|
||||
$temp->add(new admin_setting_configexecutable('pathtosassc', new lang_string('pathtosassc', 'admin'), new lang_string('pathtosassc_help', 'admin'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('forceclean', new lang_string('forceclean', 'core_admin'),
|
||||
new lang_string('forceclean_desc', 'core_admin'), 0));
|
||||
|
||||
$ADMIN->add('experimental', $temp);
|
||||
|
||||
// "debugging" settingpage
|
||||
@@ -30,7 +33,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$ADMIN->add('development', $temp);
|
||||
|
||||
// "Profiling" settingpage (conditionally if the 'xhprof' extension is available only).
|
||||
$xhprofenabled = extension_loaded('xhprof') || extension_loaded('tideways');
|
||||
$xhprofenabled = extension_loaded('tideways_xhprof');
|
||||
$xhprofenabled = $xhprofenabled || extension_loaded('tideways');
|
||||
$xhprofenabled = $xhprofenabled || extension_loaded('xhprof');
|
||||
$temp = new admin_settingpage('profiling', new lang_string('profiling', 'admin'), 'moodle/site:config', !$xhprofenabled);
|
||||
// Main profiling switch.
|
||||
$temp->add(new admin_setting_configcheckbox('profilingenabled', new lang_string('profilingenabled', 'admin'), new lang_string('profilingenabled_help', 'admin'), false));
|
||||
|
||||
@@ -1,80 +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 privacy and policies links to admin tree.
|
||||
*
|
||||
* @package core_privacy
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
// Privacy settings.
|
||||
$temp = new admin_settingpage('privacysettings', new lang_string('privacysettings', 'admin'));
|
||||
|
||||
$options = array(
|
||||
0 => get_string('no'),
|
||||
1 => get_string('yes')
|
||||
);
|
||||
$url = new moodle_url('/admin/settings.php?section=supportcontact');
|
||||
$url = $url->out();
|
||||
$setting = new admin_setting_configselect('agedigitalconsentverification',
|
||||
new lang_string('agedigitalconsentverification', 'admin'),
|
||||
new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
|
||||
$setting->set_force_ltr(true);
|
||||
$temp->add($setting);
|
||||
|
||||
$setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
|
||||
new lang_string('ageofdigitalconsentmap', 'admin'),
|
||||
new lang_string('ageofdigitalconsentmap_desc', 'admin'),
|
||||
// See {@link https://gdpr-info.eu/art-8-gdpr/}.
|
||||
implode(PHP_EOL, [
|
||||
'*, 16',
|
||||
'AT, 14',
|
||||
'CZ, 13',
|
||||
'DE, 14',
|
||||
'DK, 13',
|
||||
'ES, 13',
|
||||
'FI, 15',
|
||||
'GB, 13',
|
||||
'HU, 14',
|
||||
'IE, 13',
|
||||
'LT, 16',
|
||||
'LU, 16',
|
||||
'NL, 16',
|
||||
'PL, 13',
|
||||
'SE, 13',
|
||||
]),
|
||||
PARAM_RAW
|
||||
);
|
||||
$temp->add($setting);
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
|
||||
// Policy settings.
|
||||
$temp = new admin_settingpage('policysettings', new lang_string('policysettings', 'admin'));
|
||||
$temp->add(new admin_settings_sitepolicy_handler_select('sitepolicyhandler', new lang_string('sitepolicyhandler', 'core_admin'),
|
||||
new lang_string('sitepolicyhandler_desc', 'core_admin')));
|
||||
$temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'core_admin'),
|
||||
new lang_string('sitepolicy_help', 'core_admin'), '', PARAM_RAW));
|
||||
$temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'core_admin'),
|
||||
new lang_string('sitepolicyguest_help', 'core_admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
}
|
||||
@@ -43,9 +43,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
|
||||
new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('forceclean', new lang_string('forceclean', 'core_admin'),
|
||||
new lang_string('forceclean_desc', 'core_admin'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configselect('maxeditingtime', new lang_string('maxeditingtime','admin'), new lang_string('configmaxeditingtime','admin'), 1800,
|
||||
|
||||
@@ -29,7 +29,6 @@ $ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empt
|
||||
$ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
|
||||
$ADMIN->add('root', new admin_category('language', new lang_string('language')));
|
||||
$ADMIN->add('root', new admin_category('modules', new lang_string('plugins', 'admin')));
|
||||
$ADMIN->add('root', new admin_category('privacy', new lang_string('privacyandpolicies', 'admin')));
|
||||
$ADMIN->add('root', new admin_category('security', new lang_string('security','admin')));
|
||||
$ADMIN->add('root', new admin_category('appearance', new lang_string('appearance','admin')));
|
||||
$ADMIN->add('root', new admin_category('frontpage', new lang_string('frontpage','admin')));
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
$ADMIN->add('users', new admin_category('accounts', new lang_string('accounts', 'admin')));
|
||||
$ADMIN->add('users', new admin_category('roles', new lang_string('permissions', 'role')));
|
||||
$ADMIN->add('users', new admin_category('privacy', new lang_string('privacyandpolicies', 'admin')));
|
||||
|
||||
if ($hassiteconfig
|
||||
or has_capability('moodle/user:create', $systemcontext)
|
||||
@@ -210,3 +211,48 @@ if ($hassiteconfig
|
||||
$ADMIN->add('roles', new admin_externalpage('checkpermissions', new lang_string('checkglobalpermissions', 'role'), "$CFG->wwwroot/$CFG->admin/roles/check.php?contextid=".$systemcontext->id, array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:manage')));
|
||||
|
||||
} // end of speedup
|
||||
|
||||
// Privacy settings.
|
||||
if ($hassiteconfig) {
|
||||
$temp = new admin_settingpage('privacysettings', new lang_string('privacysettings', 'admin'));
|
||||
|
||||
$options = array(
|
||||
0 => get_string('no'),
|
||||
1 => get_string('yes')
|
||||
);
|
||||
$url = new moodle_url('/admin/settings.php?section=supportcontact');
|
||||
$url = $url->out();
|
||||
$setting = new admin_setting_configselect('agedigitalconsentverification',
|
||||
new lang_string('agedigitalconsentverification', 'admin'),
|
||||
new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
|
||||
$setting->set_force_ltr(true);
|
||||
$temp->add($setting);
|
||||
|
||||
// See {@link https://gdpr-info.eu/art-8-gdpr/}.
|
||||
$ageofdigitalconsentmap = implode(PHP_EOL, [
|
||||
'*, 16',
|
||||
'AT, 14',
|
||||
'ES, 14',
|
||||
'US, 13'
|
||||
]);
|
||||
$setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
|
||||
new lang_string('ageofdigitalconsentmap', 'admin'),
|
||||
new lang_string('ageofdigitalconsentmap_desc', 'admin'),
|
||||
$ageofdigitalconsentmap,
|
||||
PARAM_RAW
|
||||
);
|
||||
$temp->add($setting);
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
|
||||
// Policy settings.
|
||||
$temp = new admin_settingpage('policysettings', new lang_string('policysettings', 'admin'));
|
||||
$temp->add(new admin_settings_sitepolicy_handler_select('sitepolicyhandler', new lang_string('sitepolicyhandler', 'core_admin'),
|
||||
new lang_string('sitepolicyhandler_desc', 'core_admin')));
|
||||
$temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'core_admin'),
|
||||
new lang_string('sitepolicy_help', 'core_admin'), '', PARAM_RAW));
|
||||
$temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'core_admin'),
|
||||
new lang_string('sitepolicyguest_help', 'core_admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
<div class="settingsform clearfix">
|
||||
{{#params}}
|
||||
<input type="hidden" name="{{name}}" value="{{value}}">
|
||||
<input type="hidden" name="action" value="save-settings">
|
||||
{{/params}}
|
||||
<input type="hidden" name="sesskey" value="{{sesskey}}">
|
||||
<input type="hidden" name="return" value="{{return}}">
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_assignmentupgrade';
|
||||
$plugin->dependencies = array('mod_assign' => 2017110800);
|
||||
$plugin->dependencies = array('mod_assign' => 2018050800);
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_availabilityconditions';
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version
|
||||
$plugin->component = 'tool_behat'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_capability'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Privacy Subsystem implementation for tool_cohortroles.
|
||||
*
|
||||
* @package tool_cohortroles
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_cohortroles\privacy;
|
||||
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\context;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for tool_cohortroles implementing metadata and plugin providers.
|
||||
*
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider {
|
||||
|
||||
/**
|
||||
* Returns meta data about this system.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
// The tool_cohortroles plugin utilises the mdl_tool_cohortroles table.
|
||||
$collection->add_database_table(
|
||||
'tool_cohortroles',
|
||||
[
|
||||
'id' => 'privacy:metadata:tool_cohortroles:id',
|
||||
'cohortid' => 'privacy:metadata:tool_cohortroles:cohortid',
|
||||
'roleid' => 'privacy:metadata:tool_cohortroles:roleid',
|
||||
'userid' => 'privacy:metadata:tool_cohortroles:userid',
|
||||
'timecreated' => 'privacy:metadata:tool_cohortroles:timecreated',
|
||||
'timemodified' => 'privacy:metadata:tool_cohortroles:timemodified',
|
||||
'usermodified' => 'privacy:metadata:tool_cohortroles:usermodified'
|
||||
],
|
||||
'privacy:metadata:tool_cohortroles'
|
||||
);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @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 {
|
||||
$contextlist = new contextlist();
|
||||
|
||||
// Retrieve the User context associated with tool_cohortroles records.
|
||||
$sql = "SELECT DISTINCT c.id
|
||||
FROM {context} c
|
||||
JOIN {tool_cohortroles} cr ON cr.userid = c.instanceid AND c.contextlevel = :contextuser
|
||||
WHERE cr.userid = :userid";
|
||||
|
||||
$params = [
|
||||
'contextuser' => CONTEXT_USER,
|
||||
'userid' => $userid
|
||||
];
|
||||
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
// If the user has tool_cohortroles data, then only the User context should be present so get the first context.
|
||||
$contexts = $contextlist->get_contexts();
|
||||
if (count($contexts) == 0) {
|
||||
return;
|
||||
}
|
||||
$context = reset($contexts);
|
||||
|
||||
// Sanity check that context is at the User context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
|
||||
// Retrieve the tool_cohortroles records created for the user.
|
||||
$sql = 'SELECT cr.id as cohortroleid,
|
||||
c.name as cohortname,
|
||||
c.idnumber as cohortidnumber,
|
||||
c.description as cohortdescription,
|
||||
r.shortname as roleshortname,
|
||||
cr.userid as userid,
|
||||
cr.timecreated as timecreated,
|
||||
cr.timemodified as timemodified
|
||||
FROM {tool_cohortroles} cr
|
||||
JOIN {cohort} c ON c.id = cr.cohortid
|
||||
JOIN {role} r ON r.id = cr.roleid
|
||||
WHERE cr.userid = :userid';
|
||||
|
||||
$params = [
|
||||
'userid' => $userid
|
||||
];
|
||||
|
||||
$cohortroles = $DB->get_records_sql($sql, $params);
|
||||
foreach ($cohortroles as $cohortrole) {
|
||||
// The tool_cohortroles data export is organised in:
|
||||
// {User Context}/Cohort roles management/{cohort name}/{role shortname}/data.json.
|
||||
$subcontext = [
|
||||
get_string('pluginname', 'tool_cohortroles'),
|
||||
$cohortrole->cohortname,
|
||||
$cohortrole->roleshortname
|
||||
];
|
||||
|
||||
$data = (object) [
|
||||
'cohortname' => $cohortrole->cohortname,
|
||||
'cohortidnumber' => $cohortrole->cohortidnumber,
|
||||
'cohortdescription' => $cohortrole->cohortdescription,
|
||||
'roleshortname' => $cohortrole->roleshortname,
|
||||
'userid' => transform::user($cohortrole->userid),
|
||||
'timecreated' => transform::datetime($cohortrole->timecreated),
|
||||
'timemodified' => transform::datetime($cohortrole->timemodified)
|
||||
];
|
||||
|
||||
writer::with_context($context)->export_data($subcontext, $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
global $DB;
|
||||
|
||||
// Sanity check that context is at the User context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
|
||||
// Delete the tool_cohortroles records created for the userid.
|
||||
$DB->delete_records('tool_cohortroles', ['userid' => $userid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
// If the user has tool_cohortroles data, then only the User context should be present so get the first context.
|
||||
$contexts = $contextlist->get_contexts();
|
||||
if (count($contexts) == 0) {
|
||||
return;
|
||||
}
|
||||
$context = reset($contexts);
|
||||
|
||||
// Sanity check that context is at the User context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
|
||||
// Delete the tool_cohortroles records created for the userid.
|
||||
$DB->delete_records('tool_cohortroles', ['userid' => $userid]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,3 +42,11 @@ $string['selectrole'] = 'Select role';
|
||||
$string['selectusers'] = 'Select users to assign role';
|
||||
$string['taskname'] = 'Sync cohort role assignments';
|
||||
$string['thisuserroles'] = 'Roles assigned relative to this user';
|
||||
$string['privacy:metadata:tool_cohortroles'] = 'The Cohort roles management plugin stores user cohort role mappings.';
|
||||
$string['privacy:metadata:tool_cohortroles:id'] = 'The ID of the cohort role mapping record';
|
||||
$string['privacy:metadata:tool_cohortroles:cohortid'] = 'The ID of the cohort';
|
||||
$string['privacy:metadata:tool_cohortroles:roleid'] = 'The ID of the role';
|
||||
$string['privacy:metadata:tool_cohortroles:userid'] = 'The ID of the user';
|
||||
$string['privacy:metadata:tool_cohortroles:timecreated'] = 'The time when the cohort role mapping was created';
|
||||
$string['privacy:metadata:tool_cohortroles:timemodified'] = 'The time when the cohort role mapping was modified';
|
||||
$string['privacy:metadata:tool_cohortroles:usermodified'] = 'The ID of the user who last modified the cohort role mapping';
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the tool_cohortroles implementation of the privacy API.
|
||||
*
|
||||
* @package tool_cohortroles
|
||||
* @category test
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \tool_cohortroles\api;
|
||||
use \tool_cohortroles\privacy\provider;
|
||||
|
||||
/**
|
||||
* Unit tests for the tool_cohortroles implementation of the privacy API.
|
||||
*
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_testcase {
|
||||
|
||||
/**
|
||||
* Overriding setUp() function to always reset after tests.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::get_contexts_for_userid().
|
||||
*/
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $DB;
|
||||
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 3;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's assigned cohortroles matches 3.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount($nocohortroles, $cohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::export_user_data().
|
||||
*/
|
||||
public function test_export_user_data() {
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 3;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
|
||||
// Retrieve the User's tool_cohortroles data.
|
||||
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', $contextlist->get_contextids());
|
||||
provider::export_user_data($approvedcontextlist);
|
||||
|
||||
// Test the tool_cohortroles data is exported at the User context level.
|
||||
$writer = writer::with_context($context);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 4;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's assigned cohortroles matches 4.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount($nocohortroles, $cohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
|
||||
// Delete all the User's records in mdl_tool_cohortroles table by the specified User context.
|
||||
provider::delete_data_for_all_users_in_context($context);
|
||||
|
||||
// Test the cohort roles records in mdl_tool_cohortroles table is equals zero.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount(0, $cohortroles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 4;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's assigned cohortroles matches 4.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount($nocohortroles, $cohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
|
||||
// Delete all the User's records in mdl_tool_cohortroles table by the specified User approved context list.
|
||||
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', $contextlist->get_contextids());
|
||||
provider::delete_data_for_user($approvedcontextlist);
|
||||
|
||||
// Test the records in mdl_tool_cohortroles table is equals zero.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount(0, $cohortroles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to setup tool_cohortroles records for testing a specific user.
|
||||
*
|
||||
* @param int $userid The ID of the user used for testing.
|
||||
* @param int $nocohortroles The number of tool_cohortroles to create for the user.
|
||||
* @throws \core_competency\invalid_persistent_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
protected function setup_test_scenario_data($userid, $nocohortroles) {
|
||||
$roleid = create_role('Sausage Roll', 'sausageroll', 'mmmm');
|
||||
|
||||
for ($c = 0; $c < $nocohortroles; $c++) {
|
||||
$cohort = $this->getDataGenerator()->create_cohort();
|
||||
|
||||
$params = (object)array(
|
||||
'userid' => $userid,
|
||||
'roleid' => $roleid,
|
||||
'cohortid' => $cohort->id
|
||||
);
|
||||
|
||||
api::create_cohort_role_assignment($params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,8 +25,8 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_cohortroles'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
$plugin->dependencies = array(
|
||||
|
||||
@@ -38,5 +38,8 @@ function xmldb_tool_customlang_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
// Automatically generated Moodle v3.5.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ $string['checkin'] = 'Save strings to language pack';
|
||||
$string['checkout'] = 'Open language pack for editing';
|
||||
$string['checkoutdone'] = 'Language pack loaded';
|
||||
$string['checkoutinprogress'] = 'Loading language pack';
|
||||
$string['confirmcheckin'] = 'You are about to save modifications to your local language pack. This will export the customised strings from the translator into you Moodle data directory and Moodle will start using the modified strings. Press \'Continue\' to proceed with saving.';
|
||||
$string['confirmcheckin'] = 'You are about to save modifications to your local language pack. This will export the customised strings from the translator into your site data directory and your site will start using the modified strings. Press \'Continue\' to proceed with saving.';
|
||||
$string['customlang:edit'] = 'Edit local translation';
|
||||
$string['customlang:view'] = 'View local translation';
|
||||
$string['filter'] = 'Filter strings';
|
||||
|
||||
@@ -134,11 +134,13 @@ class tool_customlang_renderer extends plugin_renderer_base {
|
||||
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'translatorsubmitted', 'value'=>1));
|
||||
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()));
|
||||
$output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'p', 'value'=>$translator->currentpage));
|
||||
$save1 = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'savecontinue', 'value'=>get_string('savecontinue', 'tool_customlang')));
|
||||
$save2 = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'savecheckin', 'value'=>get_string('savecheckin', 'tool_customlang')));
|
||||
$output .= html_writer::tag('fieldset', $save1.$save2, array('class'=>'buttonsbar'));
|
||||
$save1 = html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'savecontinue',
|
||||
'value' => get_string('savecontinue', 'tool_customlang'), 'class' => 'btn btn-secondary'));
|
||||
$save2 = html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'savecheckin',
|
||||
'value' => get_string('savecheckin', 'tool_customlang'), 'class' => 'btn btn-secondary'));
|
||||
$output .= html_writer::tag('fieldset', $save1 . ' ' . $save2, array('class' => 'buttonsbar'));
|
||||
$output .= html_writer::table($table);
|
||||
$output .= html_writer::tag('fieldset', $save1.$save2, array('class'=>'buttonsbar'));
|
||||
$output .= html_writer::tag('fieldset', $save1 . ' ' . $save2, array('class' => 'buttonsbar'));
|
||||
$output .= html_writer::end_tag('div');
|
||||
$output .= html_writer::end_tag('form');
|
||||
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_customlang'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletecategory"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletecategory",component:"tool_dataprivacy",param:i},{key:"deletecategorytext",component:"tool_dataprivacy",param:i}];d.get_strings(j).then(function(d){var g=d[0],i=d[1];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(g),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_category",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-categoryid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletecategory"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletecategory",component:"tool_dataprivacy"},{key:"deletecategorytext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_category",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-categoryid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
|
||||
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]'},i=function(a){d.call(this,a),this.getFooter().find(h.APPROVE_BUTTON).length||b.exception({message:"No approve button found"}),this.getFooter().find(h.DENY_BUTTON).length||b.exception({message:"No deny button found"})};return i.TYPE="tool_dataprivacy-data_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),g=!0),i});
|
||||
define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]',COMPLETE_BUTTON:'[data-action="complete"]'},i=function(a){d.call(this,a)};return i.TYPE="tool_dataprivacy-data_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.COMPLETE_BUTTON,function(b,c){var d=a.Event(f.complete);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),g=!0),i});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define([],function(){return{approve:"tool_dataprivacy-data_request:approve",deny:"tool_dataprivacy-data_request:deny"}});
|
||||
define([],function(){return{approve:"tool_dataprivacy-data_request:approve",deny:"tool_dataprivacy-data_request:deny",complete:"tool_dataprivacy-data_request:complete"}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletepurpose"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletepurpose",component:"tool_dataprivacy",param:i},{key:"deletepurposetext",component:"tool_dataprivacy",param:i}];d.get_strings(j).then(function(d){var g=d[0],i=d[1];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(g),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_purpose",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-purposeid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletepurpose"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletepurpose",component:"tool_dataprivacy"},{key:"deletepurposetext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_purpose",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-purposeid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/form-autocomplete","core/str","core/notification"],function(a,b,c,d){var e={REQUEST_FILTERS:"#request-filters"},f=function(){var f=[{key:"filter",component:"moodle"},{key:"nofiltersapplied",component:"moodle"}];c.get_strings(f).then(function(a){var c=a[0],d=a[1];return b.enhance(e.REQUEST_FILTERS,!1,"",c,!1,!0,d,!0)}).fail(d.exception);var g=a(e.REQUEST_FILTERS).val();a(e.REQUEST_FILTERS).on("change",function(){var b=a(this).val();g.join(",")!==b.join(",")&&(0===b.length&&a("#filters-cleared").val(1),a(this.form).submit())})};return{init:function(){f()}}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a,g){var h=[],j="";switch(a){case i.approve:h=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}],j="tool_dataprivacy_approve_data_request";break;case i.deny:h=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}],j="tool_dataprivacy_deny_data_request"}var k="";d.get_strings(h).then(function(a){k=a[0];var b=a[1];return e.create({title:k,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){return a.setSaveButtonText(k),a.getRoot().on(f.save,function(){var a={requestid:g},d={methodname:j,args:a};b.call([d])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a}).done(function(a){a.show()}).fail(c.exception)}var k={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]'},l=function(){this.registerEvents()};return l.prototype.registerEvents=function(){a(k.VIEW_REQUEST).click(function(d){d.preventDefault();var k=a(this).data("requestid"),l={requestid:k},m={methodname:"tool_dataprivacy_get_data_request",args:l},n=b.call([m]),o="",p=e.types.DEFAULT;a.when(n[0]).then(function(a){return a.result?(2==a.result.status&&(p=h.TYPE),o=a.result.typename,g.render("tool_dataprivacy/request_details",a.result)):(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){return e.create({title:o,body:a,type:p,large:!0}).then(function(a){return a.getRoot().on(i.approve,function(){j(i.approve,k)}),a.getRoot().on(i.deny,function(){j(i.deny,k)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(k.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.approve,c)}),a(k.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.deny,c)})},l});
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a,b){var g=[],h="",j={requestid:b};switch(a){case i.approve:g=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}],h="tool_dataprivacy_approve_data_request";break;case i.deny:g=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}],h="tool_dataprivacy_deny_data_request";break;case i.complete:g=[{key:"markcomplete",component:"tool_dataprivacy"},{key:"confirmcompletion",component:"tool_dataprivacy"}],h="tool_dataprivacy_mark_complete"}var l="";d.get_strings(g).then(function(a){l=a[0];var b=a[1];return e.create({title:l,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){a.setSaveButtonText(l),a.getRoot().on(f.save,function(){k(h,j)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}function k(a,d){var e={methodname:a,args:d};b.call([e])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}var l={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]',MARK_COMPLETE:'[data-action="complete"]'},m=function(){this.registerEvents()};return m.prototype.registerEvents=function(){a(l.VIEW_REQUEST).click(function(d){d.preventDefault();var l=a(this).data("requestid"),m={requestid:l},n={methodname:"tool_dataprivacy_get_data_request",args:m},o=b.call([n]);a.when(o[0]).then(function(a){return a.result?a.result:(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){var b=g.render("tool_dataprivacy/request_details",a),c={approvedeny:a.approvedeny,canmarkcomplete:a.canmarkcomplete};return e.create({title:a.typename,body:b,type:h.TYPE,large:!0,templateContext:c})}).then(function(a){a.getRoot().on(i.approve,function(){j(i.approve,l)}),a.getRoot().on(i.deny,function(){j(i.deny,l)}),a.getRoot().on(i.complete,function(){var a={requestid:l};k("tool_dataprivacy_mark_complete",a)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}),a(l.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.approve,c)}),a(l.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.deny,c)}),a(l.MARK_COMPLETE).click(function(b){b.preventDefault(),j(i.complete,a(this).data("requestid"))})},m});
|
||||
@@ -58,25 +58,28 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'deletecategory',
|
||||
component: 'tool_dataprivacy',
|
||||
param: categoryname
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'deletecategorytext',
|
||||
component: 'tool_dataprivacy',
|
||||
param: categoryname
|
||||
},
|
||||
{
|
||||
key: 'delete'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langStrings) {
|
||||
var title = langStrings[0];
|
||||
var confirmMessage = langStrings[1];
|
||||
var buttonText = langStrings[2];
|
||||
return ModalFactory.create({
|
||||
title: title,
|
||||
body: confirmMessage,
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(title);
|
||||
modal.setSaveButtonText(buttonText);
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
|
||||
@@ -234,6 +234,7 @@ define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/templates'
|
||||
},
|
||||
fail: Notification.exception
|
||||
}]);
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
|
||||
var SELECTORS = {
|
||||
APPROVE_BUTTON: '[data-action="approve"]',
|
||||
DENY_BUTTON: '[data-action="deny"]',
|
||||
COMPLETE_BUTTON: '[data-action="complete"]'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -38,14 +39,6 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
|
||||
*/
|
||||
var ModalDataRequest = function(root) {
|
||||
Modal.call(this, root);
|
||||
|
||||
if (!this.getFooter().find(SELECTORS.APPROVE_BUTTON).length) {
|
||||
Notification.exception({message: 'No approve button found'});
|
||||
}
|
||||
|
||||
if (!this.getFooter().find(SELECTORS.DENY_BUTTON).length) {
|
||||
Notification.exception({message: 'No deny button found'});
|
||||
}
|
||||
};
|
||||
|
||||
ModalDataRequest.TYPE = 'tool_dataprivacy-data_request';
|
||||
@@ -80,6 +73,16 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, function(e, data) {
|
||||
var completeEvent = $.Event(DataPrivacyEvents.complete);
|
||||
this.getRoot().trigger(completeEvent, this);
|
||||
|
||||
if (!completeEvent.isDefaultPrevented()) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
// Automatically register with the modal registry the first time this module is imported so that you can create modals
|
||||
|
||||
@@ -26,5 +26,6 @@ define([], function() {
|
||||
return {
|
||||
approve: 'tool_dataprivacy-data_request:approve',
|
||||
deny: 'tool_dataprivacy-data_request:deny',
|
||||
complete: 'tool_dataprivacy-data_request:complete'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -34,7 +34,6 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
*
|
||||
* @param {object} targetnode The node that we want to expand / collapse
|
||||
* @param {object} thisnode The node that was clicked.
|
||||
* @return {null}
|
||||
*/
|
||||
expandCollapse: function(targetnode, thisnode) {
|
||||
if (targetnode.hasClass('hide')) {
|
||||
@@ -58,7 +57,6 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
* Expand or collapse all nodes on this page.
|
||||
*
|
||||
* @param {string} nextstate The next state to change to.
|
||||
* @return {null}
|
||||
*/
|
||||
expandCollapseAll: function(nextstate) {
|
||||
var currentstate = (nextstate == 'visible') ? 'hide' : 'visible';
|
||||
@@ -75,6 +73,7 @@ define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
|
||||
str.get_string(currentstate, 'tool_dataprivacy').then(function(langString) {
|
||||
$('.tool_dataprivacy-expand-all').html(langString);
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
|
||||
$(':header i.fa').each(function() {
|
||||
|
||||
@@ -58,25 +58,28 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'deletepurpose',
|
||||
component: 'tool_dataprivacy',
|
||||
param: purposename
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'deletepurposetext',
|
||||
component: 'tool_dataprivacy',
|
||||
param: purposename
|
||||
},
|
||||
{
|
||||
key: 'delete'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langStrings) {
|
||||
var title = langStrings[0];
|
||||
var confirmMessage = langStrings[1];
|
||||
var buttonText = langStrings[2];
|
||||
return ModalFactory.create({
|
||||
title: title,
|
||||
body: confirmMessage,
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(title);
|
||||
modal.setSaveButtonText(buttonText);
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* JS module for the data requests filter.
|
||||
*
|
||||
* @module tool_dataprivacy/request_filter
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'], function($, Autocomplete, Str, Notification) {
|
||||
|
||||
/**
|
||||
* Selectors.
|
||||
*
|
||||
* @access private
|
||||
* @type {{REQUEST_FILTERS: string}}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
REQUEST_FILTERS: '#request-filters'
|
||||
};
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*
|
||||
* @method init
|
||||
* @private
|
||||
*/
|
||||
var init = function() {
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'filter',
|
||||
component: 'moodle'
|
||||
},
|
||||
{
|
||||
key: 'nofiltersapplied',
|
||||
component: 'moodle'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langstrings) {
|
||||
var placeholder = langstrings[0];
|
||||
var noSelectionString = langstrings[1];
|
||||
return Autocomplete.enhance(SELECTORS.REQUEST_FILTERS, false, '', placeholder, false, true, noSelectionString, true);
|
||||
}).fail(Notification.exception);
|
||||
|
||||
var last = $(SELECTORS.REQUEST_FILTERS).val();
|
||||
$(SELECTORS.REQUEST_FILTERS).on('change', function() {
|
||||
var current = $(this).val();
|
||||
// Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
|
||||
if (last.join(',') !== current.join(',')) {
|
||||
// If we're submitting without filters, set the hidden input 'filters-cleared' to 1.
|
||||
if (current.length === 0) {
|
||||
$('#filters-cleared').val(1);
|
||||
}
|
||||
$(this.form).submit();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return /** @alias module:core/form-autocomplete */ {
|
||||
/**
|
||||
* Initialise the unified user filter.
|
||||
*
|
||||
* @method init
|
||||
*/
|
||||
init: function() {
|
||||
init();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -39,11 +39,13 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
* @type {{APPROVE_REQUEST: string}}
|
||||
* @type {{DENY_REQUEST: string}}
|
||||
* @type {{VIEW_REQUEST: string}}
|
||||
* @type {{MARK_COMPLETE: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
APPROVE_REQUEST: '[data-action="approve"]',
|
||||
DENY_REQUEST: '[data-action="deny"]',
|
||||
VIEW_REQUEST: '[data-action="view"]'
|
||||
VIEW_REQUEST: '[data-action="view"]',
|
||||
MARK_COMPLETE: '[data-action="complete"]'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -73,16 +75,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
};
|
||||
|
||||
var promises = Ajax.call([request]);
|
||||
var modalTitle = '';
|
||||
var modalType = ModalFactory.types.DEFAULT;
|
||||
$.when(promises[0]).then(function(data) {
|
||||
if (data.result) {
|
||||
// Check if the status is awaiting approval.
|
||||
if (data.result.status == 2) {
|
||||
modalType = ModalDataRequest.TYPE;
|
||||
}
|
||||
modalTitle = data.result.typename;
|
||||
return Templates.render('tool_dataprivacy/request_details', data.result);
|
||||
return data.result;
|
||||
}
|
||||
// Fail.
|
||||
Notification.addNotification({
|
||||
@@ -91,35 +86,51 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
});
|
||||
return false;
|
||||
|
||||
}).then(function(html) {
|
||||
}).then(function(data) {
|
||||
var body = Templates.render('tool_dataprivacy/request_details', data);
|
||||
var templateContext = {
|
||||
approvedeny: data.approvedeny,
|
||||
canmarkcomplete: data.canmarkcomplete
|
||||
};
|
||||
return ModalFactory.create({
|
||||
title: modalTitle,
|
||||
body: html,
|
||||
type: modalType,
|
||||
large: true
|
||||
}).then(function(modal) {
|
||||
// Handle approve event.
|
||||
modal.getRoot().on(DataPrivacyEvents.approve, function() {
|
||||
showConfirmation(DataPrivacyEvents.approve, requestId);
|
||||
});
|
||||
|
||||
// Handle deny event.
|
||||
modal.getRoot().on(DataPrivacyEvents.deny, function() {
|
||||
showConfirmation(DataPrivacyEvents.deny, requestId);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
return modal;
|
||||
title: data.typename,
|
||||
body: body,
|
||||
type: ModalDataRequest.TYPE,
|
||||
large: true,
|
||||
templateContext: templateContext
|
||||
});
|
||||
}).done(function(modal) {
|
||||
|
||||
}).then(function(modal) {
|
||||
// Handle approve event.
|
||||
modal.getRoot().on(DataPrivacyEvents.approve, function() {
|
||||
showConfirmation(DataPrivacyEvents.approve, requestId);
|
||||
});
|
||||
|
||||
// Handle deny event.
|
||||
modal.getRoot().on(DataPrivacyEvents.deny, function() {
|
||||
showConfirmation(DataPrivacyEvents.deny, requestId);
|
||||
});
|
||||
|
||||
// Handle send event.
|
||||
modal.getRoot().on(DataPrivacyEvents.complete, function() {
|
||||
var params = {
|
||||
'requestid': requestId
|
||||
};
|
||||
handleSave('tool_dataprivacy_mark_complete', params);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Show the modal!
|
||||
modal.show();
|
||||
}).fail(Notification.exception);
|
||||
|
||||
return;
|
||||
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
|
||||
$(ACTIONS.APPROVE_REQUEST).click(function(e) {
|
||||
@@ -135,6 +146,11 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
var requestId = $(this).data('requestid');
|
||||
showConfirmation(DataPrivacyEvents.deny, requestId);
|
||||
});
|
||||
|
||||
$(ACTIONS.MARK_COMPLETE).click(function(e) {
|
||||
e.preventDefault();
|
||||
showConfirmation(DataPrivacyEvents.complete, $(this).data('requestid'));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -146,6 +162,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
function showConfirmation(action, requestId) {
|
||||
var keys = [];
|
||||
var wsfunction = '';
|
||||
var params = {
|
||||
'requestid': requestId
|
||||
};
|
||||
switch (action) {
|
||||
case DataPrivacyEvents.approve:
|
||||
keys = [
|
||||
@@ -173,6 +192,19 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
];
|
||||
wsfunction = 'tool_dataprivacy_deny_data_request';
|
||||
break;
|
||||
case DataPrivacyEvents.complete:
|
||||
keys = [
|
||||
{
|
||||
key: 'markcomplete',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'confirmcompletion',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
wsfunction = 'tool_dataprivacy_mark_complete';
|
||||
break;
|
||||
}
|
||||
|
||||
var modalTitle = '';
|
||||
@@ -189,26 +221,7 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
// Confirm the request.
|
||||
var params = {
|
||||
'requestid': requestId
|
||||
};
|
||||
|
||||
var request = {
|
||||
methodname: wsfunction,
|
||||
args: params
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).fail(Notification.exception);
|
||||
handleSave(wsfunction, params);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
@@ -217,9 +230,39 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
return modal;
|
||||
}).done(function(modal) {
|
||||
modal.show();
|
||||
|
||||
return;
|
||||
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a web service function and reloads the page on success and shows a notification.
|
||||
* Displays an error notification, otherwise.
|
||||
*
|
||||
* @param {String} wsfunction The web service function to call.
|
||||
* @param {Object} params The parameters for the web service functoon.
|
||||
*/
|
||||
function handleSave(wsfunction, params) {
|
||||
// Confirm the request.
|
||||
var request = {
|
||||
methodname: wsfunction,
|
||||
args: params
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
// On success, reload the page so that the data request table will be updated.
|
||||
// TODO: Probably in the future, better to reload the table or the target data request via AJAX.
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Add the notification.
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).fail(Notification.exception);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$url = new moodle_url("/admin/tool/dataprivacy/categories.php");
|
||||
$title = get_string('editcategories', 'tool_dataprivacy');
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use coding_exception;
|
||||
use context_course;
|
||||
use context_system;
|
||||
use core\invalid_persistent_exception;
|
||||
use core\message\message;
|
||||
@@ -37,6 +38,7 @@ use moodle_url;
|
||||
use required_capability_exception;
|
||||
use stdClass;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
use tool_dataprivacy\local\helper;
|
||||
use tool_dataprivacy\task\initiate_data_request_task;
|
||||
use tool_dataprivacy\task\process_data_request_task;
|
||||
|
||||
@@ -74,7 +76,7 @@ class api {
|
||||
/** The request is now being processed. */
|
||||
const DATAREQUEST_STATUS_PROCESSING = 4;
|
||||
|
||||
/** Data request completed. */
|
||||
/** Information/other request completed. */
|
||||
const DATAREQUEST_STATUS_COMPLETE = 5;
|
||||
|
||||
/** Data request cancelled by the user. */
|
||||
@@ -83,6 +85,15 @@ class api {
|
||||
/** Data request rejected by the DPO. */
|
||||
const DATAREQUEST_STATUS_REJECTED = 7;
|
||||
|
||||
/** Data request download ready. */
|
||||
const DATAREQUEST_STATUS_DOWNLOAD_READY = 8;
|
||||
|
||||
/** Data request expired. */
|
||||
const DATAREQUEST_STATUS_EXPIRED = 9;
|
||||
|
||||
/** Data delete request completed, account is removed. */
|
||||
const DATAREQUEST_STATUS_DELETED = 10;
|
||||
|
||||
/**
|
||||
* Determines whether the user can contact the site's Data Protection Officer via Moodle.
|
||||
*
|
||||
@@ -125,6 +136,25 @@ class api {
|
||||
require_capability('tool/dataprivacy:managedataregistry', $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the role shortnames of Data Protection Officer roles.
|
||||
*
|
||||
* @return array An array of the DPO role shortnames
|
||||
*/
|
||||
public static function get_dpo_role_names() : array {
|
||||
global $DB;
|
||||
|
||||
$dporoleids = explode(',', str_replace(' ', '', get_config('tool_dataprivacy', 'dporoles')));
|
||||
$dponames = array();
|
||||
|
||||
if (!empty($dporoleids)) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($dporoleids);
|
||||
$dponames = $DB->get_fieldset_select('role', 'shortname', "id {$insql}", $inparams);
|
||||
}
|
||||
|
||||
return $dponames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of users with the Data Protection Officer role.
|
||||
*
|
||||
@@ -186,8 +216,25 @@ class api {
|
||||
$datarequest = new data_request();
|
||||
// The user the request is being made for.
|
||||
$datarequest->set('userid', $foruser);
|
||||
|
||||
$requestinguser = $USER->id;
|
||||
// Check when the user is making a request on behalf of another.
|
||||
if ($requestinguser != $foruser) {
|
||||
if (self::is_site_dpo($requestinguser)) {
|
||||
// The user making the request is a DPO. Should be fine.
|
||||
$datarequest->set('dpo', $requestinguser);
|
||||
} else {
|
||||
// If not a DPO, only users with the capability to make data requests for the user should be allowed.
|
||||
// (e.g. users with the Parent role, etc).
|
||||
if (!self::can_create_data_request_for_user($foruser)) {
|
||||
$forusercontext = \context_user::instance($foruser);
|
||||
throw new required_capability_exception($forusercontext,
|
||||
'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
// The user making the request.
|
||||
$datarequest->set('requestedby', $USER->id);
|
||||
$datarequest->set('requestedby', $requestinguser);
|
||||
// Set status.
|
||||
$datarequest->set('status', self::DATAREQUEST_STATUS_PENDING);
|
||||
// Set request type.
|
||||
@@ -214,30 +261,149 @@ class api {
|
||||
* (e.g. Users with the Data Protection Officer roles)
|
||||
*
|
||||
* @param int $userid The User ID.
|
||||
* @param int[] $statuses The status filters.
|
||||
* @param int[] $types The request type filters.
|
||||
* @param string $sort The order by clause.
|
||||
* @param int $offset Amount of records to skip.
|
||||
* @param int $limit Amount of records to fetch.
|
||||
* @return data_request[]
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function get_data_requests($userid = 0) {
|
||||
global $USER;
|
||||
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $sort = '', $offset = 0, $limit = 0) {
|
||||
global $DB, $USER;
|
||||
$results = [];
|
||||
$sqlparams = [];
|
||||
$sqlconditions = [];
|
||||
|
||||
// Set default sort.
|
||||
if (empty($sort)) {
|
||||
$sort = 'status ASC, timemodified ASC';
|
||||
}
|
||||
|
||||
// Set status filters.
|
||||
if (!empty($statuses)) {
|
||||
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "status $statusinsql";
|
||||
}
|
||||
|
||||
// Set request type filter.
|
||||
if (!empty($types)) {
|
||||
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "type $typeinsql";
|
||||
$sqlparams = array_merge($sqlparams, $typeparams);
|
||||
}
|
||||
|
||||
if ($userid) {
|
||||
// Get the data requests for the user or data requests made by the user.
|
||||
$select = "userid = :userid OR requestedby = :requestedby";
|
||||
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'requestedby' => $userid
|
||||
];
|
||||
$results = data_request::get_records_select($select, $params, 'status DESC, timemodified DESC');
|
||||
|
||||
// Build a list of user IDs that the user is allowed to make data requests for.
|
||||
// Of course, the user should be included in this list.
|
||||
$alloweduserids = [$userid];
|
||||
// Get any users that the user can make data requests for.
|
||||
if ($children = helper::get_children_of_user($userid)) {
|
||||
// Get the list of user IDs of the children and merge to the allowed user IDs.
|
||||
$alloweduserids = array_merge($alloweduserids, array_keys($children));
|
||||
}
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] .= "userid $insql";
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$params = array_merge($params, $inparams, $sqlparams);
|
||||
|
||||
$results = data_request::get_records_select($select, $params, $sort, '*', $offset, $limit);
|
||||
} else {
|
||||
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
|
||||
if (self::is_site_dpo($USER->id)) {
|
||||
$results = data_request::get_records(null, 'status DESC, timemodified DESC', '');
|
||||
if (!empty($sqlconditions)) {
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$results = data_request::get_records_select($select, $sqlparams, $sort, '*', $offset, $limit);
|
||||
} else {
|
||||
$results = data_request::get_records(null, $sort, '', $offset, $limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If any are due to expire, expire them and re-fetch updated data.
|
||||
if (empty($statuses)
|
||||
|| in_array(self::DATAREQUEST_STATUS_DOWNLOAD_READY, $statuses)
|
||||
|| in_array(self::DATAREQUEST_STATUS_EXPIRED, $statuses)) {
|
||||
$expiredrequests = data_request::get_expired_requests($userid);
|
||||
|
||||
if (!empty($expiredrequests)) {
|
||||
data_request::expire($expiredrequests);
|
||||
$results = self::get_data_requests($userid, $statuses, $types, $sort, $offset, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the count of data request records based on the given parameters.
|
||||
*
|
||||
* @param int $userid The User ID.
|
||||
* @param int[] $statuses The status filters.
|
||||
* @param int[] $types The request type filters.
|
||||
* @return int
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function get_data_requests_count($userid = 0, $statuses = [], $types = []) {
|
||||
global $DB, $USER;
|
||||
$count = 0;
|
||||
$sqlparams = [];
|
||||
$sqlconditions = [];
|
||||
if (!empty($statuses)) {
|
||||
list($statusinsql, $sqlparams) = $DB->get_in_or_equal($statuses, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "status $statusinsql";
|
||||
}
|
||||
if (!empty($types)) {
|
||||
list($typeinsql, $typeparams) = $DB->get_in_or_equal($types, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] = "type $typeinsql";
|
||||
$sqlparams = array_merge($sqlparams, $typeparams);
|
||||
}
|
||||
if ($userid) {
|
||||
// Get the data requests for the user or data requests made by the user.
|
||||
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'requestedby' => $userid
|
||||
];
|
||||
|
||||
// Build a list of user IDs that the user is allowed to make data requests for.
|
||||
// Of course, the user should be included in this list.
|
||||
$alloweduserids = [$userid];
|
||||
// Get any users that the user can make data requests for.
|
||||
if ($children = helper::get_children_of_user($userid)) {
|
||||
// Get the list of user IDs of the children and merge to the allowed user IDs.
|
||||
$alloweduserids = array_merge($alloweduserids, array_keys($children));
|
||||
}
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
|
||||
$sqlconditions[] .= "userid $insql";
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$params = array_merge($params, $inparams, $sqlparams);
|
||||
|
||||
$count = data_request::count_records_select($select, $params);
|
||||
} else {
|
||||
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
|
||||
if (self::is_site_dpo($USER->id)) {
|
||||
if (!empty($sqlconditions)) {
|
||||
$select = implode(' AND ', $sqlconditions);
|
||||
$count = data_request::count_records_select($select, $sqlparams);
|
||||
} else {
|
||||
$count = data_request::count_records();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there is already an existing pending/in-progress data request for a user for a given request type.
|
||||
*
|
||||
@@ -255,6 +421,9 @@ class api {
|
||||
self::DATAREQUEST_STATUS_COMPLETE,
|
||||
self::DATAREQUEST_STATUS_CANCELLED,
|
||||
self::DATAREQUEST_STATUS_REJECTED,
|
||||
self::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
self::DATAREQUEST_STATUS_EXPIRED,
|
||||
self::DATAREQUEST_STATUS_DELETED,
|
||||
];
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED);
|
||||
$select = 'type = :type AND userid = :userid AND status NOT ' . $insql;
|
||||
@@ -278,6 +447,9 @@ class api {
|
||||
self::DATAREQUEST_STATUS_COMPLETE,
|
||||
self::DATAREQUEST_STATUS_CANCELLED,
|
||||
self::DATAREQUEST_STATUS_REJECTED,
|
||||
self::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
self::DATAREQUEST_STATUS_EXPIRED,
|
||||
self::DATAREQUEST_STATUS_DELETED,
|
||||
];
|
||||
|
||||
return !in_array($status, $finalstatuses);
|
||||
@@ -289,17 +461,34 @@ class api {
|
||||
* @param int $requestid The request identifier.
|
||||
* @param int $status The request status.
|
||||
* @param int $dpoid The user ID of the Data Protection Officer
|
||||
* @param string $comment The comment about the status update.
|
||||
* @return bool
|
||||
* @throws invalid_persistent_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function update_request_status($requestid, $status, $dpoid = 0) {
|
||||
public static function update_request_status($requestid, $status, $dpoid = 0, $comment = '') {
|
||||
// Update the request.
|
||||
$datarequest = new data_request($requestid);
|
||||
$datarequest->set('status', $status);
|
||||
if ($dpoid) {
|
||||
$datarequest->set('dpo', $dpoid);
|
||||
}
|
||||
// Update the comment if necessary.
|
||||
if (!empty(trim($comment))) {
|
||||
$params = [
|
||||
'date' => userdate(time()),
|
||||
'comment' => $comment
|
||||
];
|
||||
$commenttosave = get_string('datecomment', 'tool_dataprivacy', $params);
|
||||
// Check if there's an existing DPO comment.
|
||||
$currentcomment = trim($datarequest->get('dpocomment'));
|
||||
if ($currentcomment) {
|
||||
// Append the new comment to the current comment and give them 1 line space in between.
|
||||
$commenttosave = $currentcomment . PHP_EOL . PHP_EOL . $commenttosave;
|
||||
}
|
||||
$datarequest->set('dpocomment', $commenttosave);
|
||||
}
|
||||
|
||||
return $datarequest->update();
|
||||
}
|
||||
|
||||
@@ -394,7 +583,6 @@ class api {
|
||||
* @param data_request $request The data request
|
||||
* @return int|false
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function notify_dpo($dpo, data_request $request) {
|
||||
@@ -417,7 +605,7 @@ class api {
|
||||
$message->courseid = $SITE->id;
|
||||
$message->component = 'tool_dataprivacy';
|
||||
$message->name = 'contactdataprotectionofficer';
|
||||
$message->userfrom = $requestedby;
|
||||
$message->userfrom = $requestedby->id;
|
||||
$message->replyto = $requestedby->email;
|
||||
$message->replytoname = $requestedby->fullname;
|
||||
$message->subject = $subject;
|
||||
@@ -453,6 +641,67 @@ class api {
|
||||
return message_send($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a non-DPO user can make a data request for another user.
|
||||
*
|
||||
* @param int $user The user ID of the target user.
|
||||
* @param int $requester The user ID of the user making the request.
|
||||
* @return bool
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function can_create_data_request_for_user($user, $requester = null) {
|
||||
$usercontext = \context_user::instance($user);
|
||||
return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a user can download a data request.
|
||||
*
|
||||
* @param int $userid Target user id (subject of data request)
|
||||
* @param int $requesterid Requester user id (person who requsted it)
|
||||
* @param int|null $downloaderid Person who wants to download user id (default current)
|
||||
* @return bool
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function can_download_data_request_for_user($userid, $requesterid, $downloaderid = null) {
|
||||
global $USER;
|
||||
|
||||
if (!$downloaderid) {
|
||||
$downloaderid = $USER->id;
|
||||
}
|
||||
|
||||
$usercontext = \context_user::instance($userid);
|
||||
// If it's your own and you have the right capability, you can download it.
|
||||
if ($userid == $downloaderid && has_capability('tool/dataprivacy:downloadownrequest', $usercontext, $downloaderid)) {
|
||||
return true;
|
||||
}
|
||||
// If you can download anyone's in that context, you can download it.
|
||||
if (has_capability('tool/dataprivacy:downloadallrequests', $usercontext, $downloaderid)) {
|
||||
return true;
|
||||
}
|
||||
// If you can have the 'child access' ability to request in that context, and you are the one
|
||||
// who requested it, then you can download it.
|
||||
if ($requesterid == $downloaderid && self::can_create_data_request_for_user($userid, $requesterid)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an action menu link to download a data request.
|
||||
*
|
||||
* @param \context_user $usercontext User context (of user who the data is for)
|
||||
* @param int $requestid Request id
|
||||
* @return \action_menu_link_secondary Action menu link
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function get_download_link(\context_user $usercontext, $requestid) {
|
||||
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id,
|
||||
'tool_dataprivacy', 'export', $requestid, '/', 'export.zip', true);
|
||||
$downloadtext = get_string('download', 'tool_dataprivacy');
|
||||
return new \action_menu_link_secondary($downloadurl, null, $downloadtext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data purpose.
|
||||
*
|
||||
@@ -477,6 +726,10 @@ class api {
|
||||
public static function update_purpose(stdClass $record) {
|
||||
self::check_can_manage_data_registry();
|
||||
|
||||
if (!isset($record->sensitivedatareasons)) {
|
||||
$record->sensitivedatareasons = '';
|
||||
}
|
||||
|
||||
$purpose = new purpose($record->id);
|
||||
$purpose->from_record($record);
|
||||
|
||||
@@ -764,6 +1017,7 @@ class api {
|
||||
* @param int $status the status to set the contexts to.
|
||||
*/
|
||||
public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
|
||||
$request = new data_request($requestid);
|
||||
foreach ($clcollection as $contextlist) {
|
||||
// Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
|
||||
$clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
|
||||
@@ -772,6 +1026,12 @@ class api {
|
||||
|
||||
// Store the associated contexts in the contextlist.
|
||||
foreach ($contextlist->get_contextids() as $contextid) {
|
||||
if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
if (($purpose = static::get_effective_context_purpose($context)) && !empty($purpose->get('protected'))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$context = new contextlist_context();
|
||||
$context->set('contextid', $contextid)
|
||||
->set('contextlistid', $contextlistid)
|
||||
@@ -868,6 +1128,7 @@ class api {
|
||||
}
|
||||
$contexts = [];
|
||||
}
|
||||
|
||||
$contexts[] = $record->contextid;
|
||||
$lastcomponent = $record->component;
|
||||
}
|
||||
|
||||
@@ -289,7 +289,8 @@ class data_registry {
|
||||
* @param int $forcedcategoryvalue Use this value as if this was this context level category.
|
||||
* @return int[]
|
||||
*/
|
||||
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false, $forcedcategoryvalue = false) {
|
||||
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
|
||||
$forcedcategoryvalue = false) {
|
||||
|
||||
list($purposeid, $categoryid) = self::get_defaults($contextlevel);
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ class data_request extends persistent {
|
||||
api::DATAREQUEST_STATUS_COMPLETE,
|
||||
api::DATAREQUEST_STATUS_CANCELLED,
|
||||
api::DATAREQUEST_STATUS_REJECTED,
|
||||
api::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
api::DATAREQUEST_STATUS_EXPIRED,
|
||||
api::DATAREQUEST_STATUS_DELETED,
|
||||
],
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
@@ -110,4 +113,101 @@ class data_request extends persistent {
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a completed data export request has expired.
|
||||
* The response will be valid regardless of the expiry scheduled task having run.
|
||||
*
|
||||
* @param data_request $request the data request object whose expiry will be checked.
|
||||
* @return bool true if the request has expired.
|
||||
*/
|
||||
public static function is_expired(data_request $request) {
|
||||
$result = false;
|
||||
|
||||
// Only export requests expire.
|
||||
if ($request->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
|
||||
switch ($request->get('status')) {
|
||||
// Expired requests are obviously expired.
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$result = true;
|
||||
break;
|
||||
// Complete requests are expired if the expiry time has elapsed.
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
|
||||
$result = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Fetch completed data requests which are due to expire.
|
||||
*
|
||||
* @param int $userid Optional user ID to filter by.
|
||||
*
|
||||
* @return array Details of completed requests which are due to expire.
|
||||
*/
|
||||
public static function get_expired_requests($userid = 0) {
|
||||
global $DB;
|
||||
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
$expirytime = strtotime("-{$expiryseconds} second");
|
||||
$table = self::TABLE;
|
||||
$sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
|
||||
$params = array(
|
||||
'export_type' => api::DATAREQUEST_TYPE_EXPORT,
|
||||
'completestatus' => api::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
'expirytime' => $expirytime,
|
||||
);
|
||||
$sort = 'id';
|
||||
$fields = 'id, userid';
|
||||
|
||||
// Filter by user ID if specified.
|
||||
if ($userid > 0) {
|
||||
$sqlwhere .= ' AND (userid = :userid OR requestedby = :requestedby)';
|
||||
$params['userid'] = $userid;
|
||||
$params['requestedby'] = $userid;
|
||||
}
|
||||
|
||||
return $DB->get_records_select_menu($table, $sqlwhere, $params, $sort, $fields, 0, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a given set of data requests.
|
||||
* Update request status and delete the files.
|
||||
*
|
||||
* @param array $expiredrequests [requestid => userid]
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function expire($expiredrequests) {
|
||||
global $DB;
|
||||
|
||||
$ids = array_keys($expiredrequests);
|
||||
|
||||
if (count($ids) > 0) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($ids);
|
||||
$initialparams = array(api::DATAREQUEST_STATUS_EXPIRED, time());
|
||||
$params = array_merge($initialparams, $inparams);
|
||||
|
||||
$update = "UPDATE {" . self::TABLE . "}
|
||||
SET status = ?, timemodified = ?
|
||||
WHERE id $insql";
|
||||
|
||||
if ($DB->execute($update, $params)) {
|
||||
$fs = get_file_storage();
|
||||
|
||||
foreach ($expiredrequests as $id => $userid) {
|
||||
$usercontext = \context_user::instance($userid);
|
||||
$fs->delete_area_files($usercontext->id, 'tool_dataprivacy', 'export', $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,7 @@
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\purpose;
|
||||
use tool_dataprivacy\context_instance;
|
||||
use tool_dataprivacy\data_registry;
|
||||
use core_privacy\manager;
|
||||
use tool_dataprivacy\expired_context;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
@@ -90,7 +87,8 @@ abstract class expired_contexts_manager {
|
||||
return $numprocessed;
|
||||
}
|
||||
|
||||
$privacymanager = new \core_privacy\manager();
|
||||
$privacymanager = new manager();
|
||||
$privacymanager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
foreach ($this->get_context_levels() as $level) {
|
||||
|
||||
@@ -117,11 +115,11 @@ abstract class expired_contexts_manager {
|
||||
/**
|
||||
* Deletes user data from the provided context.
|
||||
*
|
||||
* @param \core_privacy\manager $privacymanager
|
||||
* @param \tool_dataprivacy\expired_context $expiredctx
|
||||
* @param manager $privacymanager
|
||||
* @param expired_context $expiredctx
|
||||
* @return \context|false
|
||||
*/
|
||||
protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
|
||||
protected function delete_expired_context(manager $privacymanager, expired_context $expiredctx) {
|
||||
|
||||
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
|
||||
if (!$context) {
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use tool_dataprivacy\purpose;
|
||||
use tool_dataprivacy\context_instance;
|
||||
use core_privacy\manager;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -110,17 +109,40 @@ class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
|
||||
*
|
||||
* Overwritten to delete the user.
|
||||
*
|
||||
* @param \core_privacy\manager $privacymanager
|
||||
* @param \tool_dataprivacy\expired_context $expiredctx
|
||||
* @param manager $privacymanager
|
||||
* @param expired_context $expiredctx
|
||||
* @return \context|false
|
||||
*/
|
||||
protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
|
||||
if (!$context = parent::delete_expired_context($privacymanager, $expiredctx)) {
|
||||
protected function delete_expired_context(manager $privacymanager, expired_context $expiredctx) {
|
||||
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
|
||||
if (!$context) {
|
||||
api::delete_expired_context($expiredctx->get('contextid'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the user.
|
||||
if (!PHPUNIT_TEST) {
|
||||
mtrace('Deleting context ' . $context->id . ' - ' .
|
||||
shorten_text($context->get_context_name(true, true)));
|
||||
}
|
||||
|
||||
// To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
|
||||
// contexts for the user that may still exist and call delete_data_for_user().
|
||||
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
|
||||
$approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
||||
$contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
|
||||
|
||||
foreach ($contextlistcollection as $contextlist) {
|
||||
$approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
|
||||
$user,
|
||||
$contextlist->get_component(),
|
||||
$contextlist->get_contextids()
|
||||
));
|
||||
}
|
||||
|
||||
$privacymanager->delete_data_for_user($approvedlistcollection);
|
||||
api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
|
||||
|
||||
// Delete the user.
|
||||
delete_user($user);
|
||||
|
||||
return $context;
|
||||
|
||||
@@ -31,6 +31,7 @@ use context_helper;
|
||||
use context_system;
|
||||
use context_user;
|
||||
use core\invalid_persistent_exception;
|
||||
use core\notification;
|
||||
use core_user;
|
||||
use dml_exception;
|
||||
use external_api;
|
||||
@@ -96,8 +97,8 @@ class external extends external_api {
|
||||
self::validate_context($context);
|
||||
|
||||
// Ensure the request exists.
|
||||
$select = 'id = :id AND requestedby = :requestedby';
|
||||
$params = ['id' => $requestid, 'requestedby' => $USER->id];
|
||||
$select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
|
||||
$params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
|
||||
$requestexists = data_request::record_exists_select($select, $params);
|
||||
|
||||
$result = false;
|
||||
@@ -144,7 +145,7 @@ class external extends external_api {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deny a data request.
|
||||
* Make a general enquiry to a DPO.
|
||||
*
|
||||
* @since Moodle 3.5
|
||||
* @param string $message The message to be sent to the DPO.
|
||||
@@ -210,7 +211,7 @@ class external extends external_api {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter description for deny_data_request().
|
||||
* Parameter description for contact_dpo().
|
||||
*
|
||||
* @since Moodle 3.5
|
||||
* @return external_description
|
||||
@@ -222,6 +223,70 @@ class external extends external_api {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter description for mark_complete().
|
||||
*
|
||||
* @since Moodle 3.5.2
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function mark_complete_parameters() {
|
||||
return new external_function_parameters([
|
||||
'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a user's general enquiry's status as complete.
|
||||
*
|
||||
* @since Moodle 3.5.2
|
||||
* @param int $requestid The request ID of the general enquiry.
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
* @throws invalid_parameter_exception
|
||||
* @throws invalid_persistent_exception
|
||||
* @throws restricted_context_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function mark_complete($requestid) {
|
||||
global $USER;
|
||||
|
||||
$warnings = [];
|
||||
$params = external_api::validate_parameters(self::mark_complete_parameters(), [
|
||||
'requestid' => $requestid,
|
||||
]);
|
||||
$requestid = $params['requestid'];
|
||||
|
||||
// Validate context.
|
||||
$context = context_system::instance();
|
||||
self::validate_context($context);
|
||||
|
||||
$message = get_string('markedcomplete', 'tool_dataprivacy');
|
||||
// Update the data request record.
|
||||
if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
|
||||
// Add notification in the session to be shown when the page is reloaded on the JS side.
|
||||
notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => $result,
|
||||
'warnings' => $warnings
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter description for mark_complete().
|
||||
*
|
||||
* @since Moodle 3.5.2
|
||||
* @return external_description
|
||||
*/
|
||||
public static function mark_complete_returns() {
|
||||
return new external_single_structure([
|
||||
'result' => new external_value(PARAM_BOOL, 'The processing result'),
|
||||
'warnings' => new external_warnings()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter description for get_data_request().
|
||||
*
|
||||
@@ -258,9 +323,9 @@ class external extends external_api {
|
||||
// Validate context.
|
||||
$context = context_system::instance();
|
||||
self::validate_context($context);
|
||||
$requestpersistent = new data_request($requestid);
|
||||
require_capability('tool/dataprivacy:managedatarequests', $context);
|
||||
|
||||
$requestpersistent = new data_request($requestid);
|
||||
$exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
|
||||
$renderer = $PAGE->get_renderer('tool_dataprivacy');
|
||||
$result = $exporter->export($renderer);
|
||||
@@ -326,6 +391,9 @@ class external extends external_api {
|
||||
$result = false;
|
||||
if ($requestexists) {
|
||||
$result = api::approve_data_request($requestid);
|
||||
|
||||
// Add notification in the session to be shown when the page is reloaded on the JS side.
|
||||
notification::success(get_string('requestapproved', 'tool_dataprivacy'));
|
||||
} else {
|
||||
$warnings[] = [
|
||||
'item' => $requestid,
|
||||
@@ -395,6 +463,9 @@ class external extends external_api {
|
||||
$result = false;
|
||||
if ($requestexists) {
|
||||
$result = api::deny_data_request($requestid);
|
||||
|
||||
// Add notification in the session to be shown when the page is reloaded on the JS side.
|
||||
notification::success(get_string('requestdenied', 'tool_dataprivacy'));
|
||||
} else {
|
||||
$warnings[] = [
|
||||
'item' => $requestid,
|
||||
|
||||
@@ -102,6 +102,16 @@ class data_request_exporter extends persistent_exporter {
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
'approvedeny' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
'canmarkcomplete' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -140,14 +150,19 @@ class data_request_exporter extends persistent_exporter {
|
||||
|
||||
$values['messagehtml'] = text_to_html($this->persistent->get('comments'));
|
||||
|
||||
$values['typename'] = helper::get_request_type_string($this->persistent->get('type'));
|
||||
$values['typenameshort'] = helper::get_shortened_request_type_string($this->persistent->get('type'));
|
||||
$requesttype = $this->persistent->get('type');
|
||||
$values['typename'] = helper::get_request_type_string($requesttype);
|
||||
$values['typenameshort'] = helper::get_shortened_request_type_string($requesttype);
|
||||
|
||||
$values['canreview'] = false;
|
||||
$values['approvedeny'] = false;
|
||||
$values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
|
||||
|
||||
switch ($this->persistent->get('status')) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
$values['statuslabelclass'] = 'label-default';
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
// Request can be manually completed for general enquiry requests.
|
||||
$values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_PREPROCESSING:
|
||||
$values['statuslabelclass'] = 'label-default';
|
||||
@@ -156,6 +171,8 @@ class data_request_exporter extends persistent_exporter {
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
// DPO can review the request once it's ready.
|
||||
$values['canreview'] = true;
|
||||
// Whether the DPO can approve or deny the request.
|
||||
$values['approvedeny'] = in_array($requesttype, [api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_TYPE_DELETE]);
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_APPROVED:
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
@@ -164,6 +181,8 @@ class data_request_exporter extends persistent_exporter {
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_COMPLETE:
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
case api::DATAREQUEST_STATUS_DELETED:
|
||||
$values['statuslabelclass'] = 'label-success';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
@@ -172,6 +191,9 @@ class data_request_exporter extends persistent_exporter {
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
$values['statuslabelclass'] = 'label-important';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$values['statuslabelclass'] = 'label-default';
|
||||
break;
|
||||
}
|
||||
|
||||
return $values;
|
||||
|
||||
@@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_registry;
|
||||
use tool_dataprivacy\purpose;
|
||||
|
||||
/**
|
||||
* Context instance data form.
|
||||
@@ -55,6 +56,7 @@ class context_instance extends \core\form\persistent {
|
||||
$subjectscope = get_string('noassignedroles', 'tool_dataprivacy');
|
||||
}
|
||||
$this->_form->addElement('static', 'subjectscope', get_string('subjectscope', 'tool_dataprivacy'), $subjectscope);
|
||||
$this->_form->addHelpButton('subjectscope', 'subjectscope', 'tool_dataprivacy');
|
||||
|
||||
$this->add_purpose_category($this->_customdata['context']->contextlevel);
|
||||
|
||||
@@ -79,6 +81,7 @@ class context_instance extends \core\form\persistent {
|
||||
$addcategory = $mform->createElement('button', 'addcategory', $addcategorytext, ['data-add-element' => 'category']);
|
||||
$mform->addElement('group', 'categorygroup', get_string('category', 'tool_dataprivacy'),
|
||||
[$categoryselect, $addcategory], null, false);
|
||||
$mform->addHelpButton('categorygroup', 'category', 'tool_dataprivacy');
|
||||
$mform->setType('categoryid', PARAM_INT);
|
||||
$mform->setDefault('categoryid', 0);
|
||||
|
||||
@@ -87,12 +90,14 @@ class context_instance extends \core\form\persistent {
|
||||
$addpurpose = $mform->createElement('button', 'addpurpose', $addpurposetext, ['data-add-element' => 'purpose']);
|
||||
$mform->addElement('group', 'purposegroup', get_string('purpose', 'tool_dataprivacy'),
|
||||
[$purposeselect, $addpurpose], null, false);
|
||||
$mform->addHelpButton('purposegroup', 'purpose', 'tool_dataprivacy');
|
||||
$mform->setType('purposeid', PARAM_INT);
|
||||
$mform->setDefault('purposeid', 0);
|
||||
|
||||
if (!empty($this->_customdata['currentretentionperiod'])) {
|
||||
$mform->addElement('static', 'retention_current', get_string('retentionperiod', 'tool_dataprivacy'),
|
||||
$this->_customdata['currentretentionperiod']);
|
||||
$mform->addHelpButton('retention_current', 'retentionperiod', 'tool_dataprivacy');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,12 +187,12 @@ class context_instance extends \core\form\persistent {
|
||||
/**
|
||||
* Returns the purpose display text.
|
||||
*
|
||||
* @param \tool_dataprivacy\purpose $effectivepurpose
|
||||
* @param purpose $effectivepurpose
|
||||
* @param int $retentioncontextlevel
|
||||
* @param \context $context The context, just for displaying (filters) purposes.
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_retention_display_text(\tool_dataprivacy\purpose $effectivepurpose, $retentioncontextlevel, \context $context) {
|
||||
protected static function get_retention_display_text(purpose $effectivepurpose, $retentioncontextlevel, \context $context) {
|
||||
global $PAGE;
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_dataprivacy');
|
||||
|
||||
@@ -68,9 +68,11 @@ class defaults extends \moodleform {
|
||||
$purposeoptions = data_registry_page::category_options($this->_customdata['purposes'], false, $includeinherit);
|
||||
|
||||
$mform->addElement('select', $categoryvar, get_string('category', 'tool_dataprivacy'), $categoryoptions);
|
||||
$mform->addHelpButton($categoryvar, 'categorydefault', 'tool_dataprivacy');
|
||||
$mform->setType($categoryvar, PARAM_INT);
|
||||
|
||||
$mform->addElement('select', $purposevar, get_string('purpose', 'tool_dataprivacy'), $purposeoptions);
|
||||
$mform->addHelpButton($purposevar, 'purposedefault', 'tool_dataprivacy');
|
||||
$mform->setType($purposevar, PARAM_INT);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,17 @@ use tool_dataprivacy\api;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
/** The default number of results to be shown per page. */
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
/** Filter constant associated with the request type filter. */
|
||||
const FILTER_TYPE = 1;
|
||||
|
||||
/** Filter constant associated with the request status filter. */
|
||||
const FILTER_STATUS = 2;
|
||||
|
||||
/** The request filters preference key. */
|
||||
const PREF_REQUEST_FILTERS = 'tool_dataprivacy_request-filters';
|
||||
|
||||
/**
|
||||
* Retrieves the human-readable text value of a data request type.
|
||||
@@ -45,16 +56,11 @@ class helper {
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_request_type_string($requesttype) {
|
||||
switch ($requesttype) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
return get_string('requesttypeexport', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_DELETE:
|
||||
return get_string('requesttypedelete', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_OTHERS:
|
||||
return get_string('requesttypeothers', 'tool_dataprivacy');
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
$types = self::get_request_types();
|
||||
if (!isset($types[$requesttype])) {
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
return $types[$requesttype];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,16 +72,37 @@ class helper {
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_shortened_request_type_string($requesttype) {
|
||||
switch ($requesttype) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
return get_string('requesttypeexportshort', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_DELETE:
|
||||
return get_string('requesttypedeleteshort', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_TYPE_OTHERS:
|
||||
return get_string('requesttypeothersshort', 'tool_dataprivacy');
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
$types = self::get_request_types_short();
|
||||
if (!isset($types[$requesttype])) {
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
return $types[$requesttype];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request type code and their string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_types() {
|
||||
return [
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothers', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request type code and their shortened string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_types_short() {
|
||||
return [
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexportshort', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedeleteshort', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothersshort', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,29 +110,103 @@ class helper {
|
||||
*
|
||||
* @param int $status The request status.
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_request_status_string($status) {
|
||||
switch ($status) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
return get_string('statuspending', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_PREPROCESSING:
|
||||
return get_string('statuspreprocessing', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
|
||||
return get_string('statusawaitingapproval', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_APPROVED:
|
||||
return get_string('statusapproved', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_PROCESSING:
|
||||
return get_string('statusprocessing', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_COMPLETE:
|
||||
return get_string('statuscomplete', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
return get_string('statuscancelled', 'tool_dataprivacy');
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
return get_string('statusrejected', 'tool_dataprivacy');
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
|
||||
$statuses = self::get_request_statuses();
|
||||
if (!isset($statuses[$status])) {
|
||||
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
return $statuses[$status];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request status code and string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_statuses() {
|
||||
return [
|
||||
api::DATAREQUEST_STATUS_PENDING => get_string('statuspending', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PREPROCESSING => get_string('statuspreprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => get_string('statusawaitingapproval', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_APPROVED => get_string('statusapproved', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PROCESSING => get_string('statusprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_COMPLETE => get_string('statuscomplete', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_DOWNLOAD_READY => get_string('statusready', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_EXPIRED => get_string('statusexpired', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_CANCELLED => get_string('statuscancelled', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_REJECTED => get_string('statusrejected', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_DELETED => get_string('statusdeleted', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the users that a user can make data request for.
|
||||
*
|
||||
* E.g. User having a parent role and has the 'tool/dataprivacy:makedatarequestsforchildren' capability.
|
||||
* @param int $userid The user's ID.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_children_of_user($userid) {
|
||||
global $DB;
|
||||
|
||||
// Get users that the user has role assignments to.
|
||||
$allusernames = get_all_user_name_fields(true, 'u');
|
||||
$sql = "SELECT u.id, $allusernames
|
||||
FROM {role_assignments} ra, {context} c, {user} u
|
||||
WHERE ra.userid = :userid
|
||||
AND ra.contextid = c.id
|
||||
AND c.instanceid = u.id
|
||||
AND c.contextlevel = :contextlevel";
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'contextlevel' => CONTEXT_USER
|
||||
];
|
||||
|
||||
// The final list of users that we will return.
|
||||
$finalresults = [];
|
||||
|
||||
// Our prospective list of users.
|
||||
if ($candidates = $DB->get_records_sql($sql, $params)) {
|
||||
foreach ($candidates as $key => $child) {
|
||||
$childcontext = \context_user::instance($child->id);
|
||||
if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext, $userid)) {
|
||||
$finalresults[$key] = $child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $finalresults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options for the data requests filter.
|
||||
*
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function get_request_filter_options() {
|
||||
$filters = [
|
||||
self::FILTER_TYPE => (object)[
|
||||
'name' => get_string('requesttype', 'tool_dataprivacy'),
|
||||
'options' => self::get_request_types_short()
|
||||
],
|
||||
self::FILTER_STATUS => (object)[
|
||||
'name' => get_string('requeststatus', 'tool_dataprivacy'),
|
||||
'options' => self::get_request_statuses()
|
||||
],
|
||||
];
|
||||
$options = [];
|
||||
foreach ($filters as $category => $filtercategory) {
|
||||
foreach ($filtercategory->options as $key => $name) {
|
||||
$option = (object)[
|
||||
'category' => $filtercategory->name,
|
||||
'name' => $name
|
||||
];
|
||||
$options["{$category}:{$key}"] = get_string('filteroption', 'tool_dataprivacy', $option);
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Class \tool_dataprivacy\manager_observer.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* A failure observer for the \core_privacy\manager.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class manager_observer implements \core_privacy\manager_observer {
|
||||
/**
|
||||
* Notifies all DPOs that an exception occurred.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @param string $component
|
||||
* @param string $interface
|
||||
* @param string $methodname
|
||||
* @param array $params
|
||||
*/
|
||||
public function handle_component_failure($e, $component, $interface, $methodname, array $params) {
|
||||
// Get the list of the site Data Protection Officers.
|
||||
$dpos = api::get_site_dpos();
|
||||
|
||||
$messagesubject = get_string('exceptionnotificationsubject', 'tool_dataprivacy');
|
||||
$a = (object)[
|
||||
'fullmethodname' => \core_privacy\manager::get_provider_classname_for_component($component) . '::' . $methodname,
|
||||
'component' => $component,
|
||||
'message' => $e->getMessage(),
|
||||
'backtrace' => $e->getTraceAsString()
|
||||
];
|
||||
$messagebody = get_string('exceptionnotificationbody', 'tool_dataprivacy', $a);
|
||||
|
||||
// Email the data request to the Data Protection Officer(s)/Admin(s).
|
||||
foreach ($dpos as $dpo) {
|
||||
$message = new \core\message\message();
|
||||
$message->courseid = SITEID;
|
||||
$message->component = 'tool_dataprivacy';
|
||||
$message->name = 'notifyexceptions';
|
||||
$message->userfrom = \core_user::get_noreply_user();
|
||||
$message->subject = $messagesubject;
|
||||
$message->fullmessageformat = FORMAT_HTML;
|
||||
$message->notification = 1;
|
||||
$message->userto = $dpo;
|
||||
$message->fullmessagehtml = $messagebody;
|
||||
$message->fullmessage = html_to_text($messagebody);
|
||||
|
||||
// Send message.
|
||||
message_send($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class metadata_registry {
|
||||
*/
|
||||
public function get_registry_metadata() {
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$pluginman = \core_plugin_manager::instance();
|
||||
$contributedplugins = $this->get_contrib_list();
|
||||
$metadata = $manager->get_metadata_for_components();
|
||||
@@ -56,6 +58,10 @@ class metadata_registry {
|
||||
if (isset($metadata[$component])) {
|
||||
$collection = $metadata[$component]->get_collection();
|
||||
$internaldata = $this->format_metadata($collection, $component, $internaldata);
|
||||
} else if ($manager->is_empty_subsystem($component)) {
|
||||
// This is an unused subsystem.
|
||||
// Use the generic string.
|
||||
$internaldata['nullprovider'] = get_string('privacy:subsystem:empty', 'core_privacy');
|
||||
} else {
|
||||
// Call get_reason for null provider.
|
||||
$internaldata['nullprovider'] = get_string($manager->get_null_provider_reason($component), $component);
|
||||
@@ -64,7 +70,8 @@ class metadata_registry {
|
||||
$internaldata['compliant'] = false;
|
||||
}
|
||||
// Check to see if we are an external plugin.
|
||||
$componentshortname = explode('_', $component);
|
||||
// Plugin names can contain _ characters, limit to 2 to just remove initial plugintype.
|
||||
$componentshortname = explode('_', $component, 2);
|
||||
$shortname = array_pop($componentshortname);
|
||||
if (isset($contributedplugins[$plugintype][$shortname])) {
|
||||
$internaldata['external'] = true;
|
||||
@@ -122,11 +129,15 @@ class metadata_registry {
|
||||
* @return array An array of plugin types which contain plugin data.
|
||||
*/
|
||||
protected function get_full_component_list() {
|
||||
global $CFG;
|
||||
|
||||
$list = \core_component::get_component_list();
|
||||
$list['core']['core'] = "{$CFG->dirroot}/lib";
|
||||
$formattedlist = [];
|
||||
foreach ($list as $plugintype => $plugin) {
|
||||
$formattedlist[] = ['plugin_type' => $plugintype, 'plugins' => array_keys($plugin)];
|
||||
}
|
||||
|
||||
return $formattedlist;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use dml_exception;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
@@ -34,7 +33,7 @@ use single_select;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\output\expired_contexts_table;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
/**
|
||||
* Class containing data for a user's data requests.
|
||||
@@ -44,9 +43,6 @@ use tool_dataprivacy\output\expired_contexts_table;
|
||||
*/
|
||||
class data_deletion_page implements renderable, templatable {
|
||||
|
||||
/** The default number of results to be shown per page. */
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
/** @var data_request[] $requests List of data requests. */
|
||||
protected $filter = null;
|
||||
|
||||
@@ -57,7 +53,7 @@ class data_deletion_page implements renderable, templatable {
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param \tool_dataprivacy\data_request[] $filter
|
||||
* @param \tool_dataprivacy\expired_contexts_table $expiredcontextstable
|
||||
* @param expired_contexts_table $expiredcontextstable
|
||||
*/
|
||||
public function __construct($filter, expired_contexts_table $expiredcontextstable) {
|
||||
$this->filter = $filter;
|
||||
@@ -70,7 +66,6 @@ class data_deletion_page implements renderable, templatable {
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
@@ -87,7 +82,7 @@ class data_deletion_page implements renderable, templatable {
|
||||
$data->filter = $filterselector->export_for_template($output);
|
||||
|
||||
ob_start();
|
||||
$this->expiredcontextstable->out(self::DEFAULT_PAGE_SIZE, true);
|
||||
$this->expiredcontextstable->out(helper::DEFAULT_PAGE_SIZE, true);
|
||||
$expiredcontexts = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$data->expiredcontexts = $expiredcontexts;
|
||||
|
||||
@@ -101,6 +101,10 @@ class data_registry_page implements renderable, templatable {
|
||||
$data->actions = $actionmenu->export_for_template($output);
|
||||
|
||||
if (!data_registry::defaults_set()) {
|
||||
$data->info = (object)[
|
||||
'message' => get_string('dataregistryinfo', 'tool_dataprivacy'),
|
||||
'announce' => 1
|
||||
];
|
||||
$data->nosystemdefaults = (object)[
|
||||
'message' => get_string('nosystemdefaults', 'tool_dataprivacy'),
|
||||
'announce' => 1
|
||||
@@ -131,7 +135,7 @@ class data_registry_page implements renderable, templatable {
|
||||
'text' => get_string('user'),
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
], [
|
||||
'text' => get_string('categories', 'tool_dataprivacy'),
|
||||
'text' => get_string('categories'),
|
||||
'branches' => $categorybranches,
|
||||
'expandelement' => 'category',
|
||||
], [
|
||||
@@ -312,29 +316,23 @@ class data_registry_page implements renderable, templatable {
|
||||
|
||||
$branches = [];
|
||||
|
||||
$blockinstances = \core_block_external::get_course_blocks($coursecontext->instanceid);
|
||||
if (empty($blockinstances['blocks'])) {
|
||||
return $branches;
|
||||
}
|
||||
$children = $coursecontext->get_child_contexts();
|
||||
foreach ($children as $childcontext) {
|
||||
|
||||
foreach ($blockinstances['blocks'] as $bi) {
|
||||
if (function_exists('block_instance_by_id')) {
|
||||
$blockinstance = block_instance_by_id($bi['instanceid']);
|
||||
} else {
|
||||
// TODO To be removed when MDL-61621 gets integrated.
|
||||
$blockinstance = $DB->get_record('block_instances', ['id' => $bi['instanceid']]);
|
||||
$blockinstance = block_instance($blockinstance->blockname, $blockinstance);
|
||||
if ($childcontext->contextlevel !== CONTEXT_BLOCK) {
|
||||
continue;
|
||||
}
|
||||
$blockcontext = \context_block::instance($bi['instanceid']);
|
||||
$displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $blockcontext->id]));
|
||||
|
||||
$blockinstance = block_instance_by_id($childcontext->instanceid);
|
||||
$displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $childcontext]));
|
||||
$branches[] = self::complete([
|
||||
'text' => $displayname,
|
||||
'contextid' => $blockcontext->id,
|
||||
'contextid' => $childcontext->id,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
return $branches;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,20 +24,17 @@
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use action_menu;
|
||||
use action_menu_link_secondary;
|
||||
use coding_exception;
|
||||
use context_system;
|
||||
use dml_exception;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use single_select;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
/**
|
||||
* Class containing data for a user's data requests.
|
||||
@@ -47,16 +44,21 @@ use tool_dataprivacy\external\data_request_exporter;
|
||||
*/
|
||||
class data_requests_page implements renderable, templatable {
|
||||
|
||||
/** @var data_request[] $requests List of data requests. */
|
||||
protected $requests = [];
|
||||
/** @var data_requests_table $table The data requests table. */
|
||||
protected $table;
|
||||
|
||||
/** @var int[] $filters The applied filters. */
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param data_request[] $requests
|
||||
* @param data_requests_table $table The data requests table.
|
||||
* @param int[] $filters The applied filters.
|
||||
*/
|
||||
public function __construct($requests) {
|
||||
$this->requests = $requests;
|
||||
public function __construct($table, $filters) {
|
||||
$this->table = $table;
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,43 +75,22 @@ class data_requests_page implements renderable, templatable {
|
||||
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
|
||||
$data->newdatarequesturl->param('manage', true);
|
||||
|
||||
$requests = [];
|
||||
foreach ($this->requests as $request) {
|
||||
$requestid = $request->get('id');
|
||||
$status = $request->get('status');
|
||||
$requestexporter = new data_request_exporter($request, ['context' => context_system::instance()]);
|
||||
$item = $requestexporter->export($output);
|
||||
|
||||
// Prepare actions.
|
||||
$actions = [];
|
||||
|
||||
// View action.
|
||||
$actionurl = new moodle_url('#');
|
||||
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
|
||||
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
if ($status == api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
|
||||
// Approve.
|
||||
$actiondata['data-action'] = 'approve';
|
||||
$actiontext = get_string('approverequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
// Deny.
|
||||
$actiondata['data-action'] = 'deny';
|
||||
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
}
|
||||
|
||||
$actionsmenu = new action_menu($actions);
|
||||
$actionsmenu->set_menu_trigger(get_string('actions'));
|
||||
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
|
||||
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
$item->actions = $actionsmenu->export_for_template($output);
|
||||
|
||||
$requests[] = $item;
|
||||
if (!is_https()) {
|
||||
$httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
|
||||
$data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
|
||||
}
|
||||
$data->requests = $requests;
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
|
||||
$filteroptions = helper::get_request_filter_options();
|
||||
$filter = new request_filter($filteroptions, $this->filters, $url);
|
||||
$data->filter = $filter->export_for_template($output);
|
||||
|
||||
ob_start();
|
||||
$this->table->out(helper::DEFAULT_PAGE_SIZE, true);
|
||||
$requests = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$data->datarequests = $requests;
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Contains the class used for the displaying the data requests table.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/tablelib.php');
|
||||
|
||||
use action_menu;
|
||||
use action_menu_link_secondary;
|
||||
use coding_exception;
|
||||
use dml_exception;
|
||||
use html_writer;
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
use table_sql;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
/**
|
||||
* The class for displaying the data requests table.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_requests_table extends table_sql {
|
||||
|
||||
/** @var int The user ID. */
|
||||
protected $userid = 0;
|
||||
|
||||
/** @var int[] The status filters. */
|
||||
protected $statuses = [];
|
||||
|
||||
/** @var int[] The request type filters. */
|
||||
protected $types = [];
|
||||
|
||||
/** @var bool Whether this table is being rendered for managing data requests. */
|
||||
protected $manage = false;
|
||||
|
||||
/** @var \tool_dataprivacy\data_request[] Array of data request persistents. */
|
||||
protected $datarequests = [];
|
||||
|
||||
/**
|
||||
* data_requests_table constructor.
|
||||
*
|
||||
* @param int $userid The user ID
|
||||
* @param int[] $statuses
|
||||
* @param int[] $types
|
||||
* @param bool $manage
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function __construct($userid = 0, $statuses = [], $types = [], $manage = false) {
|
||||
parent::__construct('data-requests-table');
|
||||
|
||||
$this->userid = $userid;
|
||||
$this->statuses = $statuses;
|
||||
$this->types = $types;
|
||||
$this->manage = $manage;
|
||||
|
||||
$columnheaders = [
|
||||
'type' => get_string('requesttype', 'tool_dataprivacy'),
|
||||
'userid' => get_string('user', 'tool_dataprivacy'),
|
||||
'timecreated' => get_string('daterequested', 'tool_dataprivacy'),
|
||||
'requestedby' => get_string('requestby', 'tool_dataprivacy'),
|
||||
'status' => get_string('requeststatus', 'tool_dataprivacy'),
|
||||
'comments' => get_string('message', 'tool_dataprivacy'),
|
||||
'actions' => '',
|
||||
];
|
||||
|
||||
$this->define_columns(array_keys($columnheaders));
|
||||
$this->define_headers(array_values($columnheaders));
|
||||
$this->no_sorting('actions');
|
||||
}
|
||||
|
||||
/**
|
||||
* The type column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_type($data) {
|
||||
if ($this->manage) {
|
||||
return $data->typenameshort;
|
||||
}
|
||||
return $data->typename;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_userid($data) {
|
||||
$user = $data->foruser;
|
||||
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The context information column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_timecreated($data) {
|
||||
return userdate($data->timecreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* The requesting user's column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_requestedby($data) {
|
||||
$user = $data->requestedbyuser;
|
||||
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The status column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_status($data) {
|
||||
return html_writer::span($data->statuslabel, 'label ' . $data->statuslabelclass);
|
||||
}
|
||||
|
||||
/**
|
||||
* The comments column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_comments($data) {
|
||||
return shorten_text($data->comments, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actions column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_actions($data) {
|
||||
global $OUTPUT;
|
||||
|
||||
$requestid = $data->id;
|
||||
$status = $data->status;
|
||||
|
||||
// Prepare actions.
|
||||
$actions = [];
|
||||
|
||||
// View action.
|
||||
$actionurl = new moodle_url('#');
|
||||
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
|
||||
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
switch ($status) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
// Add action to mark a general enquiry request as complete.
|
||||
if ($data->type == api::DATAREQUEST_TYPE_OTHERS) {
|
||||
$actiondata['data-action'] = 'complete';
|
||||
$nameemail = (object)[
|
||||
'name' => $data->foruser->fullname,
|
||||
'email' => $data->foruser->email
|
||||
];
|
||||
$actiondata['data-requestid'] = $data->id;
|
||||
$actiondata['data-replytoemail'] = get_string('nameemail', 'tool_dataprivacy', $nameemail);
|
||||
$actiontext = get_string('markcomplete', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
}
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
|
||||
// Approve.
|
||||
$actiondata['data-action'] = 'approve';
|
||||
$actiontext = get_string('approverequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
// Deny.
|
||||
$actiondata['data-action'] = 'deny';
|
||||
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$userid = $data->foruser->id;
|
||||
$usercontext = \context_user::instance($userid, IGNORE_MISSING);
|
||||
// If user has permission to view download link, show relevant action item.
|
||||
if ($usercontext && api::can_download_data_request_for_user($userid, $data->requestedbyuser->id)) {
|
||||
$actions[] = api::get_download_link($usercontext, $requestid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$actionsmenu = new action_menu($actions);
|
||||
$actionsmenu->set_menu_trigger(get_string('actions'));
|
||||
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
|
||||
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
$actionsmenu->set_constraint('[data-region=data-requests-table] > .no-overflow');
|
||||
|
||||
return $OUTPUT->render($actionsmenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database for results to display in the table.
|
||||
*
|
||||
* @param int $pagesize size of page for paginated displayed table.
|
||||
* @param bool $useinitialsbar do you want to use the initials bar.
|
||||
* @throws dml_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function query_db($pagesize, $useinitialsbar = true) {
|
||||
global $PAGE;
|
||||
|
||||
// Set dummy page total until we fetch full result set.
|
||||
$this->pagesize($pagesize, $pagesize + 1);
|
||||
|
||||
$sort = $this->get_sql_sort();
|
||||
|
||||
// Get data requests from the given conditions.
|
||||
$datarequests = api::get_data_requests($this->userid, $this->statuses, $this->types, $sort,
|
||||
$this->get_page_start(), $this->get_page_size());
|
||||
|
||||
// Count data requests from the given conditions.
|
||||
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types);
|
||||
$this->pagesize($pagesize, $total);
|
||||
|
||||
$this->rawdata = [];
|
||||
$context = \context_system::instance();
|
||||
$renderer = $PAGE->get_renderer('tool_dataprivacy');
|
||||
|
||||
foreach ($datarequests as $persistent) {
|
||||
$this->datarequests[$persistent->get('id')] = $persistent;
|
||||
$exporter = new data_request_exporter($persistent, ['context' => $context]);
|
||||
$this->rawdata[] = $exporter->export($renderer);
|
||||
}
|
||||
|
||||
// Set initial bars.
|
||||
if ($useinitialsbar) {
|
||||
$this->initialbars($total > $pagesize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override default implementation to display a more meaningful information to the user.
|
||||
*/
|
||||
public function print_nothing_to_display() {
|
||||
global $OUTPUT;
|
||||
echo $this->render_reset_button();
|
||||
$this->print_initials_bar();
|
||||
if (!empty($this->statuses) || !empty($this->types)) {
|
||||
$message = get_string('nodatarequestsmatchingfilter', 'tool_dataprivacy');
|
||||
} else {
|
||||
$message = get_string('nodatarequests', 'tool_dataprivacy');
|
||||
}
|
||||
echo $OUTPUT->notification($message, 'warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the table's show_hide_link method to prevent the show/hide links from rendering.
|
||||
*
|
||||
* @param string $column the column name, index into various names.
|
||||
* @param int $index numerical index of the column.
|
||||
* @return string HTML fragment.
|
||||
*/
|
||||
protected function show_hide_link($column, $index) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -67,18 +67,41 @@ class my_data_requests_page implements renderable, templatable {
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $USER;
|
||||
|
||||
$data = new stdClass();
|
||||
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
|
||||
|
||||
if (!is_https()) {
|
||||
$httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
|
||||
$data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
|
||||
}
|
||||
|
||||
$requests = [];
|
||||
foreach ($this->requests as $request) {
|
||||
$requestid = $request->get('id');
|
||||
$status = $request->get('status');
|
||||
$userid = $request->get('userid');
|
||||
$usercontext = context_user::instance($userid);
|
||||
$requestexporter = new data_request_exporter($request, ['context' => $usercontext]);
|
||||
$type = $request->get('type');
|
||||
|
||||
$usercontext = context_user::instance($userid, IGNORE_MISSING);
|
||||
if (!$usercontext) {
|
||||
// Use the context system.
|
||||
$outputcontext = \context_system::instance();
|
||||
} else {
|
||||
$outputcontext = $usercontext;
|
||||
}
|
||||
|
||||
$requestexporter = new data_request_exporter($request, ['context' => $outputcontext]);
|
||||
$item = $requestexporter->export($output);
|
||||
|
||||
$self = $request->get('userid') == $USER->id;
|
||||
if (!$self) {
|
||||
// Append user name if it differs from $USER.
|
||||
$a = (object)['typename' => $item->typename, 'user' => $item->foruser->fullname];
|
||||
$item->typename = get_string('requesttypeuser', 'tool_dataprivacy', $a);
|
||||
}
|
||||
|
||||
$candownload = false;
|
||||
$cancancel = true;
|
||||
switch ($status) {
|
||||
@@ -86,7 +109,28 @@ class my_data_requests_page implements renderable, templatable {
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statusready', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
$candownload = true;
|
||||
|
||||
if ($usercontext) {
|
||||
$candownload = api::can_download_data_request_for_user(
|
||||
$request->get('userid'), $request->get('requestedby'));
|
||||
}
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DELETED:
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statusdeleted', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$item->statuslabelclass = 'label-default';
|
||||
$item->statuslabel = get_string('statusexpired', 'tool_dataprivacy');
|
||||
$item->statuslabeltitle = get_string('downloadexpireduser', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
@@ -102,11 +146,8 @@ class my_data_requests_page implements renderable, templatable {
|
||||
$canceltext = get_string('cancelrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($cancelurl, null, $canceltext, $canceldata);
|
||||
}
|
||||
if ($candownload) {
|
||||
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $requestid, '/',
|
||||
'export.zip', true);
|
||||
$downloadtext = get_string('download', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($downloadurl, null, $downloadtext);
|
||||
if ($candownload && $usercontext) {
|
||||
$actions[] = api::get_download_link($usercontext, $requestid);
|
||||
}
|
||||
if (!empty($actions)) {
|
||||
$actionsmenu = new action_menu($actions);
|
||||
|
||||
@@ -62,7 +62,8 @@ class renderer extends plugin_renderer_base {
|
||||
public function render_contact_dpo_link($replytoemail) {
|
||||
$params = [
|
||||
'data-action' => 'contactdpo',
|
||||
'data-replytoemail' => $replytoemail
|
||||
'data-replytoemail' => $replytoemail,
|
||||
'class' => 'contactdpo'
|
||||
];
|
||||
return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Class containing the filter options data for rendering the autocomplete element for the data requests page.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class containing the filter options data for rendering the autocomplete element for the data requests page.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class request_filter implements renderable, templatable {
|
||||
|
||||
/** @var array $filteroptions The filter options. */
|
||||
protected $filteroptions;
|
||||
|
||||
/** @var array $selectedoptions The list of selected filter option values. */
|
||||
protected $selectedoptions;
|
||||
|
||||
/** @var moodle_url|string $baseurl The url with params needed to call up this page. */
|
||||
protected $baseurl;
|
||||
|
||||
/**
|
||||
* request_filter constructor.
|
||||
*
|
||||
* @param array $filteroptions The filter options.
|
||||
* @param array $selectedoptions The list of selected filter option values.
|
||||
* @param string|moodle_url $baseurl The url with params needed to call up this page.
|
||||
*/
|
||||
public function __construct($filteroptions, $selectedoptions, $baseurl = null) {
|
||||
$this->filteroptions = $filteroptions;
|
||||
$this->selectedoptions = $selectedoptions;
|
||||
if (!empty($baseurl)) {
|
||||
$this->baseurl = new moodle_url($baseurl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to export the renderer data in a format that is suitable for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
|
||||
* @return stdClass|array
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $PAGE;
|
||||
$data = new stdClass();
|
||||
if (empty($this->baseurl)) {
|
||||
$this->baseurl = $PAGE->url;
|
||||
}
|
||||
$data->action = $this->baseurl->out(false);
|
||||
|
||||
foreach ($this->selectedoptions as $option) {
|
||||
if (!isset($this->filteroptions[$option])) {
|
||||
$this->filteroptions[$option] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
$data->filteroptions = [];
|
||||
foreach ($this->filteroptions as $value => $label) {
|
||||
$selected = in_array($value, $this->selectedoptions);
|
||||
$filteroption = (object)[
|
||||
'value' => $value,
|
||||
'label' => $label
|
||||
];
|
||||
$filteroption->selected = $selected;
|
||||
$data->filteroptions[] = $filteroption;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,10 @@ class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
|
||||
// This tool may provide access to and deletion of user data.
|
||||
\core_privacy\local\request\plugin\provider {
|
||||
\core_privacy\local\request\plugin\provider,
|
||||
|
||||
// This plugin has some sitewide user preferences to export.
|
||||
\core_privacy\local\request\user_preference_provider {
|
||||
/**
|
||||
* Returns meta data about this system.
|
||||
*
|
||||
@@ -70,6 +73,10 @@ class provider implements
|
||||
],
|
||||
'privacy:metadata:request'
|
||||
);
|
||||
|
||||
$collection->add_user_preference(tool_helper::PREF_REQUEST_FILTERS,
|
||||
'privacy:metadata:preference:tool_dataprivacy_request-filters');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
@@ -162,4 +169,36 @@ class provider implements
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
*
|
||||
* @param int $userid The userid of the user whose data is to be exported.
|
||||
*/
|
||||
public static function export_user_preferences(int $userid) {
|
||||
$preffilter = get_user_preferences(tool_helper::PREF_REQUEST_FILTERS, null, $userid);
|
||||
if ($preffilter !== null) {
|
||||
$filters = json_decode($preffilter);
|
||||
$descriptions = [];
|
||||
foreach ($filters as $filter) {
|
||||
list($category, $value) = explode(':', $filter);
|
||||
$option = new stdClass();
|
||||
switch($category) {
|
||||
case tool_helper::FILTER_TYPE:
|
||||
$option->category = get_string('requesttype', 'tool_dataprivacy');
|
||||
$option->name = tool_helper::get_shortened_request_type_string($value);
|
||||
break;
|
||||
case tool_helper::FILTER_STATUS:
|
||||
$option->category = get_string('requeststatus', 'tool_dataprivacy');
|
||||
$option->name = tool_helper::get_request_status_string($value);
|
||||
break;
|
||||
}
|
||||
$descriptions[] = get_string('filteroption', 'tool_dataprivacy', $option);
|
||||
}
|
||||
// Export the filter preference as comma-separated values and text descriptions.
|
||||
$values = implode(', ', $filters);
|
||||
$descriptionstext = implode(', ', $descriptions);
|
||||
writer::export_user_preference('tool_dataprivacy', tool_helper::PREF_REQUEST_FILTERS, $values, $descriptionstext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class purpose extends \core\persistent {
|
||||
// Replicate self::read.
|
||||
$this->from_record($data);
|
||||
|
||||
// Using validate() as self::$validated is private.
|
||||
// Validate the purpose record.
|
||||
$this->validate();
|
||||
|
||||
// Now replicate the parent constructor.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Scheduled task to delete files and update statuses of expired data requests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use coding_exception;
|
||||
use core\task\scheduled_task;
|
||||
use tool_dataprivacy\api;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Scheduled task to delete files and update request statuses once they expire.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class delete_expired_requests extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Returns the task name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('deleteexpireddatarequeststask', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task to delete expired data request files and update request statuses.
|
||||
*
|
||||
*/
|
||||
public function execute() {
|
||||
$expiredrequests = \tool_dataprivacy\data_request::get_expired_requests();
|
||||
$deletecount = count($expiredrequests);
|
||||
|
||||
if ($deletecount > 0) {
|
||||
\tool_dataprivacy\data_request::expire($expiredrequests);
|
||||
|
||||
mtrace($deletecount . ' expired completed data requests have been deleted');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ class initiate_data_request_task extends adhoc_task {
|
||||
public function execute() {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/admin/tool/dataprivacy/lib.php');
|
||||
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
|
||||
|
||||
if (!isset($this->get_custom_data()->requestid)) {
|
||||
throw new coding_exception('The custom data \'requestid\' is required.');
|
||||
@@ -70,12 +70,35 @@ class initiate_data_request_task extends adhoc_task {
|
||||
return;
|
||||
}
|
||||
|
||||
$requestedby = $datarequest->get('requestedby');
|
||||
$valid = true;
|
||||
$comment = '';
|
||||
$foruser = $datarequest->get('userid');
|
||||
if ($foruser != $requestedby) {
|
||||
if (!$valid = api::can_create_data_request_for_user($foruser, $requestedby)) {
|
||||
$params = (object)[
|
||||
'requestedby' => $requestedby,
|
||||
'userid' => $foruser
|
||||
];
|
||||
$comment = get_string('errornocapabilitytorequestforothers', 'tool_dataprivacy', $params);
|
||||
mtrace($comment);
|
||||
}
|
||||
}
|
||||
// Reject the request outright if it's invalid.
|
||||
if (!$valid) {
|
||||
$dpo = $datarequest->get('dpo');
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_REJECTED, $dpo, $comment);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the status of this request as pre-processing.
|
||||
mtrace('Generating the contexts containing personal data for the user...');
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
|
||||
|
||||
// Add the list of relevant contexts to the request, and mark all as pending approval.
|
||||
$privacymanager = new \core_privacy\manager();
|
||||
$privacymanager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$contextlistcollection = $privacymanager->get_contexts_for_userid($datarequest->get('userid'));
|
||||
api::add_request_contexts_with_status($contextlistcollection, $requestid, contextlist_context::STATUS_PENDING);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class process_data_request_task extends adhoc_task {
|
||||
public function execute() {
|
||||
global $CFG, $PAGE, $SITE;
|
||||
|
||||
require_once($CFG->dirroot . '/admin/tool/dataprivacy/lib.php');
|
||||
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
|
||||
|
||||
if (!isset($this->get_custom_data()->requestid)) {
|
||||
throw new coding_exception('The custom data \'requestid\' is required.');
|
||||
@@ -68,8 +68,9 @@ class process_data_request_task extends adhoc_task {
|
||||
$request = $requestpersistent->to_record();
|
||||
|
||||
// Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run.
|
||||
if ($request->status != api::DATAREQUEST_STATUS_APPROVED) {
|
||||
mtrace("Request {$request->id} hasn\'t been approved yet or it has already been processed. Skipping...");
|
||||
$status = $requestpersistent->get('status');
|
||||
if (!api::is_active($status)) {
|
||||
mtrace("Request {$requestid} with status {$status} doesn't need to be processed. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ class process_data_request_task extends adhoc_task {
|
||||
// Update the status of this request as pre-processing.
|
||||
mtrace('Processing request...');
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING);
|
||||
$completestatus = api::DATAREQUEST_STATUS_COMPLETE;
|
||||
|
||||
if ($request->type == api::DATAREQUEST_TYPE_EXPORT) {
|
||||
// Get the collection of approved_contextlist objects needed for core_privacy data export.
|
||||
@@ -87,6 +89,8 @@ class process_data_request_task extends adhoc_task {
|
||||
|
||||
// Export the data.
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$exportedcontent = $manager->export_user_data($approvedclcollection);
|
||||
|
||||
$fs = get_file_storage();
|
||||
@@ -102,18 +106,21 @@ class process_data_request_task extends adhoc_task {
|
||||
$filerecord->author = fullname($foruser);
|
||||
// Save somewhere.
|
||||
$thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
|
||||
|
||||
$completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY;
|
||||
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
|
||||
// Get the collection of approved_contextlist objects needed for core_privacy data deletion.
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
|
||||
|
||||
// Delete the data.
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$manager->delete_data_for_user($approvedclcollection);
|
||||
$completestatus = api::DATAREQUEST_STATUS_DELETED;
|
||||
}
|
||||
|
||||
// When the preparation of the metadata finishes, update the request status to awaiting approval.
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE);
|
||||
api::update_request_status($requestid, $completestatus);
|
||||
mtrace('The processing of the user data request has been completed...');
|
||||
|
||||
// Create message to notify the user regarding the processing results.
|
||||
@@ -134,8 +141,17 @@ class process_data_request_task extends adhoc_task {
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
$emailonly = false;
|
||||
$notifyuser = true;
|
||||
switch ($request->type) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
// Check if the user is allowed to download their own export. (This is for
|
||||
// institutions which centrally co-ordinate subject access request across many
|
||||
// systems, not just one Moodle instance, so we don't want every instance emailing
|
||||
// the user.)
|
||||
if (!api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->userid)) {
|
||||
$notifyuser = false;
|
||||
}
|
||||
|
||||
$typetext = get_string('requesttypeexport', 'tool_dataprivacy');
|
||||
// We want to notify the user in Moodle about the processing results.
|
||||
$message->notification = 1;
|
||||
@@ -174,30 +190,56 @@ class process_data_request_task extends adhoc_task {
|
||||
$message->fullmessagehtml = $messagehtml;
|
||||
|
||||
// Send message to the user involved.
|
||||
if ($emailonly) {
|
||||
email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
|
||||
} else {
|
||||
message_send($message);
|
||||
}
|
||||
mtrace('Message sent to user: ' . $messagetextdata['username']);
|
||||
|
||||
// Send to the requester as well. requestedby is 0 if the request was made on behalf of the user by a DPO.
|
||||
if (!empty($request->requestedby) && $foruser->id != $request->requestedby) {
|
||||
$requestedby = core_user::get_user($request->requestedby);
|
||||
$message->userto = $requestedby;
|
||||
$messagetextdata['username'] = fullname($requestedby);
|
||||
// Render message email body.
|
||||
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
|
||||
$message->fullmessage = html_to_text($messagehtml);
|
||||
$message->fullmessagehtml = $messagehtml;
|
||||
|
||||
// Send message.
|
||||
if ($notifyuser) {
|
||||
if ($emailonly) {
|
||||
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
|
||||
email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
|
||||
} else {
|
||||
message_send($message);
|
||||
}
|
||||
mtrace('Message sent to requester: ' . $messagetextdata['username']);
|
||||
mtrace('Message sent to user: ' . $messagetextdata['username']);
|
||||
}
|
||||
|
||||
// Send to requester as well in some circumstances.
|
||||
if ($foruser->id != $request->requestedby) {
|
||||
$sendtorequester = false;
|
||||
switch ($request->type) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
// Send to the requester as well if they can download it, unless they are the
|
||||
// DPO. If we didn't notify the user themselves (because they can't download)
|
||||
// then send to requester even if it is the DPO, as in that case the requester
|
||||
// needs to take some action.
|
||||
if (api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->requestedby)) {
|
||||
$sendtorequester = !$notifyuser || !api::is_site_dpo($request->requestedby);
|
||||
}
|
||||
break;
|
||||
case api::DATAREQUEST_TYPE_DELETE:
|
||||
// Send to the requester if they are not the DPO and if they are allowed to
|
||||
// create data requests for the user (e.g. Parent).
|
||||
$sendtorequester = !api::is_site_dpo($request->requestedby) &&
|
||||
api::can_create_data_request_for_user($request->userid, $request->requestedby);
|
||||
break;
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
// Ensure the requester has the capability to make data requests for this user.
|
||||
if ($sendtorequester) {
|
||||
$requestedby = core_user::get_user($request->requestedby);
|
||||
$message->userto = $requestedby;
|
||||
$messagetextdata['username'] = fullname($requestedby);
|
||||
// Render message email body.
|
||||
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
|
||||
$message->fullmessage = html_to_text($messagehtml);
|
||||
$message->fullmessagehtml = $messagehtml;
|
||||
|
||||
// Send message.
|
||||
if ($emailonly) {
|
||||
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
|
||||
} else {
|
||||
message_send($message);
|
||||
}
|
||||
mtrace('Message sent to requester: ' . $messagetextdata['username']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
|
||||
|
||||
@@ -53,11 +53,12 @@ if ($manage) {
|
||||
$PAGE->set_context($context);
|
||||
|
||||
// If contactdataprotectionofficer is disabled, send the user back to the profile page, or the privacy policy page.
|
||||
if (!\tool_dataprivacy\api::can_contact_dpo()) {
|
||||
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), \core\output\notification::NOTIFY_ERROR);
|
||||
// That is, unless you have sufficient capabilities to perform this on behalf of a user.
|
||||
if (!$manage && !\tool_dataprivacy\api::can_contact_dpo()) {
|
||||
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), 0, \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
|
||||
$mform = new tool_dataprivacy_data_request_form($url->out(false));
|
||||
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage)]);
|
||||
|
||||
// Data request cancelled.
|
||||
if ($mform->is_cancelled()) {
|
||||
@@ -68,7 +69,13 @@ if ($mform->is_cancelled()) {
|
||||
if ($data = $mform->get_data()) {
|
||||
\tool_dataprivacy\api::create_data_request($data->userid, $data->type, $data->comments);
|
||||
|
||||
redirect($returnurl, get_string('requestsubmitted', 'tool_dataprivacy'));
|
||||
if ($manage) {
|
||||
$foruser = core_user::get_user($data->userid);
|
||||
$redirectmessage = get_string('datarequestcreatedforuser', 'tool_dataprivacy', fullname($foruser));
|
||||
} else {
|
||||
$redirectmessage = get_string('requestsubmitted', 'tool_dataprivacy');
|
||||
}
|
||||
redirect($returnurl, $redirectmessage);
|
||||
}
|
||||
|
||||
$title = get_string('contactdataprotectionofficer', 'tool_dataprivacy');
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -37,48 +38,46 @@ require_once($CFG->libdir.'/formslib.php');
|
||||
*/
|
||||
class tool_dataprivacy_data_request_form extends moodleform {
|
||||
|
||||
/** @var bool Flag to indicate whether this form is being rendered for managing data requests or for regular requests. */
|
||||
protected $manage = false;
|
||||
|
||||
/**
|
||||
* Form definition.
|
||||
*
|
||||
* @throws HTML_QuickForm_Error
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public function definition() {
|
||||
global $DB, $USER;
|
||||
global $USER;
|
||||
$mform =& $this->_form;
|
||||
|
||||
if (api::is_site_dpo($USER->id)) {
|
||||
$this->manage = $this->_customdata['manage'];
|
||||
if ($this->manage) {
|
||||
$options = [
|
||||
'ajax' => 'tool_dataprivacy/form-user-selector',
|
||||
'multiple' => false
|
||||
'valuehtmlcallback' => function($value) {
|
||||
global $OUTPUT;
|
||||
|
||||
$allusernames = get_all_user_name_fields(true);
|
||||
$fields = 'id, email, ' . $allusernames;
|
||||
$user = \core_user::get_user($value, $fields);
|
||||
$useroptiondata = [
|
||||
'fullname' => fullname($user),
|
||||
'email' => $user->email
|
||||
];
|
||||
return $OUTPUT->render_from_template('tool_dataprivacy/form-user-selector-suggestion', $useroptiondata);
|
||||
}
|
||||
];
|
||||
$mform->addElement('autocomplete', 'userid', get_string('requestfor', 'tool_dataprivacy'), [], $options);
|
||||
$mform->addRule('userid', null, 'required', null, 'client');
|
||||
|
||||
} else {
|
||||
// Get users whom you are being a guardian to if your role has the capability to make data requests for children.
|
||||
$allusernames = get_all_user_name_fields(true, 'u');
|
||||
$sql = "SELECT u.id, $allusernames
|
||||
FROM {role_assignments} ra, {context} c, {user} u
|
||||
WHERE ra.userid = :userid
|
||||
AND ra.contextid = c.id
|
||||
AND c.instanceid = u.id
|
||||
AND c.contextlevel = :contextlevel";
|
||||
$params = [
|
||||
'userid' => $USER->id,
|
||||
'contextlevel' => CONTEXT_USER
|
||||
];
|
||||
$children = $DB->get_records_sql($sql, $params);
|
||||
|
||||
if ($children) {
|
||||
$useroptions = [];
|
||||
$useroptions[$USER->id] = fullname($USER);
|
||||
foreach ($children as $child) {
|
||||
$childcontext = context_user::instance($child->id);
|
||||
if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext)) {
|
||||
$useroptions[$child->id] = fullname($child);
|
||||
}
|
||||
if ($children = helper::get_children_of_user($USER->id)) {
|
||||
$useroptions = [
|
||||
$USER->id => fullname($USER)
|
||||
];
|
||||
foreach ($children as $key => $child) {
|
||||
$useroptions[$key] = fullname($child);
|
||||
}
|
||||
$mform->addElement('autocomplete', 'userid', get_string('requestfor', 'tool_dataprivacy'), $useroptions);
|
||||
$mform->addRule('userid', null, 'required', null, 'client');
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$filter = optional_param('filter', CONTEXT_COURSE, PARAM_INT);
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datadeletion.php');
|
||||
@@ -35,13 +37,19 @@ $title = get_string('datadeletion', 'tool_dataprivacy');
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
$table = new \tool_dataprivacy\output\expired_contexts_table($filter);
|
||||
$table->baseurl = $url;
|
||||
$table->baseurl->param('filter', $filter);
|
||||
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
$table = new \tool_dataprivacy\output\expired_contexts_table($filter);
|
||||
$table->baseurl = $url;
|
||||
$table->baseurl->param('filter', $filter);
|
||||
|
||||
$datadeletionpage = new \tool_dataprivacy\output\data_deletion_page($filter, $table);
|
||||
$datadeletionpage = new \tool_dataprivacy\output\data_deletion_page($filter, $table);
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->render($datadeletionpage);
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->render($datadeletionpage);
|
||||
} else {
|
||||
$dponamestring = implode (',', tool_dataprivacy\api::get_dpo_role_names());
|
||||
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
|
||||
echo $OUTPUT->notification($message, 'error');
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$contextlevel = optional_param('contextlevel', CONTEXT_SYSTEM, PARAM_INT);
|
||||
$contextid = optional_param('contextid', 0, PARAM_INT);
|
||||
|
||||
@@ -36,7 +38,12 @@ $title = get_string('dataregistry', 'tool_dataprivacy');
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->header();
|
||||
|
||||
$dataregistry = new tool_dataprivacy\output\data_registry_page($contextlevel, $contextid);
|
||||
|
||||
echo $output->render($dataregistry);
|
||||
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
$dataregistry = new tool_dataprivacy\output\data_registry_page($contextlevel, $contextid);
|
||||
echo $output->render($dataregistry);
|
||||
} else {
|
||||
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
|
||||
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
|
||||
echo $OUTPUT->notification($message, 'error');
|
||||
}
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
require_once("../../../config.php");
|
||||
require_once('lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
|
||||
|
||||
$title = get_string('datarequests', 'tool_dataprivacy');
|
||||
@@ -34,9 +36,45 @@ $title = get_string('datarequests', 'tool_dataprivacy');
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($title);
|
||||
|
||||
$requests = tool_dataprivacy\api::get_data_requests();
|
||||
$requestlist = new tool_dataprivacy\output\data_requests_page($requests);
|
||||
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $requestlistoutput->render($requestlist);
|
||||
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
$filtersapplied = optional_param_array('request-filters', [-1], PARAM_NOTAGS);
|
||||
$filterscleared = optional_param('filters-cleared', 0, PARAM_INT);
|
||||
if ($filtersapplied === [-1]) {
|
||||
// If there are no filters submitted, check if there is a saved filters from the user preferences.
|
||||
$filterprefs = get_user_preferences(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, null);
|
||||
if ($filterprefs && empty($filterscleared)) {
|
||||
$filtersapplied = json_decode($filterprefs);
|
||||
} else {
|
||||
$filtersapplied = [];
|
||||
}
|
||||
}
|
||||
// Save the current applied filters to the user preferences.
|
||||
set_user_preference(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, json_encode($filtersapplied));
|
||||
|
||||
$types = [];
|
||||
$statuses = [];
|
||||
foreach ($filtersapplied as $filter) {
|
||||
list($category, $value) = explode(':', $filter);
|
||||
switch($category) {
|
||||
case \tool_dataprivacy\local\helper::FILTER_TYPE:
|
||||
$types[] = $value;
|
||||
break;
|
||||
case \tool_dataprivacy\local\helper::FILTER_STATUS:
|
||||
$statuses[] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$table = new \tool_dataprivacy\output\data_requests_table(0, $statuses, $types, true);
|
||||
$table->baseurl = $url;
|
||||
|
||||
$requestlist = new tool_dataprivacy\output\data_requests_page($table, $filtersapplied);
|
||||
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $requestlistoutput->render($requestlist);
|
||||
} else {
|
||||
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
|
||||
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
|
||||
echo $OUTPUT->notification($message, 'error');
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -49,4 +49,22 @@ $capabilities = [
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'archetypes' => []
|
||||
],
|
||||
|
||||
// Capability for users to download the results of their own data request.
|
||||
'tool/dataprivacy:downloadownrequest' => [
|
||||
'riskbitmask' => 0,
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW
|
||||
]
|
||||
],
|
||||
|
||||
// Capability for administrators to download other people's data requests.
|
||||
'tool/dataprivacy:downloadallrequests' => [
|
||||
'riskbitmask' => RISK_PERSONAL,
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'archetypes' => []
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20180313" COMMENT="XMLDB file for Moodle tool/dataprivacy"
|
||||
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20180821" COMMENT="XMLDB file for Moodle tool/dataprivacy"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -12,7 +12,7 @@
|
||||
<FIELD NAME="commentsformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID the request is being made for"/>
|
||||
<FIELD NAME="requestedby" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the one making the request"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The current status of the data request"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The current status of the data request"/>
|
||||
<FIELD NAME="dpo" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the Data Protection Officer who is reviewing th request"/>
|
||||
<FIELD NAME="dpocomment" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="DPO's comments (e.g. reason for rejecting the request, etc.)"/>
|
||||
<FIELD NAME="dpocommentformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
@@ -98,7 +98,7 @@
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="2" DEFAULT="0" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
@@ -147,4 +147,4 @@
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
@@ -41,4 +41,12 @@ $messageproviders = [
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
]
|
||||
],
|
||||
|
||||
// Notify Data Protection Officer about exceptions.
|
||||
'notifyexceptions' => [
|
||||
'defaults' => [
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
],
|
||||
'capability' => 'tool/dataprivacy:managedatarequests'
|
||||
],
|
||||
];
|
||||
|
||||
@@ -43,6 +43,16 @@ $functions = [
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_mark_complete' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'mark_complete',
|
||||
'classpath' => '',
|
||||
'description' => 'Mark a user\'s general enquiry as complete',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_get_data_request' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'get_data_request',
|
||||
|
||||
@@ -42,5 +42,13 @@ $tasks = array(
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
), array(
|
||||
'classname' => 'tool_dataprivacy\task\delete_expired_requests',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* tool_dataprivacy plugin upgrade code
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Function to upgrade tool_dataprivacy.
|
||||
*
|
||||
* @param int $oldversion the version we are upgrading from
|
||||
* @return bool result
|
||||
*/
|
||||
function xmldb_tool_dataprivacy_upgrade($oldversion) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
if ($oldversion < 2018051405) {
|
||||
// Define table tool_dataprivacy_ctxexpired to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_ctxexpired');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_ctxexpired.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table tool_dataprivacy_ctxexpired.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('contextid', XMLDB_KEY_FOREIGN_UNIQUE, array('contextid'), 'context', array('id'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_ctxexpired.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table tool_dataprivacy_contextlist to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_contextlist');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_contextlist.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table tool_dataprivacy_contextlist.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_contextlist.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table tool_dataprivacy_ctxlst_ctx to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_ctxlst_ctx.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table tool_dataprivacy_ctxlst_ctx.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table tool_dataprivacy_rqst_ctxlst to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_rqst_ctxlst.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table tool_dataprivacy_rqst_ctxlst.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('requestid', XMLDB_KEY_FOREIGN, array('requestid'), 'tool_dataprivacy_request', array('id'));
|
||||
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
|
||||
$table->add_key('request_contextlist', XMLDB_KEY_UNIQUE, array('requestid', 'contextlistid'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define field lawfulbases to be added to tool_dataprivacy_purpose.
|
||||
$table = new xmldb_table('tool_dataprivacy_purpose');
|
||||
|
||||
// It is a required field. We initially define and add it as null and later update it to XMLDB_NOTNULL.
|
||||
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null, 'descriptionformat');
|
||||
|
||||
// Conditionally launch add field lawfulbases.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
|
||||
// Set a kind-of-random value to lawfulbasis field.
|
||||
$DB->set_field('tool_dataprivacy_purpose', 'lawfulbases', 'gdpr_art_6_1_a');
|
||||
|
||||
// We redefine it now as not null.
|
||||
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'descriptionformat');
|
||||
|
||||
// Launch change of nullability for field lawfulbases.
|
||||
$dbman->change_field_notnull($table, $field);
|
||||
}
|
||||
|
||||
// Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
|
||||
$table = new xmldb_table('tool_dataprivacy_purpose');
|
||||
$field = new xmldb_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null, 'lawfulbases');
|
||||
|
||||
// Conditionally launch add field sensitivedatareasons.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2018051405, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2018051406) {
|
||||
// Update completed delete requests to new delete status.
|
||||
$query = "UPDATE {tool_dataprivacy_request}
|
||||
SET status = :setstatus
|
||||
WHERE type = :type
|
||||
AND status = :wherestatus";
|
||||
$params = array(
|
||||
'setstatus' => 10, // Request deleted.
|
||||
'type' => 2, // Delete type.
|
||||
'wherestatus' => 5, // Request completed.
|
||||
);
|
||||
|
||||
$DB->execute($query, $params);
|
||||
|
||||
// Update completed data export requests to new download ready status.
|
||||
$params = array(
|
||||
'setstatus' => 8, // Request download ready.
|
||||
'type' => 1, // export type.
|
||||
'wherestatus' => 5, // Request completed.
|
||||
);
|
||||
|
||||
$DB->execute($query, $params);
|
||||
|
||||
upgrade_plugin_savepoint(true, 2018051406, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2018051407) {
|
||||
|
||||
// Changing precision of field status on table tool_dataprivacy_request to (2).
|
||||
$table = new xmldb_table('tool_dataprivacy_request');
|
||||
$field = new xmldb_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'requestedby');
|
||||
|
||||
// Launch change of precision for field status.
|
||||
$dbman->change_field_precision($table, $field);
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2018051407, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -25,6 +25,8 @@
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/defaults.php');
|
||||
$title = get_string('setdefaults', 'tool_dataprivacy');
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$id = optional_param('id', 0, PARAM_INT);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/editcategory.php', array('id' => $id));
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$id = optional_param('id', 0, PARAM_INT);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/editpurpose.php', array('id' => $id));
|
||||
|
||||
@@ -37,16 +37,20 @@ $string['cancelrequest'] = 'Cancel request';
|
||||
$string['cancelrequestconfirmation'] = 'Do you really want cancel this data request?';
|
||||
$string['categories'] = 'Categories';
|
||||
$string['category'] = 'Category';
|
||||
$string['category_help'] = 'A category in the data registry describes a type of data. A new category may be added, or if Inherit is selected, the data category from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > Site.';
|
||||
$string['categorycreated'] = 'Category created';
|
||||
$string['categorydefault'] = 'Default category';
|
||||
$string['categorydefault_help'] = 'The default category is the data category applied to any new instances. If Inherit is selected, the data category from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
|
||||
$string['categorieslist'] = 'List of data categories';
|
||||
$string['categoryupdated'] = 'Category updated';
|
||||
$string['close'] = 'Close';
|
||||
$string['compliant'] = 'Compliant';
|
||||
$string['confirmapproval'] = 'Do you really want to approve this data request?';
|
||||
$string['confirmcompletion'] = 'Do you really want to mark this user enquiry as complete?';
|
||||
$string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
|
||||
$string['confirmdenial'] = 'Do you really want deny this data request?';
|
||||
$string['contactdataprotectionofficer'] = 'Contact Data Protection Officer';
|
||||
$string['contactdataprotectionofficer_desc'] = 'Enabling this feature will provide a link for users to contact the site\'s Data Protection Officer through this site. This link will be shown on their profile page, and on the site\'s privacy policy page, as well. The link leads to a form in which the user can make a data request to the Data Protection Officer.';
|
||||
$string['contactdataprotectionofficer'] = 'Contact the privacy officer';
|
||||
$string['contactdataprotectionofficer_desc'] = 'If enabled, users will be able to contact the privacy officer and make a data request via a link on their profile page.';
|
||||
$string['contextlevelname10'] = 'Site';
|
||||
$string['contextlevelname30'] = 'Users';
|
||||
$string['contextlevelname40'] = 'Course categories';
|
||||
@@ -54,31 +58,38 @@ $string['contextlevelname50'] = 'Courses';
|
||||
$string['contextlevelname70'] = 'Activity modules';
|
||||
$string['contextlevelname80'] = 'Blocks';
|
||||
$string['contextpurposecategorysaved'] = 'Purpose and category saved.';
|
||||
$string['contactdpoviaprivacypolicy'] = 'Please contact the site\'s Data Protection Officer as described in the Privacy Policy';
|
||||
$string['contactdpoviaprivacypolicy'] = 'Please contact the privacy officer as described in the privacy policy.';
|
||||
$string['createcategory'] = 'Create data category';
|
||||
$string['createpurpose'] = 'Create data purpose';
|
||||
$string['datadeletion'] = 'Data deletion';
|
||||
$string['datadeletionpagehelp'] = 'This page lists the contexts that are already past their retention period and need to be confirmed for user data deletion. Once the selected contexts have been confirmed for deletion, the user data related to these contexts will be deleted on the next execution of the "Delete expired contexts" scheduled task.';
|
||||
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for children';
|
||||
$string['datadeletionpagehelp'] = 'Data for which the retention period has expired are listed here. Please review and confirm data deletion, which will then be executed by the "Delete expired contexts" scheduled task.';
|
||||
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for minors';
|
||||
$string['dataprivacy:managedatarequests'] = 'Manage data requests';
|
||||
$string['dataprivacy:managedataregistry'] = 'Manage data registry';
|
||||
$string['dataprivacy:downloadownrequest'] = 'Download your own exported data';
|
||||
$string['dataprivacy:downloadallrequests'] = 'Download exported data for everyone';
|
||||
$string['dataregistry'] = 'Data registry';
|
||||
$string['dataregistryinfo'] = 'The data registry enables categories (types of data) and purposes (the reasons for processing data) to be set for all content on the site - from users and courses down to activities and blocks. For each purpose, a retention period may be set. When a retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
|
||||
$string['datarequestcreatedforuser'] = 'Data request created for {$a}';
|
||||
$string['datarequestemailsubject'] = 'Data request: {$a}';
|
||||
$string['datarequests'] = 'Data requests';
|
||||
$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
|
||||
$string['daterequested'] = 'Date requested';
|
||||
$string['daterequesteddetail'] = 'Date requested:';
|
||||
$string['defaultsinfo'] = 'Default categories and purposes are applied to all newly created instances.';
|
||||
$string['deletecategory'] = 'Delete "{$a}" category';
|
||||
$string['deletecategorytext'] = 'Are you sure you want to delete "{$a}" category?';
|
||||
$string['deletecategory'] = 'Delete category';
|
||||
$string['deletecategorytext'] = 'Are you sure you want to delete the category \'{$a}\'?';
|
||||
$string['deleteexpiredcontextstask'] = 'Delete expired contexts';
|
||||
$string['deletepurpose'] = 'Delete "{$a}" purpose';
|
||||
$string['deletepurposetext'] = 'Are you sure you want to delete "{$a}" purpose?';
|
||||
$string['deleteexpireddatarequeststask'] = 'Delete files from completed data requests that have expired';
|
||||
$string['deletepurpose'] = 'Delete purpose';
|
||||
$string['deletepurposetext'] = 'Are you sure you want to delete the purpose \'{$a}\'?';
|
||||
$string['defaultssaved'] = 'Defaults saved';
|
||||
$string['deny'] = 'Deny';
|
||||
$string['denyrequest'] = 'Deny request';
|
||||
$string['download'] = 'Download';
|
||||
$string['dporolemapping'] = 'Data Protection Officer role mapping';
|
||||
$string['dporolemapping_desc'] = 'Select one or more roles that map to the Data Protection Officer role. Users with these roles will be able to manage data requests. This requires the selected role(s) to have the capability \'tool/dataprivacy:managedatarequests\'';
|
||||
$string['downloadexpireduser'] = 'Download has expired. Submit a new request if you wish to export your personal data.';
|
||||
$string['dporolemapping'] = 'Privacy officer role mapping';
|
||||
$string['dporolemapping_desc'] = 'The privacy officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a privacy officer role mapping option.';
|
||||
$string['editcategories'] = 'Edit categories';
|
||||
$string['editcategory'] = 'Edit category';
|
||||
$string['editcategories'] = 'Edit categories';
|
||||
@@ -89,19 +100,25 @@ $string['effectiveretentionperioduser'] = '{$a} (since the last time the user ac
|
||||
$string['emailsalutation'] = 'Dear {$a},';
|
||||
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
|
||||
$string['errorinvalidrequesttype'] = 'Invalid request type!';
|
||||
$string['errornocapabilitytorequestforothers'] = 'User {$a->requestedby} doesn\'t have the capability to make a data request on behalf of user {$a->userid}';
|
||||
$string['errornoexpiredcontexts'] = 'There are no expired contexts to process';
|
||||
$string['errorcontexthasunexpiredchildren'] = 'The context "{$a}" still has sub-contexts that have not yet expired. No contexts have been flagged for deletion.';
|
||||
$string['errorrequestalreadyexists'] = 'You already have an ongoing request.';
|
||||
$string['errorrequestnotfound'] = 'Request not found';
|
||||
$string['errorrequestnotwaitingforapproval'] = 'The request is not awaiting approval. Either it is not yet ready or it has already been processed.';
|
||||
$string['errorsendingmessagetodpo'] = 'An error was encountered while trying to send a message to {$a}.';
|
||||
$string['exceptionnotificationsubject'] = 'Exception occurred while processing privacy data';
|
||||
$string['exceptionnotificationbody'] = '<p>Exception occurred while calling <b>{$a->fullmethodname}</b>.<br>This means that plugin <b>{$a->component}</b> did not complete the processing of data. The following exception information may be passed on to the plugin developer:</p><pre>{$a->message}<br>
|
||||
|
||||
{$a->backtrace}</pre>';
|
||||
$string['expiredretentionperiodtask'] = 'Expired retention period';
|
||||
$string['expiry'] = 'Expiry';
|
||||
$string['expandplugin'] = 'Expand and collapse plugin.';
|
||||
$string['expandplugintype'] = 'Expand and collapse plugin type.';
|
||||
$string['explanationtitle'] = 'Icons used on this page and what they mean.';
|
||||
$string['external'] = 'External';
|
||||
$string['external'] = 'Additional';
|
||||
$string['externalexplanation'] = 'An additional plugin installed on this site.';
|
||||
$string['filteroption'] = '{$a->category}: {$a->name}';
|
||||
$string['frontpagecourse'] = 'Front page course';
|
||||
$string['gdpr_art_6_1_a_description'] = 'The data subject has given consent to the processing of his or her personal data for one or more specific purposes';
|
||||
$string['gdpr_art_6_1_a_name'] = 'Consent (GDPR Art. 6.1(a))';
|
||||
@@ -136,11 +153,15 @@ $string['gdpr_art_9_2_i_name'] = 'Public health (GDPR Art. 9.2(i))';
|
||||
$string['gdpr_art_9_2_j_description'] = 'Processing is necessary for archiving purposes in the public interest, scientific or historical research purposes or statistical purposes in accordance with Article 89(1) based on Union or Member State law which shall be proportionate to the aim pursued, respect the essence of the right to data protection and provide for suitable and specific measures to safeguard the fundamental rights and the interests of the data subject';
|
||||
$string['gdpr_art_9_2_j_name'] = 'Public interest, or scientific/historical/statistical research (GDPR Art. 9.2(j))';
|
||||
$string['hide'] = 'Collapse all';
|
||||
$string['httpwarning'] = 'Any data downloaded from this site may not be encrypted. Please contact your system administrator and request that they install SSL on this site.';
|
||||
$string['inherit'] = 'Inherit';
|
||||
$string['lawfulbases'] = 'Lawful bases';
|
||||
$string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
|
||||
$string['markcomplete'] = 'Mark as complete';
|
||||
$string['markedcomplete'] = 'Your enquiry has been marked as complete by the privacy officer.';
|
||||
$string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
|
||||
$string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
|
||||
$string['messageprovider:notifyexceptions'] = 'Data requests exceptions notifications';
|
||||
$string['message'] = 'Message';
|
||||
$string['messagelabel'] = 'Message:';
|
||||
$string['moduleinstancename'] = '{$a->instancename} ({$a->modulename})';
|
||||
@@ -150,12 +171,13 @@ $string['nameemail'] = '{$a->name} ({$a->email})';
|
||||
$string['nchildren'] = '{$a} children';
|
||||
$string['newrequest'] = 'New request';
|
||||
$string['nodatarequests'] = 'There are no data requests';
|
||||
$string['nodatarequestsmatchingfilter'] = 'There are no data requests matching the given filter';
|
||||
$string['noactivitiestoload'] = 'No activities';
|
||||
$string['noassignedroles'] = 'No assigned roles in this context';
|
||||
$string['noblockstoload'] = 'No blocks';
|
||||
$string['nocategories'] = 'There are no categories yet';
|
||||
$string['nocoursestoload'] = 'No activities';
|
||||
$string['noexpiredcontexts'] = 'There are no expired contexts in this level that need to be confirmed for deletion.';
|
||||
$string['noexpiredcontexts'] = 'This context level has no data for which the retention period has expired.';
|
||||
$string['nopersonaldatarequests'] = 'You don\'t have any personal data requests';
|
||||
$string['nopurposes'] = 'There are no purposes yet';
|
||||
$string['nosubjectaccessrequests'] = 'There are no data requests that you need to act on';
|
||||
@@ -164,42 +186,55 @@ $string['notset'] = 'Not set (use the default value)';
|
||||
$string['pluginregistry'] = 'Plugin privacy registry';
|
||||
$string['pluginregistrytitle'] = 'Plugin privacy compliance registry';
|
||||
$string['privacy'] = 'Privacy';
|
||||
$string['privacyofficeronly'] = 'Only users who are assigned a privacy officer role ({$a}) have access to this content';
|
||||
$string['privacy:metadata:preference:tool_dataprivacy_request-filters'] = 'The filters currently applied to the data requests page.';
|
||||
$string['privacy:metadata:request'] = 'Information from personal data requests (subject access and deletion requests) made for this site.';
|
||||
$string['privacy:metadata:request:comments'] = 'Any user comments accompanying the request.';
|
||||
$string['privacy:metadata:request:userid'] = 'The ID of the user to whom the request belongs';
|
||||
$string['privacy:metadata:request:requestedby'] = 'The ID of the user making the request, if made on behalf of another user.';
|
||||
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s respective privacy officer regarding the request.';
|
||||
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s privacy officer regarding the request.';
|
||||
$string['privacy:metadata:request:timecreated'] = 'The timestamp indicating when the request was made by the user.';
|
||||
$string['privacyrequestexpiry'] = 'Data request expiry';
|
||||
$string['privacyrequestexpiry_desc'] = 'The time that approved data requests will be available for download before expiring. If set to zero, then there is no time limit.';
|
||||
$string['protected'] = 'Protected';
|
||||
$string['protectedlabel'] = 'The retention of this data has a higher legal precedent over a user\'s request to be forgotten. This data will only be deleted after the retention period has expired.';
|
||||
$string['purpose'] = 'Purpose';
|
||||
$string['purpose_help'] = 'The purpose describes the reason for processing the data. A new purpose may be added, or if Inherit is selected, the purpose from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
|
||||
$string['purposecreated'] = 'Purpose created';
|
||||
$string['purposedefault'] = 'Default purpose';
|
||||
$string['purposedefault_help'] = 'The default purpose is the purpose which is applied to any new instances. If Inherit is selected, the purpose from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
|
||||
$string['purposes'] = 'Purposes';
|
||||
$string['purposeslist'] = 'List of data purposes';
|
||||
$string['purposeupdated'] = 'Purpose updated';
|
||||
$string['replyto'] = 'Reply to';
|
||||
$string['requestactions'] = 'Actions';
|
||||
$string['requestapproved'] = 'The request has been approved';
|
||||
$string['requestby'] = 'Requested by';
|
||||
$string['requestbydetail'] = 'Requested by:';
|
||||
$string['requestcomments'] = 'Comments';
|
||||
$string['requestcomments_help'] = 'Please feel free to provide more details about your request';
|
||||
$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
|
||||
$string['requestdenied'] = 'The request has been denied';
|
||||
$string['requestemailintro'] = 'You have received a data request:';
|
||||
$string['requestfor'] = 'Requesting for';
|
||||
$string['requestmarkedcomplete'] = 'The request has been marked as complete';
|
||||
$string['requeststatus'] = 'Status';
|
||||
$string['requestsubmitted'] = 'Your request has been submitted to the site\'s Data Protection Officer';
|
||||
$string['requestsubmitted'] = 'Your request has been submitted to the privacy officer';
|
||||
$string['requesttype'] = 'Type';
|
||||
$string['requesttype_help'] = 'Select the reason why you would like to contact the site\'s Data Protection Officer';
|
||||
$string['requesttypeuser'] = '{$a->typename} ({$a->user})';
|
||||
$string['requesttype_help'] = 'Select the reason for contacting the privacy officer. Be aware that deletion of all personal data will result in you no longer being able to log in to the site.';
|
||||
$string['requesttypedelete'] = 'Delete all of my personal data';
|
||||
$string['requesttypedeleteshort'] = 'Delete';
|
||||
$string['requesttypeexport'] = 'Export all of my personal data';
|
||||
$string['requesttypeexportshort'] = 'Export';
|
||||
$string['requesttypeothers'] = 'General inquiry';
|
||||
$string['requesttypeothersshort'] = 'Others';
|
||||
$string['requesttypeothersshort'] = 'Message';
|
||||
$string['requiresattention'] = 'Requires attention.';
|
||||
$string['requiresattentionexplanation'] = 'This plugin does not implement the Moodle privacy API. If this plugin stores any personal data it will not be able to be exported or deleted through Moodle\'s privacy system.';
|
||||
$string['resultdeleted'] = 'You recently requested to have your account and personal data in {$a} to be deleted. This process has been completed and you will no longer be able to log in.';
|
||||
$string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
|
||||
$string['reviewdata'] = 'Review data';
|
||||
$string['retentionperiod'] = 'Retention period';
|
||||
$string['retentionperiod_help'] = 'The retention period specifies the length of time that data should be kept for. When the retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
|
||||
$string['retentionperiodnotdefined'] = 'No retention period was defined';
|
||||
$string['retentionperiodzero'] = 'No retention period';
|
||||
$string['send'] = 'Send';
|
||||
@@ -210,12 +245,16 @@ $string['statusapproved'] = 'Approved';
|
||||
$string['statusawaitingapproval'] = 'Awaiting approval';
|
||||
$string['statuscancelled'] = 'Cancelled';
|
||||
$string['statuscomplete'] = 'Complete';
|
||||
$string['statusready'] = 'Download ready';
|
||||
$string['statusdeleted'] = 'Deleted';
|
||||
$string['statusdetail'] = 'Status:';
|
||||
$string['statusexpired'] = 'Expired';
|
||||
$string['statuspreprocessing'] = 'Pre-processing';
|
||||
$string['statusprocessing'] = 'Processing';
|
||||
$string['statuspending'] = 'Pending';
|
||||
$string['statusrejected'] = 'Rejected';
|
||||
$string['subjectscope'] = 'Subject scope';
|
||||
$string['subjectscope_help'] = 'The subject scope lists the roles which may be assigned in this context.';
|
||||
$string['user'] = 'User';
|
||||
$string['viewrequest'] = 'View the request';
|
||||
$string['visible'] = 'Expand all';
|
||||
|
||||
@@ -185,26 +185,23 @@ function tool_dataprivacy_output_fragment_contextlevel_form($args) {
|
||||
* @return bool Returns false if we don't find a file.
|
||||
*/
|
||||
function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
|
||||
global $USER;
|
||||
|
||||
if ($context->contextlevel == CONTEXT_USER) {
|
||||
// Make sure the user is logged in.
|
||||
require_login(null, false);
|
||||
|
||||
// Validate the user downloading this archive.
|
||||
$usercontext = context_user::instance($USER->id);
|
||||
// The user downloading this is not the user the archive has been prepared for. Check if it's the requester (e.g. parent).
|
||||
if ($usercontext->instanceid !== $context->instanceid) {
|
||||
// Get the data request ID. This should be the first element of the $args array.
|
||||
$itemid = $args[0];
|
||||
// Fetch the data request object. An invalid ID will throw an exception.
|
||||
$datarequest = new \tool_dataprivacy\data_request($itemid);
|
||||
// Get the data request ID. This should be the first element of the $args array.
|
||||
$itemid = $args[0];
|
||||
// Fetch the data request object. An invalid ID will throw an exception.
|
||||
$datarequest = new \tool_dataprivacy\data_request($itemid);
|
||||
|
||||
// Check if the user is the requester and has the capability to make data requests for the target user.
|
||||
$candownloadforuser = has_capability('tool/dataprivacy:makedatarequestsforchildren', $context);
|
||||
if ($USER->id != $datarequest->get('requestedby') || !$candownloadforuser) {
|
||||
return false;
|
||||
}
|
||||
// Check if user is allowed to download it.
|
||||
if (!\tool_dataprivacy\api::can_download_data_request_for_user($context->instanceid, $datarequest->get('requestedby'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make the file unavailable if it has expired.
|
||||
if (\tool_dataprivacy\data_request::is_expired($datarequest)) {
|
||||
send_file_not_found();
|
||||
}
|
||||
|
||||
// All good. Serve the exported data.
|
||||
|
||||
@@ -55,7 +55,7 @@ $PAGE->set_title($title);
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($title);
|
||||
|
||||
$requests = tool_dataprivacy\api::get_data_requests($USER->id);
|
||||
$requests = tool_dataprivacy\api::get_data_requests($USER->id, [], [], 'timecreated DESC');
|
||||
$requestlist = new tool_dataprivacy\output\my_data_requests_page($requests);
|
||||
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $requestlistoutput->render($requestlist);
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$contextlevel = optional_param('contextlevel', CONTEXT_SYSTEM, PARAM_INT);
|
||||
$contextid = optional_param('contextid', 0, PARAM_INT);
|
||||
|
||||
@@ -36,11 +38,18 @@ $title = get_string('pluginregistry', 'tool_dataprivacy');
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->header();
|
||||
|
||||
// Get data!
|
||||
$metadatatool = new \tool_dataprivacy\metadata_registry();
|
||||
$metadata = $metadatatool->get_registry_metadata();
|
||||
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
// Get data!
|
||||
$metadatatool = new \tool_dataprivacy\metadata_registry();
|
||||
$metadata = $metadatatool->get_registry_metadata();
|
||||
|
||||
$dataregistry = new tool_dataprivacy\output\data_registry_compliance_page($metadata);
|
||||
$dataregistry = new tool_dataprivacy\output\data_registry_compliance_page($metadata);
|
||||
|
||||
echo $output->render($dataregistry);
|
||||
} else {
|
||||
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
|
||||
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
|
||||
echo $OUTPUT->notification($message, 'error');
|
||||
}
|
||||
|
||||
echo $output->render($dataregistry);
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$url = new moodle_url("/admin/tool/dataprivacy/purposes.php");
|
||||
$title = get_string('editpurposes', 'tool_dataprivacy');
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ if ($hassiteconfig) {
|
||||
new lang_string('contactdataprotectionofficer_desc', 'tool_dataprivacy'), 0)
|
||||
);
|
||||
|
||||
// Set days approved data requests will be accessible. 1 week default.
|
||||
$privacysettings->add(new admin_setting_configduration('tool_dataprivacy/privacyrequestexpiry',
|
||||
new lang_string('privacyrequestexpiry', 'tool_dataprivacy'),
|
||||
new lang_string('privacyrequestexpiry_desc', 'tool_dataprivacy'),
|
||||
WEEKSECS, 1));
|
||||
|
||||
// Fetch roles that are assignable.
|
||||
$assignableroles = get_assignable_roles(context_system::instance());
|
||||
|
||||
@@ -49,7 +55,7 @@ if ($hassiteconfig) {
|
||||
}
|
||||
}
|
||||
if (!empty($roles)) {
|
||||
$privacysettings->add(new admin_setting_configmultiselect('tool_dataprivacy/dporoles',
|
||||
$privacysettings->add(new admin_setting_configmulticheckbox('tool_dataprivacy/dporoles',
|
||||
new lang_string('dporolemapping', 'tool_dataprivacy'),
|
||||
new lang_string('dporolemapping_desc', 'tool_dataprivacy'), null, $roles)
|
||||
);
|
||||
@@ -57,22 +63,25 @@ if ($hassiteconfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// Link that leads to the data requests management page.
|
||||
$ADMIN->add('privacy', new admin_externalpage('datarequests', get_string('datarequests', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/datarequests.php'), 'tool/dataprivacy:managedatarequests')
|
||||
);
|
||||
// Restrict config links to the DPO.
|
||||
if (tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
// Link that leads to the data requests management page.
|
||||
$ADMIN->add('privacy', new admin_externalpage('datarequests', get_string('datarequests', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/datarequests.php'), 'tool/dataprivacy:managedatarequests')
|
||||
);
|
||||
|
||||
// Link that leads to the data registry management page.
|
||||
$ADMIN->add('privacy', new admin_externalpage('dataregistry', get_string('dataregistry', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/dataregistry.php'), 'tool/dataprivacy:managedataregistry')
|
||||
);
|
||||
// Link that leads to the data registry management page.
|
||||
$ADMIN->add('privacy', new admin_externalpage('dataregistry', get_string('dataregistry', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/dataregistry.php'), 'tool/dataprivacy:managedataregistry')
|
||||
);
|
||||
|
||||
// Link that leads to the review page of expired contexts that are up for deletion.
|
||||
$ADMIN->add('privacy', new admin_externalpage('datadeletion', get_string('datadeletion', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/datadeletion.php'), 'tool/dataprivacy:managedataregistry')
|
||||
);
|
||||
// Link that leads to the review page of expired contexts that are up for deletion.
|
||||
$ADMIN->add('privacy', new admin_externalpage('datadeletion', get_string('datadeletion', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/datadeletion.php'), 'tool/dataprivacy:managedataregistry')
|
||||
);
|
||||
|
||||
// Link that leads to the other data registry management page.
|
||||
$ADMIN->add('privacy', new admin_externalpage('pluginregistry', get_string('pluginregistry', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/pluginregistry.php'), 'tool/dataprivacy:managedataregistry')
|
||||
);
|
||||
// Link that leads to the other data registry management page.
|
||||
$ADMIN->add('privacy', new admin_externalpage('pluginregistry', get_string('pluginregistry', 'tool_dataprivacy'),
|
||||
new moodle_url('/admin/tool/dataprivacy/pluginregistry.php'), 'tool/dataprivacy:managedataregistry')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.nav-pills .nav-pills {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.data-registry > .top-nav > * {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
/*Extra attribute selection to have preference over bs2's .moodle-actionmenu[data-enhance] */
|
||||
.data-registry > .top-nav > .singlebutton,
|
||||
.data-registry > .top-nav > .moodle-actionmenu[data-owner='dataregistry-actions'] {
|
||||
@@ -12,3 +14,17 @@
|
||||
height: 70vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
dd a.contactdpo {
|
||||
/* Reverting dd's left margin */
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.card dd a.contactdpo {
|
||||
/* Reverting dd's left margin */
|
||||
margin-left: inherit;
|
||||
}
|
||||
|
||||
[data-region="data-requests-table"] .moodle-actionmenu {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<div data-region="categories" class="m-t-3 m-b-1">
|
||||
<h3>{{#str}}categories, tool_dataprivacy{{/str}}</h3>
|
||||
<div class="m-y-1">
|
||||
<button class="btn btn-secondary" data-add-element="category">
|
||||
<button class="btn btn-secondary" data-add-element="category" title="{{#str}}addcategory, tool_dataprivacy{{/str}}">
|
||||
{{#pix}}t/add, moodle, {{#str}}addcategory, tool_dataprivacy{{/str}}{{/pix}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
}
|
||||
}}
|
||||
|
||||
<div classs="container-fluid">
|
||||
<div class="container-fluid">
|
||||
<hr />
|
||||
<div class="row">
|
||||
{{#compliant}}
|
||||
@@ -64,17 +64,17 @@
|
||||
</div>
|
||||
|
||||
{{#compliant}}
|
||||
<div class="hide" data-section="{{raw_component}}" aria-expanded="false">
|
||||
<div class="hide" data-section="{{raw_component}}" aria-expanded="false" role="contentinfo">
|
||||
{{#metadata}}
|
||||
<hr />
|
||||
<div class="p-l-3">
|
||||
<dl class="row">
|
||||
<dt class="span3 col-xs-3">
|
||||
{{#link}}
|
||||
<a href="#{{name}}"><h5 style="word-wrap:break-word">{{name}}</h5></a>
|
||||
<a href="#{{name}}"><strong style="word-wrap:break-word">{{name}}</strong></a>
|
||||
{{/link}}
|
||||
{{^link}}
|
||||
<h5 style="word-wrap:break-word">{{name}}</h5>
|
||||
<strong style="word-wrap:break-word">{{name}}</strong>
|
||||
{{/link}}
|
||||
<div class="small text-muted" style="word-wrap:break-word">{{type}}</div>
|
||||
</dt>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
}}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="row m-b-2">
|
||||
<label class="col-md-3 span3 col-form-label">{{#str}}replyto, tool_dataprivacy{{/str}}</label>
|
||||
<div class="col-md-9 span9 col-form-label">{{replytoemail}}</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -33,7 +33,7 @@
|
||||
}
|
||||
}}
|
||||
<div class="data-registry">
|
||||
<div class="top-nav">
|
||||
<div class="top-nav d-flex">
|
||||
{{#defaultsbutton}}
|
||||
{{> core/action_link}}
|
||||
{{/defaultsbutton}}
|
||||
@@ -41,6 +41,11 @@
|
||||
{{> core/action_menu}}
|
||||
{{/actions}}
|
||||
</div>
|
||||
{{#info}}
|
||||
<div class="m-t-1">
|
||||
{{> core/notification_info}}
|
||||
</div>
|
||||
{{/info}}
|
||||
{{#nosystemdefaults}}
|
||||
<div class="m-t-1">
|
||||
{{> core/notification_warning}}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<h3 id="{{plugin_type_raw}}">{{#pix}}t/collapsed, moodle, {{#str}}expandplugintype, tool_dataprivacy{{/str}}{{/pix}}{{plugin_type}}</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hide p-b-1" data-plugintarget="{{plugin_type_raw}}" aria-expanded="false">
|
||||
<div class="hide p-b-1" data-plugintarget="{{plugin_type_raw}}" aria-expanded="false" role="contentinfo">
|
||||
{{#plugins}}
|
||||
{{> tool_dataprivacy/component_status}}
|
||||
{{/plugins}}
|
||||
|
||||
@@ -47,59 +47,68 @@
|
||||
"datarequestsurl": "#"
|
||||
}
|
||||
}}
|
||||
<style>
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
padding: 0.5em;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>{{#str}}emailsalutation, tool_dataprivacy, {{dponame}}{{/str}}</p>
|
||||
<p>{{#str}}requestemailintro, tool_dataprivacy{{/str}}</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requesttype, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requesttype}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requestfor, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requestfor}}
|
||||
</td>
|
||||
</tr>
|
||||
{{^forself}}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requestby, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requestedby}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/forself}}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requestcomments, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{{requestcomments}}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}daterequested, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requestdate}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
<a href="{{datarequestsurl}}">{{#str}}viewrequest, tool_dataprivacy{{/str}}</a>
|
||||
</div>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
padding: 0.5em;
|
||||
}
|
||||
</style>
|
||||
<title>{{#str}}datarequestemailsubject, tool_dataprivacy, {{requesttype}}{{/str}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>{{#str}}emailsalutation, tool_dataprivacy, {{dponame}}{{/str}}</p>
|
||||
<p>{{#str}}requestemailintro, tool_dataprivacy{{/str}}</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requesttype, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requesttype}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requestfor, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requestfor}}
|
||||
</td>
|
||||
</tr>
|
||||
{{^forself}}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requestby, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requestedby}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/forself}}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}requestcomments, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{{requestcomments}}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{#str}}daterequested, tool_dataprivacy{{/str}}
|
||||
</th>
|
||||
<td>
|
||||
{{requestdate}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
<a href="{{datarequestsurl}}">{{#str}}viewrequest, tool_dataprivacy{{/str}}</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -30,11 +30,17 @@
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"title": "Data request modal title"
|
||||
}
|
||||
}}
|
||||
{{< core/modal }}
|
||||
{{$footer}}
|
||||
<button type="button" class="btn btn-primary" data-action="approve">{{#str}} approve, tool_dataprivacy {{/str}}</button>
|
||||
<button type="button" class="btn btn-secondary" data-action="deny">{{#str}} deny, tool_dataprivacy {{/str}}</button>
|
||||
{{#approvedeny}}
|
||||
<button type="button" class="btn btn-primary" data-action="approve">{{#str}} approve, tool_dataprivacy {{/str}}</button>
|
||||
<button type="button" class="btn btn-secondary" data-action="deny">{{#str}} deny, tool_dataprivacy {{/str}}</button>
|
||||
{{/approvedeny}}
|
||||
{{#canmarkcomplete}}
|
||||
<button type="button" class="btn btn-primary" data-action="complete">{{#str}} markcomplete, tool_dataprivacy {{/str}}</button>
|
||||
{{/canmarkcomplete}}
|
||||
{{/footer}}
|
||||
{{/ core/modal }}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user