Compare commits

..

552 Commits

Author SHA1 Message Date
Eloy Lafuente (stronk7) 46574904af Moodle release 3.5 2018-05-16 18:44:30 +02:00
Eloy Lafuente (stronk7) 4c8005a0ff NOBUG: Change travis.yml to MOODLE_35_STABLE 2018-05-16 18:44:30 +02:00
Eloy Lafuente (stronk7) 57cc5dc750 Merge branch 'MDL-62472-master' of git://github.com/bmbrands/moodle 2018-05-16 18:29:46 +02:00
Marina Glancy 897a492729 MDL-62469 qtype_calculated: check remaining placeholders, see MDL-62275 2018-05-16 17:20:38 +02:00
Bas Brands a6c45af354 MDL-62472 Theme boost: update upgrade.txt for boost changes 2018-05-16 12:57:38 +02:00
David Monllao 36960958ad Merge branch 'MDL-62456_master' of git://github.com/markn86/moodle 2018-05-16 08:56:33 +02:00
Andrew Nicols 798f0f1da9 Merge branch 'wip-MDL-62147-master-4' of git://github.com/marinaglancy/moodle 2018-05-16 14:27:19 +08:00
Jun Pataleta 6c32ea4631 Merge branch 'MDL-62463-master' of git://github.com/andrewnicols/moodle 2018-05-16 14:22:05 +08:00
Frédéric Massart 554e6a6562 MDL-62147 core_grades: Report contexts and data related to scales 2018-05-16 13:57:26 +08:00
Marina Glancy 372ea5502c MDL-62147 core_grades: Declare metadata of scales and import tables 2018-05-16 13:57:25 +08:00
Marina Glancy 14f7296436 MDL-62147 portfolio: add missing tables to privacy provider 2018-05-16 13:57:22 +08:00
Marina Glancy 004fd4259e MDL-62147 privacy: unittest ensures that all tables covered 2018-05-16 13:57:19 +08:00
Andrew Nicols 39da8c1a35 MDL-62463 mod_glossary: Fix SQL query
The query was doing:
WHERE c.id ... AND ... OR ... OR ...

Which equates to:
WHERE (c.id ... AND ...) OR ... OR ...

Adding parens to:
WHERE (c.id ... AND (... OR ... OR ...))
2018-05-16 13:51:32 +08:00
Marina Glancy d1620c57f9 MDL-62147 privacy: corrections to tables, temporary tables 2018-05-16 12:57:19 +08:00
Jake Dallimore dc69734963 Merge branch 'MDL-62134-master' of git://github.com/andrewnicols/moodle 2018-05-16 12:39:27 +08:00
Andrew Nicols 8da25ca04c MDL-62134 tool_dataprivacy: Remove reference to old manager 2018-05-16 12:27:00 +08:00
Mark Nelson ad7f143897 MDL-62456 mod_lti: add missing add_external_location_link call 2018-05-16 12:22:34 +08:00
Jake Dallimore 0e156326cc Merge branch 'MDL-62134-master' of git://github.com/andrewnicols/moodle 2018-05-16 12:01:49 +08:00
Andrew Nicols 8760b7335b MDL-62134 tool_dataprivacy: Add a manager_observer 2018-05-16 11:52:46 +08:00
Andrew Nicols 22c0a30888 MDL-62134 core_privacy: Allow for a failure handler 2018-05-16 11:52:46 +08:00
Andrew Nicols 3e3e5cdbb4 Merge branch 'MDL-62426-master' of https://github.com/snake/moodle 2018-05-16 10:44:06 +08:00
Jake Dallimore 72580c0832 MDL-62426 core_enrol: control enrolment subcontexts at the provider
Added a function to the core_enrol provider allowing enrol plugins
to set their subcontext relative to a fixed 'enrolments' parent dir.
2018-05-16 10:22:15 +08:00
Jake Dallimore 37e1069289 MDL-62426 enrol_flatfile: make provider a plugin provider
The enrol_flatfile table can contain userdata relating to pending
enrolments, so this should be exported and deleted as necessary.
2018-05-16 09:37:33 +08:00
Marina Glancy 3f18d2af9a MDL-62134 tool_dataprivacy: privacy manager wrapper
If exception occurs in one plugin implementation do not fail the whole job but
instead send a message to DPOs with the exception details
2018-05-16 09:02:01 +08:00
Marina Glancy 65abf2a37c MDL-62134 privacy: consistantly call components methods 2018-05-16 09:02:01 +08:00
Eloy Lafuente (stronk7) 51c17c381d Merge branch 'MDL-62447_master' of git://github.com/dmonllao/moodle 2018-05-16 01:26:39 +02:00
Eloy Lafuente (stronk7) 3811659bc7 Merge branch 'MDL-62433-master' of git://github.com/sarjona/moodle 2018-05-15 23:30:46 +02:00
Eloy Lafuente (stronk7) cd0f1aa403 Merge branch 'MDL-62448-master' of git://github.com/sarjona/moodle 2018-05-15 17:29:06 +02:00
Eloy Lafuente (stronk7) 12ef4a1219 Merge branch 'MDL-62419-master' of git://github.com/bmbrands/moodle 2018-05-15 16:56:37 +02:00
Sara Arjona 9d594541b6 MDL-62448 block_rss_client: Export all feeds from Privacy API 2018-05-15 14:25:27 +02:00
Sara Arjona 68ed91b7af MDL-62433 tool_policy: Review language strings for consistency
All credit goes to Helen Foster. Thanks! :-*
2018-05-15 14:21:03 +02:00
Sara Arjona 5e84f521fa MDL-62433 tool_policy: Some minor fixes to Privacy API implementation
- Export files for agreements too.
- Add versionid to the export path to avoid collision if version revision
is not defined.
2018-05-15 14:07:31 +02:00
Andrew Nicols f63745eabd MDL-62433 tool_policy: Adjustments to the data export 2018-05-15 14:07:31 +02:00
Sara Arjona 827c8e98ac MDL-62433 tool_policy: Review privacy provider for adding versions 2018-05-15 14:07:31 +02:00
David Monllao bcd677bc6a Merge branch 'wip-MDL-62445-master' of git://github.com/marinaglancy/moodle 2018-05-15 13:33:25 +02:00
David Monllao 85218c28e9 Merge branch 'MDL-62228_master' of git://github.com/markn86/moodle 2018-05-15 13:31:07 +02:00
David Monllao 7f9b187994 MDL-62447 user: Fix component name typo 2018-05-15 10:41:57 +02:00
Bas Brands b188407bdc MDL-62419 Theme boost: use sr-only() mixin for .accesshide 2018-05-15 10:39:09 +02:00
David Monllao dc71a8b50e Moodle release 3.5rc1 2018-05-15 09:42:49 +02:00
David Monllao 096b3723bb Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2018-05-15 09:42:47 +02:00
Andrew Nicols 76578cf3f6 Merge branch 'MDL-62444-master' of git://github.com/zig-moodle/moodle 2018-05-15 11:38:32 +08:00
Andrew Nicols 7831f2785d Merge branch 'MDL-62428_master' of git://github.com/markn86/moodle 2018-05-15 10:58:45 +08:00
Marina Glancy 8880510d40 MDL-62445 tool_privacy: pass correct user to message_send 2018-05-15 10:27:52 +08:00
Jun Pataleta 2f48e08c46 Merge branch 'MDL-62434-master' of git://github.com/bmbrands/moodle 2018-05-15 10:06:57 +08:00
Zig Tan 0061a6ae67 MDL-62444 mod_assignment: Fix missing lang string. 2018-05-15 09:50:11 +08:00
Jun Pataleta d060e0b80f Merge branch 'MDL-62432-master' of git://github.com/andrewnicols/moodle 2018-05-15 09:34:18 +08:00
Andrew Nicols 63b0f6276c MDL-62432 quiz: Check contexts more carefully 2018-05-15 09:25:51 +08:00
Eloy Lafuente (stronk7) 05c87e7bb4 Merge branch 'MDL-62430_master' of git://github.com/dmonllao/moodle 2018-05-15 02:56:00 +02:00
AMOS bot 3a959aa800 Automatically generated installer lang files 2018-05-15 00:06:16 +00:00
David Monllao 641d878d08 MDL-62430 block_html: Check that block instances are block_html 2018-05-14 17:06:57 +02:00
David Monllao 014335fb2b Merge branch 'MDL-62370-master' of git://github.com/andrewnicols/moodle 2018-05-14 16:00:32 +02:00
Andrew Nicols 0668ba779a Merge branch 'MDL-62356-master' of git://github.com/cescobedo/moodle 2018-05-14 20:50:23 +08:00
Andrew Nicols a61cd7efef Merge branch 'MDL-62357-master' of git://github.com/cescobedo/moodle 2018-05-14 20:43:02 +08:00
Andrew Nicols 502344a904 MDL-62370 core_privacy: Directory-less subsystems are compliant 2018-05-14 19:59:43 +08:00
Mark Nelson df1714f25a MDL-62228 core: remove unnecessary context check in privacy providers 2018-05-14 18:33:29 +08:00
Marina Glancy 3960ad53a7 MDL-62430 privacy: validate context when deleting all user data 2018-05-14 18:13:27 +08:00
David Monllao 4557b4e065 Merge branch 'MDL-62414-master' of git://github.com/junpataleta/moodle 2018-05-14 11:44:13 +02:00
David Monllao 05dcf35796 Merge branch 'MDL-62425-master' of git://github.com/andrewnicols/moodle 2018-05-14 10:57:14 +02:00
cescobedo 267effaaa2 MDL-62356 enrol_meta: Add privacy implementation for enrol_meta 2018-05-14 10:55:52 +02:00
cescobedo 30f3ea7fe1 MDL-62357 enrol_cohort: Add privacy implementation for enrol_cohort 2018-05-14 10:49:16 +02:00
David Monllao 3558897fc3 Merge branch 'MDL-62365-master' of git://github.com/bmbrands/moodle 2018-05-14 10:39:41 +02:00
Bas Brands b5f402ad73 MDL-62434 Theme boost: fix popover arrows above/under popups 2018-05-14 10:36:02 +02:00
David Monllao 62b94b486e Merge branch 'MDL-62418-master' of git://github.com/mihailges/moodle 2018-05-14 10:17:58 +02:00
Andrew Nicols ef0f37ca16 MDL-62425 core: Add privacy implementation 2018-05-14 15:41:36 +08:00
Andrew Nicols b5b86c298c MDL-62425 tool_dataprivacy: Add core to the list of components 2018-05-14 15:41:30 +08:00
Andrew Nicols 66f2da2e0a MDL-62425 core_privacy: Add core to the list of components 2018-05-14 15:40:56 +08:00
Bas Brands 04b1bea112 MDL-62365 Theme Boost: default navigation item to empty
- use fa-fw as default navigation item (empty)
    - change indentation for 2nd level dropdown items
2018-05-14 09:08:42 +02:00
Mihail Geshoski 0bffcfccdd MDL-62418 tool_policy: Display blocks in user policies and agreements 2018-05-14 14:18:10 +08:00
Mark Nelson 8e24041287 MDL-62428 core: fix incorrect early return in privacy providers 2018-05-14 13:06:29 +08:00
Jake Dallimore f8630a8170 Merge branch 'MDL-62420-master' of git://github.com/andrewnicols/moodle 2018-05-14 11:57:52 +08:00
Jun Pataleta cea21312f9 Merge branch 'MDL-62153-master' of git://github.com/bmbrands/moodle 2018-05-14 11:57:01 +08:00
Jake Dallimore c7a2ed73b7 Merge branch 'MDL-62016_master' of git://github.com/dmonllao/moodle 2018-05-14 11:05:45 +08:00
Andrew Nicols 882d2787ba Merge branch 'MDL-62359-master' of git://github.com/bmbrands/moodle 2018-05-14 10:37:57 +08:00
Andrew Nicols 54ef15dac8 Merge branch 'MDL-62413-master' of git://github.com/junpataleta/moodle 2018-05-14 10:16:27 +08:00
Jake Dallimore e995166863 Merge branch 'MDL-62393-master' of git://github.com/andrewnicols/moodle 2018-05-14 09:37:34 +08:00
Eloy Lafuente (stronk7) a890f2ecfa Merge branch 'MDL-62418-master' of git://github.com/sarjona/moodle 2018-05-14 03:01:29 +02:00
Andrew Nicols af09390773 MDL-62420 enrol_lti: Fix random unit test fail in privacy 2018-05-14 08:43:15 +08:00
Eloy Lafuente (stronk7) 24bd0bc3b3 Merge branch 'MDL-62371-master' of git://github.com/andrewnicols/moodle 2018-05-14 02:37:38 +02:00
Sara Arjona 5bf7f59a19 MDL-62418 tool_policy: Redirect always to home when agreed all policies 2018-05-13 21:26:47 +02:00
Sara Arjona 15ed7d938e MDL-62418 tool_policy: Display blocks in Consent page when using clean 2018-05-13 20:42:04 +02:00
Eloy Lafuente (stronk7) 28ec28e026 Merge branch 'MDL-62251-master' of git://github.com/rezaies/moodle 2018-05-13 20:10:44 +02:00
Jun Pataleta 7bdb9d877d MDL-62409 tool_dataprivacy: Properly validate data request creation
Creating data requests
 * Add capability check when creating data requests for another user.
Ad-hoc task that processes pending data requests
 * Check if the requesting user has the capability to create the data
   request for another user. Reject otherwise.
Ad-hoc task that processes approved data requests
 * Validate that the requester can receive the notification about the
   data request processing results.
 * Do not send the confirmation link to DPOs/admins
2018-05-13 16:03:21 +02:00
David Monllao 4023f84b29 MDL-62016 tool_dataprivacy: More data registry information 2018-05-13 13:21:41 +02:00
Jun Pataleta 0f7fb98747 MDL-62299 tool_dataprivacy: Show requests correctly in mydatarequests
For DPOs viewing their personal data requests page (mydatarequests.php),
show only the requests they made for themselves and for their children.
2018-05-13 16:18:58 +08:00
Andrew Nicols 563852791d MDL-62371 core_privacy: Add support for progress trace 2018-05-13 15:45:24 +08:00
AMOS bot a2d976dd03 Automatically generated installer lang files 2018-05-13 00:06:28 +00:00
Andrew Nicols 823e08bf9d MDL-62393 message: Fix random unit test fails 2018-05-12 22:53:58 +08:00
Eloy Lafuente (stronk7) ab65b87f3d on-demand release 3.5beta+ 2018-05-12 12:57:26 +02:00
Eloy Lafuente (stronk7) 8b15175347 NOBUG: Fixed SVG browser compatibility 2018-05-12 12:57:24 +02:00
Jun Pataleta f946d87564 MDL-62414 tool_dataprivacy: Only show user's children in mydatarequests
* When coming from the DPO's "My data requests" page, don't show the
  picker for other users (unless the DPO is a parent of another user).
* When creating a data request as a DPO, show a more appropriate
  notification after redirecting back to the data requests page.
2018-05-12 10:19:47 +08:00
Jun Pataleta 2389ce39ac MDL-62413 tool_dataprivacy: Let users cancel data request made for them 2018-05-12 10:18:38 +08:00
Eloy Lafuente (stronk7) 1906a73574 MDL-61973 editor_atto: apply string changes to behat tests 2018-05-12 01:24:26 +02:00
Eloy Lafuente (stronk7) 5aa93d1b1f MDL-62275 qtype_calculatedmulti: Fix typo 2018-05-11 21:16:41 +02:00
David Monllao 9fa74e9374 MDL-62029 core_course: Site format is not a standard plugin 2018-05-11 20:15:27 +02:00
Bas Brands 526491f2f7 MDL-62153 Theme boost: lesson display issues on detailed statistics 2018-05-11 20:11:43 +02:00
Jun Pataleta 9eb19cdf94 Merge branch 'MDL-62075_master' of git://github.com/dmonllao/moodle 2018-05-12 00:29:56 +08:00
Eloy Lafuente (stronk7) 4f510bea17 Merge branch 'MDL-62384-master' of git://github.com/andrewnicols/moodle 2018-05-11 18:12:45 +02:00
Eloy Lafuente (stronk7) 614ebf05ea Merge branch 'MDL-61637_master' of git://github.com/dmonllao/moodle 2018-05-11 18:09:02 +02:00
David Monllao 28e06c2a17 Merge branch 'MDL-61858_master' of git://github.com/markn86/moodle 2018-05-11 18:06:50 +02:00
David Monllao e63a4ae540 Merge branch 'MDL-62368_master' of git://github.com/markn86/moodle 2018-05-11 17:59:20 +02:00
David Monllao 76fc9a4f5a Merge branch 'MDL-62376-master' of git://github.com/bmbrands/moodle 2018-05-11 14:47:32 +02:00
David Monllao 5da250d027 Merge branch 'MDL-58634-master' of git://github.com/bmbrands/moodle 2018-05-11 14:36:01 +02:00
Mark Nelson ffc148ae39 MDL-61858 tool_phpunit: added missing privacy null provider 2018-05-11 19:37:48 +08:00
Mark Nelson 61496b764a MDL-61858 gradingform_guide: added missing metadata implementation 2018-05-11 19:37:48 +08:00
Mark Nelson 776b4fef19 MDL-61858 core_portfolio: added missing metadata implementation 2018-05-11 19:37:48 +08:00
Andrew Nicols 7b4fa14f36 MDL-61858 core_privacy: Add test to ensure component implementations 2018-05-11 19:37:48 +08:00
Eloy Lafuente (stronk7) 3d8c0fb776 Merge branch 'MDL-61951-master' of git://github.com/sarjona/moodle 2018-05-11 13:21:58 +02:00
David Monllao aaa1140d9a Merge branch 'MDL-62397-master' of git://github.com/andrewnicols/moodle 2018-05-11 13:14:05 +02:00
Sara Arjona 5e08a28f9b MDL-61951 core_grading: Review SQL to avoid Oracle errors 2018-05-11 12:59:19 +02:00
David Monllao b96a064f14 Merge branch 'MDL-61714-master' of git://github.com/sarjona/moodle 2018-05-11 11:31:22 +02:00
Sara Arjona ee66104098 MDL-61714 admin: Removed Slovenian digital age 2018-05-11 10:12:16 +02:00
Andrew Nicols cef2c013d2 Merge branch 'MDL-62373_master' of git://github.com/dmonllao/moodle 2018-05-11 15:28:09 +08:00
David Monllao 868fc2aba1 MDL-61637 tool_dataprivacy: Point to new blocks API function 2018-05-11 09:05:43 +02:00
Jake Dallimore b513b36cfd Merge branch 'MDL-62354' of git://github.com/stronk7/moodle 2018-05-11 14:52:33 +08:00
Bas Brands 0ab6c0d546 MDL-58634 editor_atto: prevent notifications overlapping form fields 2018-05-11 08:38:21 +02:00
Jun Pataleta 0697a52218 Merge branch 'MDL-62394-master' of git://github.com/andrewnicols/moodle 2018-05-11 14:23:41 +08:00
Andrew Nicols 20f3b33b24 MDL-62394 privacy: Make JSON export pretty 2018-05-11 14:12:10 +08:00
Mark Nelson 3e642074ee MDL-62368 enrol_lti: implement privacy provider 2018-05-11 14:05:41 +08:00
Jun Pataleta 44d89dfa7a Merge branch 'MDL-62382-master' of git://github.com/ryanwyllie/moodle 2018-05-11 12:06:03 +08:00
Ryan Wyllie ebe7417f94 MDL-62382 quiz: include subcategories when previewing top categories
Include the subcategories when loading the list of questions for
a top level question in the add random question modal.
2018-05-11 11:51:50 +08:00
Andrew Nicols ddb1a13573 MDL-62397 tool_dataprivacy: Check for inactive status properly 2018-05-11 11:20:19 +08:00
Andrew Nicols 61b1cc4dd7 Merge branch 'wip-MDL-61814-master-fix' of git://github.com/abgreeve/moodle 2018-05-11 10:48:47 +08:00
Adrian Greeve 28f3776451 MDL-61814 report_stats: Update to sql to increase performance. 2018-05-11 10:44:51 +08:00
Jake Dallimore b3252ecdc1 MDL-61951 core_grading: SQL improvements for grading_instance join 2018-05-11 10:19:11 +08:00
Jake Dallimore 6f213a2fa2 Merge branch 'MDL-61951-master' of git://github.com/sarjona/moodle 2018-05-11 10:18:58 +08:00
Jun Pataleta 195f6fe37e Merge branch 'MDL-62388_master' of git://github.com/dmonllao/moodle 2018-05-11 10:03:44 +08:00
Mihail Geshoski b1add54344 MDL-62384 privacy: Modify user contexts query for auth_oauth2 2018-05-11 09:46:29 +08:00
Jun Pataleta 239c6202e2 Merge branch 'MDL-62117-master-fix' of git://github.com/andrewnicols/moodle 2018-05-11 09:34:37 +08:00
Andrew Nicols 932ee52ba7 MDL-62117 enrol_paypal: Improve SQL efficiency 2018-05-11 09:29:07 +08:00
Andrew Nicols 38e6852140 MDL-62384 core_rating: Move WHERE clauses to JOIN 2018-05-11 09:15:30 +08:00
Andrew Nicols a339908497 MDL-62384 core_grade: Move WHERE clause to JOIN 2018-05-11 09:15:30 +08:00
Andrew Nicols f6021ae0af MDL-62384 mod_lesson: Move WHERE clause to JOIN 2018-05-11 09:15:29 +08:00
Andrew Nicols 09f9af4729 MDL-62384 logstore_standard: Remove unnecessary join on context 2018-05-11 09:15:29 +08:00
Andrew Nicols 037027230a MDL-62384 mod_quiz: Improve performancy of privacy queries 2018-05-11 09:15:29 +08:00
Adrian Greeve d04b8d74a7 MDL-62384 mod_data: Update sql to improve performance. 2018-05-11 09:15:28 +08:00
Adrian Greeve 212d7fef05 MDL-62384 tool_monitor: Update to sql to fix performance. 2018-05-11 09:15:28 +08:00
Adrian Greeve 5a329d81f9 MDL-62384 core_competency: Update to sql to increase performance. 2018-05-11 09:15:27 +08:00
Adrian Greeve d8eec7ee38 MDL-62384 core_completion: Update to sql to improve performance. 2018-05-11 09:15:27 +08:00
Adrian Greeve 8a5b84cf8b MDL-62384 mod_assign: Update to sql query for performance. 2018-05-11 09:15:27 +08:00
Eloy Lafuente (stronk7) e4ad3ee714 Merge branch 'MDL-62380-master' of git://github.com/mihailges/moodle 2018-05-11 02:06:48 +02:00
Tim Hunt 75aa674b99 MDL-62275 qtype_calc*: improve validation of formulae
Many thanks to Marina Glancy for helping with this.
2018-05-11 01:21:16 +02:00
Eloy Lafuente (stronk7) 575b2eaa2d MDL-62354 notes: Reduce string to avoid js_call_amd() lenght limits
That way we are under the max 1024 allowed and also, the help string
is shown properly in the modal (previously part of it was not visible
falling off-window).
2018-05-10 23:30:22 +02:00
Eloy Lafuente (stronk7) a7a5d10319 Merge branch 'MDL-62277-master' of git://github.com/bmbrands/moodle 2018-05-10 20:50:20 +02:00
Andrew Nicols 00293f90a4 MDL-61635 tool_dataprivacy: Filter out protected contexts in delete 2018-05-10 20:36:02 +02:00
David Monllao 1f000f071e Merge branch 'MDL-62366-master' of git://github.com/zig-moodle/moodle 2018-05-10 18:13:44 +02:00
David Monllao 4033c2a11b Merge branch 'MDL-61714-master' of git://github.com/sarjona/moodle 2018-05-10 18:10:29 +02:00
David Monllao 74e82e4684 Merge branch 'MDL-62200-master' of git://github.com/bmbrands/moodle 2018-05-10 18:04:08 +02:00
David Monllao 7b6c74431b Merge branch 'MDL-61949-master-privacy-upgrade-txt' of git://github.com/mudrd8mz/moodle 2018-05-10 17:53:05 +02:00
David Monllao a319808cfb MDL-62075 tool_dataprivacy: Display correctly requests for other users 2018-05-10 17:43:59 +02:00
Bas Brands f01734122a MDL-62277 Theme boost: add badge criteria layout 2018-05-10 16:32:41 +02:00
David Monllao c459409c1b MDL-61637 tool_dataprivacy: Context-based fetching
Blocks API do not provide methods to retrieve a list of
blocks without filtering by UI-availability. We need to
get course blocks based on their course context.
2018-05-10 15:56:50 +02:00
Shamim Rezaie 8614257135 MDL-62251 Privacy: Fix dir separator in export_file() 2018-05-10 23:30:38 +10:00
Shamim Rezaie 07890336ab MDL-62251 Privacy: Url path separator should be platform independant
It should always be forward slash.
2018-05-10 23:30:38 +10:00
Shamim Rezaie 44efefcbeb MDL-62251 Privacy: Fix get_path() and get_full_path() bug in Windows 2018-05-10 23:30:23 +10:00
David Monllao c2a38d17a4 MDL-62388 tool_dataprivacy: Fixing bs4stable table layout 2018-05-10 14:58:11 +02:00
David Monllao bdc43d7729 MDL-62388 tool_dataprivacy: Default value if no sensitive data provided 2018-05-10 14:57:43 +02:00
Jake Dallimore 7feac02414 MDL-61951 core_grading: Several fixes to the privacy implementation
- Fixed legacy polyfill method definitions
- Added unit test for legacy polyfill
- Fixed delete_gradingform_for_userid in gradingform provider
2018-05-10 13:26:47 +02:00
Andrew Nicols 51e53b9f40 MDL-61905 mod_workshop: Adjust privacy SQL queries
This change moves the content of WHERE to the JOIN, thus filtering the
results before they are returned. This reduces the number of joined rows
which gives a big performance benefit.
2018-05-10 13:03:11 +02:00
Shamim Rezaie 4dfafed5d9 MDL-62251 Task: Make test_cron_delete_from_temp Unix independent 2018-05-10 20:13:09 +10:00
Eloy Lafuente (stronk7) 8e8ae80550 Merge branch 'MDL-62354-master-enfix' of git://github.com/mudrd8mz/moodle 2018-05-10 11:37:55 +02:00
David Mudrák 4f41ae2763 MDL-61949 privacy: Add mention of the new API in upgrade.txt files
The notice is explicitly repeated for activity modules because they
usually process user data heavily.
2018-05-10 11:35:12 +02:00
Bas Brands d2d150b21d MDL-62376 Theme boost: server side form validation for checkboxes 2018-05-10 11:12:31 +02:00
Shamim Rezaie da3c76158e MDL-62251 task: Fix file_temp_cleanup_task::execute() bug in Windows
$CFG->tempdir might contain both / and \ in Windows. Therefore, we need to
call realpatch() to be able to compare that with another patch.
2018-05-10 18:39:49 +10:00
Sara Arjona b1f1bd2e54 MDL-61714 admin: Update digital age consent map according legislation 2018-05-10 10:32:49 +02:00
Jake Dallimore eac66ff63c Merge branch 'MDL-61971-master' of git://github.com/mickhawkins/moodle 2018-05-10 16:23:22 +08:00
David Monllao 7258bf0bcf MDL-62373 tool_dataprivacy: Get rid of profile's contact dpo left-margin 2018-05-10 10:18:10 +02:00
Shamim Rezaie f181a24a0b MDL-62251 backup: Fix replace_tempdir() bug under Windows
rename() fails under Windows if the destination file/directory exists.
I modified the code to only call $this->get_workdir_path() once as that function
creates the directory if doesn't exist. And we don't want that considering the
behaviour of rename on Windows.
2018-05-10 18:16:09 +10:00
David Monllao 91037dfb29 MDL-62373 tool_dataprivacy: More spacing between rows 2018-05-10 09:01:23 +02:00
David Monllao 782b242ccb Merge branch 'MDL-61309-master' of git://github.com/andrewnicols/moodle 2018-05-10 08:46:32 +02:00
Andrew Nicols f662082974 MDL-61309 mod_forum: Improve privacy query performance 2018-05-10 14:05:53 +08:00
Jun Pataleta 4a70fd9973 Merge branch 'wip-MDL-62289-master' of git://github.com/abgreeve/moodle 2018-05-10 13:52:46 +08:00
Marina Glancy f5b667f1c5 MDL-62206 blocks: do not add fixed form elements as hidden 2018-05-10 12:08:41 +08:00
Adam Olley efc508c39c MDL-62210 mod_assign: limit file lookup to the target assign 2018-05-10 11:21:44 +08:00
Brendan Cox 1be5d6f4fe MDL-62210 mod_assign: improved validation in portfolio caller class 2018-05-10 11:21:44 +08:00
Jake Dallimore 84bc2245f7 Merge branch 'MDL-62383-master' of git://github.com/junpataleta/moodle 2018-05-10 10:03:32 +08:00
Jun Pataleta 6c0c9d5d08 MDL-62383 privacy: Close recordset in contextlist::add_from_sql() 2018-05-10 09:55:33 +08:00
Andrew Nicols dfa16c06a6 Merge branch 'MDL-61973-master' of git://github.com/mickhawkins/moodle 2018-05-10 09:47:10 +08:00
Jake Dallimore 34dd6c69ab MDL-62257 mnetservice_enrol: minor fixes to privacy provider 2018-05-10 09:36:40 +08:00
Jake Dallimore 24a12d96e5 Merge branch 'MDL-62257-master' of git://github.com/cescobedo/moodle 2018-05-10 09:35:02 +08:00
Mihail Geshoski 3f02b87df0 MDL-62380 themes: Minor verification pages not being displayed nicely 2018-05-10 09:35:01 +08:00
Eloy Lafuente (stronk7) c3da16f13d Merge branch 'MDL-61309-master' of git://github.com/andrewnicols/moodle 2018-05-10 09:17:31 +08:00
Eloy Lafuente (stronk7) 51b3c29b92 Merge branch 'MDL-62077-master' of git://github.com/bmbrands/moodle 2018-05-10 09:17:31 +08:00
David Monllao a83b43a118 Merge branch 'MDL-62308_master' of git://github.com/markn86/moodle 2018-05-10 09:17:30 +08:00
Eloy Lafuente (stronk7) 9d56e7925b Merge branch 'MDL-61976_master-fix' of git://github.com/dmonllao/moodle 2018-05-10 09:17:30 +08:00
David Monllao 7140f67a98 MDL-61976 mod_wiki: Trim returned value to avoid EOF-related failures 2018-05-10 09:17:29 +08:00
David Monllao 337673949b Merge branch 'MDL-62056-master' of git://github.com/sarjona/moodle 2018-05-10 09:17:28 +08:00
Andrew Nicols 06ede94f07 Merge branch 'wip-MDL-62351-master' of git://github.com/marinaglancy/moodle 2018-05-10 09:17:28 +08:00
David Monllao 1b764a2fde Merge branch 'MDL-62249-master' of git://github.com/zig-moodle/moodle 2018-05-10 09:17:27 +08:00
David Monllao 99a080f12a Merge branch 'MDL-62361' of git://github.com/stronk7/moodle 2018-05-10 09:17:25 +08:00
Zig Tan 0139b8828f MDL-62366 mod_assign: Followup fix for attemptnumber in exported JSON 2018-05-10 09:16:41 +08:00
Andrew Nicols 0f125a45f4 Merge branch 'MDL-62217_master' of git://github.com/dmonllao/moodle 2018-05-10 09:16:01 +08:00
Andrew Nicols 270cefc4b0 Merge branch 'MDL-62269-master' of git://github.com/vmdef/moodle 2018-05-10 09:16:00 +08:00
David Monllao a49a438536 Merge branch 'MDL-62364_master' of git://github.com/markn86/moodle 2018-05-10 09:15:59 +08:00
David Monllao 6fe7510ae0 Merge branch 'wip-MDL-61976-master' of git://github.com/marinaglancy/moodle 2018-05-10 09:15:59 +08:00
David Monllao 8a4b725f6d MDL-61994 mod_glossary: Fixing wrong subsystem name 2018-05-10 09:15:59 +08:00
David Monllao aea028ff29 Merge branch 'MDL-61994-master' of git://github.com/lameze/moodle 2018-05-10 09:15:58 +08:00
David Monllao f60b216de0 Merge branch 'MDL-61309-master' of git://github.com/andrewnicols/moodle 2018-05-10 09:15:57 +08:00
Brendan Cox d8609cb545 MDL-62232 mod_forum: Additional tests for portfolio file export 2018-05-10 09:15:57 +08:00
Adam Olley 5b89ff626d MDL-62232 mod_forum: Limit portfolio to files belonging to the post 2018-05-10 09:15:57 +08:00
Andrew Nicols 4191032993 Merge branch 'MDL-61905-master-privacyworkshop' of git://github.com/mudrd8mz/moodle 2018-05-10 09:15:56 +08:00
Andrew Nicols 178bb8c22e Merge branch 'MDL-62029-master-2' of git://github.com/snake/moodle 2018-05-10 09:15:55 +08:00
Jun Pataleta 9f358de241 Merge branch 'MDL-62332-master' of git://github.com/bmbrands/moodle 2018-05-10 09:15:55 +08:00
Andrew Nicols f7ca19afaa Merge branch 'MDL-62318-master' of git://github.com/rezaies/moodle 2018-05-10 09:15:54 +08:00
Andrew Nicols 820c234934 Merge branch 'MDL-62117-master' of git://github.com/rezaies/moodle 2018-05-10 09:15:54 +08:00
Andrew Nicols 5b98200ff4 Merge branch 'MDL-62051-master' of git://github.com/bmbrands/moodle 2018-05-10 09:15:46 +08:00
Marina Glancy 2d83a76ed7 MDL-62233 portfolio: check caller parent class 2018-05-10 09:13:03 +08:00
Andrew Nicols db4754e98c MDL-61309 mod_forum: Remove unnecessary deletion check 2018-05-10 07:45:27 +08:00
Sara Arjona 6416e87f26 MDL-61951 grading: Implement privacy API 2018-05-09 22:41:04 +02:00
David Mudrák 14e0ab3536 MDL-62354 tool_policy: Reflect the strings improvements in Behat tests 2018-05-09 22:39:22 +02:00
Helen Foster a5591614d5 MDL-62354 lang: Merge English strings from the en_fix language pack
Significant string changes:

* publishstate_help, core_notes - including GDPR guidance
* contactdataprotectionofficer_desc, tool_dataprivacy - removing mention
  of link on privacy policy page as it is not yet implemented
2018-05-09 22:39:21 +02:00
Bas Brands 5b399ba107 MDL-62077 Theme boost: position data registry edit button 2018-05-09 16:54:46 +02:00
Bas Brands b197ab3566 MDL-62359 Theme boost: allow breaks as separators in form groups 2018-05-09 16:50:16 +02:00
Mark Nelson 0f41e8a2a1 MDL-62308 core_backup: implement privacy provider
The provider was added to 'backup/util/ui/' due to
Moodle's implementation of autoclass loading. See
fetch_subsystems() in lib/classes/component.php.
2018-05-09 22:48:07 +08:00
Marina Glancy 468c8d39c3 MDL-62351 privacy: check that subsystem/plugintype exist 2018-05-09 17:37:31 +08:00
Mark Nelson b6b7c2636f MDL-62364 core: allow null for 'courseid' in notification_sent event
It's possible the '\core\message\message' object has a null value
for the 'courseid' which is used in \core\message\manager::send_message().
Make sure we compensate for this.
2018-05-09 17:27:00 +08:00
Marina Glancy 7cc21cb7bd MDL-61976 mod_wiki: implement privacy API 2018-05-09 17:26:52 +08:00
Victor Deniz 5d5881f0f8 MDL-62269 auth_mnet: Implement privacy API
AMOS BEGIN
 CPY [privacy:metadata:address,core_user],[privacy:metadata:mnet_external:address,auth_mnet]
 CPY [privacy:metadata:aim,core_user],[privacy:metadata:mnet_external:aim,auth_mnet]
 CPY [privacy:metadata:aim,core_user],[privacy:metadata:mnet_external:aim,auth_mnet]
 CPY [privacy:metadata:alternatename,core_user],[privacy:metadata:mnet_external:alternatename,auth_mnet]
 CPY [privacy:metadata:autosubscribe,core_user],[privacy:metadata:mnet_external:autosubscribe,auth_mnet]
 CPY [privacy:metadata:calendartype,core_user],[privacy:metadata:mnet_external:calendartype,auth_mnet]
 CPY [privacy:metadata:city,core_user],[privacy:metadata:mnet_external:city,auth_mnet]
 CPY [privacy:metadata:country,core_user],[privacy:metadata:mnet_external:country,auth_mnet]
 CPY [privacy:metadata:currentlogin,core_user],[privacy:metadata:mnet_external:currentlogin,auth_mnet]
 CPY [privacy:metadata:department,core_user],[privacy:metadata:mnet_external:department,auth_mnet]
 CPY [privacy:metadata:description,core_user],[privacy:metadata:mnet_external:description,auth_mnet]
 CPY [privacy:metadata:email,core_user],[privacy:metadata:mnet_external:email,auth_mnet]
 CPY [privacy:metadata:emailstop,core_user],[privacy:metadata:mnet_external:emailstop,auth_mnet]
 CPY [privacy:metadata:firstaccess,core_user],[privacy:metadata:mnet_external:firstaccess,auth_mnet]
 CPY [privacy:metadata:firstname,core_user],[privacy:metadata:mnet_external:firstname,auth_mnet]
 CPY [privacy:metadata:firstnamephonetic,core_user],[privacy:metadata:mnet_external:firstnamephonetic,auth_mnet]
 CPY [privacy:metadata:icq,core_user],[privacy:metadata:mnet_external:icq,auth_mnet]
 CPY [privacy:metadata:id,core_user],[privacy:metadata:mnet_external:id,auth_mnet]
 CPY [privacy:metadata:idnumber,core_user],[privacy:metadata:mnet_external:idnumber,auth_mnet]
 CPY [privacy:metadata:imagealt,core_user],[privacy:metadata:mnet_external:imagealt,auth_mnet]
 CPY [privacy:metadata:institution,core_user],[privacy:metadata:mnet_external:institution,auth_mnet]
 CPY [privacy:metadata:lang,core_user],[privacy:metadata:mnet_external:lang,auth_mnet]
 CPY [privacy:metadata:lastaccess,core_user],[privacy:metadata:mnet_external:lastaccess,auth_mnet]
 CPY [privacy:metadata:lastlogin,core_user],[privacy:metadata:mnet_external:lastlogin,auth_mnet]
 CPY [privacy:metadata:lastname,core_user],[privacy:metadata:mnet_external:lastname,auth_mnet]
 CPY [privacy:metadata:lastnamephonetic,core_user],[privacy:metadata:mnet_external:lastnamephonetic,auth_mnet]
 CPY [privacy:metadata:maildigest,core_user],[privacy:metadata:mnet_external:maildigest,auth_mnet]
 CPY [privacy:metadata:maildisplay,core_user],[privacy:metadata:mnet_external:maildisplay,auth_mnet]
 CPY [privacy:metadata:middlename,core_user],[privacy:metadata:mnet_external:middlename,auth_mnet]
 CPY [privacy:metadata:msn,core_user],[privacy:metadata:mnet_external:msn,auth_mnet]
 CPY [privacy:metadata:phone,core_user],[privacy:metadata:mnet_external:phone1,auth_mnet]
 CPY [privacy:metadata:picture,core_user],[privacy:metadata:mnet_external:picture,auth_mnet]
 CPY [privacy:metadata:policyagreed,core_user],[privacy:metadata:mnet_external:policyagreed,auth_mnet]
 CPY [privacy:metadata:skype,core_user],[privacy:metadata:mnet_external:skype,auth_mnet]
 CPY [privacy:metadata:suspended,core_user],[privacy:metadata:mnet_external:suspended,auth_mnet]
 CPY [privacy:metadata:timezone,core_user],[privacy:metadata:mnet_external:timezone,auth_mnet]
 CPY [privacy:metadata:trackforums,core_user],[privacy:metadata:mnet_external:trackforums,auth_mnet]
 CPY [privacy:metadata:trustbitmask,core_user],[privacy:metadata:mnet_external:trustbitmask,auth_mnet]
 CPY [privacy:metadata:url,core_user],[privacy:metadata:mnet_external:url,auth_mnet]
 CPY [privacy:metadata:username,core_user],[privacy:metadata:mnet_external:username,auth_mnet]
 CPY [privacy:metadata:yahoo,core_user],[privacy:metadata:mnet_external:yahoo,auth_mnet]
AMOS END
2018-05-09 10:15:20 +01:00
Barbara Ramiro 7e4bffd836 MDL-61973 editor_atto: refactoring media icons 2018-05-09 17:00:14 +08:00
David Monllao 94522f8dd0 MDL-62217 search_solr: Plugin provider fake implementation 2018-05-09 10:57:14 +02:00
David Monllao 74fc7d3120 MDL-62217 search: Privacy providers 2018-05-09 10:56:02 +02:00
David Mudrák 59a8a5c0b8 MDL-61905 workshop: Implement privacy API in grading evaluators
Standard workshop ships with only one evaluation method and that one
holds no personal data.
2018-05-09 10:01:50 +02:00
David Mudrák 4b8d462ee6 MDL-61905 workshop: Implement privacy API in assessment allocators
Assessment allocation methods normally do not store any personal data.
Their duty is to create assessment records that are then exported by the
workshop core itself.

Still, some allocators (such as the Manual allocation) can store certain
personal data such as user preferences.
2018-05-09 10:01:50 +02:00
David Mudrák fe745a0a84 MDL-61905 workshop: Implement privacy API in grading strategies
On low level, standard grading strategies subplugins do not store
personal themselves.  They make use of the grades storage provided by
the workshop itself.  What they do contain though is the information
about how the assessment forms were defined. And they are also
responsible for correctly interpreting the values in the central grades
table.

Grading strategies fulfil the contract with the parent workshop module
by implementing the workshopform_provider interface. That gives them a
chance to export data about the assessment form to each of exported
assessment.
2018-05-09 10:01:50 +02:00
David Mudrák 84a57322c2 MDL-61905 workshop: Implement the privacy API in the workshop core
Workshop module stores personal data in its tables, via user
preference and via core_files and core_plagiarism subsystems.

When exporting the data, we export not only data created by users
themselves (such as their submissions and provided peer-assessments) but
also all relevant data that can (or must) be used to interpret created
content and evaluate the user's performance and skills.

On the other hand, when deleting data at user's request, we delete only
those data that do not affect other users' performance evaluation. The
reasoning is that one's right for privacy does not overweight someone
else's right for fair assessment. For that reason, we can't fully delete
whole provided peer-assessments, for example. Because they are used in
cross-comparison and grading evaluation of all other peers who assessed
the same submission. So instead, we replace provided texts but still
keep the original record.

Workshop defines the interface for its grading strategy subplugins to
allow them attach personal data under their control to the exported
structures.
2018-05-09 10:01:50 +02:00
Jake Dallimore e4a37fbef1 MDL-62029 core_course: Fixes to context-aware provider implementation. 2018-05-09 15:34:41 +08:00
Sara Arjona fd7174d03f MDL-62056 policy: Adapt code to avoid travis errors on Github 2018-05-09 08:31:32 +02:00
Andrew Nicols 3e95e09b67 MDL-61309 mod_forum: Implement privacy deletion 2018-05-09 14:04:13 +08:00
Andrew Nicols caef6489ae MDL-61309 mod_forum: Implement the Privacy API 2018-05-09 14:04:12 +08:00
Andrew Nicols 2de389aa05 MDL-61309 core_tags: Add privacy unit test trait 2018-05-09 14:04:12 +08:00
Michael Hawkins be5cc0ba03 MDL-61971 dataprivacy: Added warning messages where data request pages are viewed on non-HTTPS sites 2018-05-09 11:59:26 +08:00
Shamim Rezaie 5c87353833 MDL-62318 core_group: Updating cache when deleting group memberships 2018-05-09 13:29:39 +10:00
Shamim Rezaie 55c1865638 MDL-62318 core_group: Add implementation of privacy API 2018-05-09 13:29:26 +10:00
Simey Lameze fe80a5ab66 MDL-61994 mod_glossary: add unit tests for provider 2018-05-09 10:57:42 +08:00
Simey Lameze a92bbd867d MDL-61994 mod_glossary: implement privacy providers 2018-05-09 10:57:13 +08:00
Adrian Greeve c8e4922194 MDL-62289 tool_dataprivacy: Ensure all user data deleted.
We now do a comprehensive check and clean of user data when
a user context expires.
2018-05-09 10:16:39 +08:00
Adrian Greeve 9faf51a7ca MDL-62029 core_privacy: context aware provider creation. 2018-05-09 10:11:47 +08:00
Adrian Greeve fd9c70c43f MDL-62029 core_course: Create privacy files for core_course. 2018-05-09 10:11:41 +08:00
Marina Glancy ce3f804683 MDL-61996 core: when loggedin always check user is complete 2018-05-09 02:55:55 +02:00
Eloy Lafuente (stronk7) 12ed3e2716 MDL-62361 versions: main version bump 2018-05-09 02:48:22 +02:00
Eloy Lafuente (stronk7) 57ea6753d3 MDL-62361 versions: bump all versions and requires near release
version = 2018051400 release version
requires= 2018050800 current beta+ (week4roll1) version
2018-05-09 02:46:30 +02:00
Andrew Nicols 4864a25baf Merge branch 'MDL-62010-master' of git://github.com/cescobedo/moodle 2018-05-09 08:12:27 +08:00
cescobedo 451f4a284c MDL-62010 core_enrol: Add privacy implementation for core_enrol 2018-05-08 18:52:25 +02:00
David Monllao 93ba31b27c MDL-61814 core_user: Remove empty line to avoid ci warning 2018-05-08 17:19:23 +02:00
David Monllao 40cd9b66a6 Merge branch 'wip-MDL-61814-master' of git://github.com/abgreeve/moodle 2018-05-08 17:17:47 +02:00
David Monllao 5d2dd881e3 Merge branch 'MDL-61943-master' of git://github.com/cescobedo/moodle 2018-05-08 16:43:50 +02:00
Bas Brands 95d7c77b28 MDL-62051 Theme boost: access restriction layout 2018-05-08 16:13:00 +02:00
David Monllao 02c7769422 on-demand release 3.5beta+ 2018-05-08 14:02:40 +02:00
David Monllao 69a0a8edef NOBUG: Fixed file access permissions 2018-05-08 14:02:40 +02:00
David Monllao 127f9c8274 Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2018-05-08 14:02:38 +02:00
cescobedo a69de9ba05 MDL-61943 core_role: Add privacy implementation for core_role 2018-05-08 13:40:30 +02:00
cescobedo 7c7ace009a MDL-62257 mnetservice_enrol: Implement Privacy API 2018-05-08 10:09:25 +02:00
Bas Brands ea6dfd78ce MDL-62332 Theme boost: paging bar empty active link 2018-05-08 09:26:14 +02:00
Andrew Nicols 800c81dc38 MDL-61876 admin: Bump version 2018-05-08 14:47:29 +08:00
Andrew Nicols 9f7aeac5e5 Merge branch 'wip-MDL-61937-master-fix' of https://github.com/marinaglancy/moodle 2018-05-08 14:43:04 +08:00
Marina Glancy ae3b1c0048 MDL-61937 mod_data: fix commentarea name 2018-05-08 14:39:23 +08:00
Adrian Greeve b781f58e91 MDL-61814 report_stats: Update to be a full privacy provider. 2018-05-08 13:42:05 +08:00
Adrian Greeve ab78499bbd MDL-61814 core_portfolio: Update to portfolio provider. 2018-05-08 13:42:05 +08:00
Andrew Nicols 4f2bc5bc8f MDL-61876 admin: Move forceclean to experimental settings 2018-05-08 13:30:09 +08:00
Andrew Nicols 1144ae255f Revert "MDL-61876 admin: set default forceclean=1"
This reverts commit c182f060fa.
2018-05-08 13:28:12 +08:00
Andrew Nicols e3a1b34376 Revert "MDL-61876 admin: for upgraded sites keep forceclean=0"
This reverts commit 7529031368.
2018-05-08 13:28:09 +08:00
Zig Tan e151fdad92 MDL-62249 block_tag_flickr: Corrected privacy providers 2018-05-08 12:11:46 +08:00
Marina Glancy 2d9d4a1d88 MDL-61976 privacy: replace pluginfile in test writer 2018-05-08 12:03:41 +08:00
Marina Glancy 8dd8486355 MDL-61976 privacy: add subcontext argument to has_any_data 2018-05-08 12:03:41 +08:00
Adrian Greeve c49f309204 MDL-61814 core_user: Implement privacy system for user. 2018-05-08 11:44:37 +08:00
Andrew Nicols a9be2f18bd Merge branch 'wip-MDL-62068-master' of git://github.com/marinaglancy/moodle 2018-05-08 11:39:44 +08:00
Andrew Nicols 44d8324247 Merge branch 'MDL-62281-master' of git://github.com/sarjona/moodle 2018-05-08 11:34:46 +08:00
Marina Glancy 2207b0fa9f MDL-62068 core_tag: implement privacy API 2018-05-08 11:30:54 +08:00
Shamim Rezaie 31fd863bd9 MDL-62117 enrol_paypal: Add implementation of Privacy API 2018-05-08 13:30:42 +10:00
Andrew Nicols 630a72f19f Merge branch 'MDL-62285-master' of git://github.com/ryanwyllie/moodle 2018-05-08 11:08:03 +08:00
Andrew Nicols b043073143 Merge branch 'MDL-62274-master' of git://github.com/bmbrands/moodle 2018-05-08 08:38:13 +08:00
Eloy Lafuente (stronk7) fc8cc24c85 Merge branch 'MDL-61521-master' of git://github.com/jleyva/moodle 2018-05-08 01:41:32 +02:00
Eloy Lafuente (stronk7) e2ede0b5c3 Merge branch 'MDL-62295-master' of git://github.com/bmbrands/moodle 2018-05-08 01:02:35 +02:00
Eloy Lafuente (stronk7) d0ce3439fe Merge branch 'wip-MDL-61876-fix' of https://github.com/marinaglancy/moodle 2018-05-07 19:15:28 +02:00
Bas Brands 3560cf5f82 MDL-62322 Theme boost: update template display classes 2018-05-07 15:06:14 +02:00
Bas Brands 29587cc9da MDL-62274 Theme boost: bootstrap classes define roles form 2018-05-07 14:36:48 +02:00
Sara Arjona a22f7bbb3a MDL-62281 tool_policy: Remove empty row in Policies and agreements page 2018-05-07 12:53:40 +02:00
David Monllao 775db1409e Merge branch 'MDL-62326-master' of git://github.com/andrewnicols/moodle 2018-05-07 12:09:40 +02:00
Bas Brands 54cbfbca8a MDL-62295 Theme boost: duration form field alignment 2018-05-07 11:39:57 +02:00
Andrew Nicols b175d43408 Merge branch 'MDL-62169-master' of git://github.com/zig-moodle/moodle 2018-05-07 17:24:26 +08:00
Jun Pataleta ea083b0a5e Merge branch 'MDL-62062-master' of git://github.com/sarjona/moodle 2018-05-07 17:21:49 +08:00
Zig Tan 966dd82d90 MDL-62169 mod_assignment: Improved phpunit test reliability 2018-05-07 16:32:49 +08:00
Sara Arjona 461b1931c0 MDL-62062 tool_policy: Let manager/dpo withdraw consent to policies
The policies can be revoked by clicking the "Agreed [on behalf]" ticks.
A message has been added also to explain users they must contact to
the DPO if they want to withdraw their consent to policies.
2018-05-07 10:11:13 +02:00
Andrew Nicols 53a58dda1c Merge branch 'MDL-61919_master' of git://github.com/markn86/moodle 2018-05-07 15:45:43 +08:00
Andrew Nicols 9846d710fe Merge branch 'wip-MDL-62137-master' of git://github.com/abgreeve/moodle 2018-05-07 15:24:01 +08:00
Andrew Nicols aad32d11f5 MDL-62326 core_antivirus: Implement null provider 2018-05-07 14:31:14 +08:00
Adrian Greeve 6d3b250b4f MDL-62137 core_privacy: Update to helper to use core_completion 2018-05-07 14:28:17 +08:00
Adrian Greeve ebab932f71 MDL-62137 core_completion: Privacy files for core_completion. 2018-05-07 14:28:16 +08:00
Andrew Nicols e9e2ef7da7 Merge branch 'MDL-62169-master' of git://github.com/zig-moodle/moodle 2018-05-07 14:12:06 +08:00
Zig Tan 0fed66ad0f MDL-62169 mod_assignment: Implemented privacy providers 2018-05-07 13:38:56 +08:00
Andrew Nicols c9b29e6b98 Merge branch 'MDL-62171-master' of git://github.com/bmbrands/moodle 2018-05-07 13:10:34 +08:00
David Monllao 2963a540ef Merge branch 'MDL-62291-master' of git://github.com/junpataleta/moodle 2018-05-07 06:12:00 +02:00
Marina Glancy 8852faea91 MDL-61876 modules: cm_info::set_content can accept formatted text
Allow modules to apply format_text() on user input before calling cm_info::set_content(). This is useful for
modules that add interactive content (for example, JS enhanced folder tree). Otherwise all javascript would
be removed if $CFG->forceclean is enabled.
2018-05-07 11:27:56 +08:00
Michael Hawkins 910b62a3d0 MDL-61973 editor_atto: refactoring media icons 2018-05-07 11:21:40 +08:00
Marina Glancy b03c67e978 MDL-61876 mod_data: call format_text on template before replacement
Otherwise when $CFG->forceclean is enabled all search input fields are stripped out
2018-05-07 11:21:06 +08:00
Jun Pataleta 23fbd252db Merge branch 'MDL-62286-master' of git://github.com/sarjona/moodle 2018-05-07 10:05:24 +08:00
Jun Pataleta 97f1c1f424 Merge branch 'MDL-61937-master-fix' of git://github.com/andrewnicols/moodle 2018-05-07 09:38:10 +08:00
Jun Pataleta fbd7daf50e Merge branch 'MDL-61937_master-fix' of git://github.com/dmonllao/moodle 2018-05-07 09:37:55 +08:00
Andrew Nicols ea80f967b8 MDL-61937 mod_data: Correct rating export args in privacy 2018-05-07 09:28:54 +08:00
Andrew Nicols a5c2308443 MDL-61407 question: Export step files in privacy 2018-05-07 07:09:30 +08:00
Eloy Lafuente (stronk7) fc5c5306c9 MDL-61693 core_calendar: make results deterministic for better testing 2018-05-06 18:19:21 +02:00
David Monllao 70e8515ea8 MDL-62218 analytics: Add missing sample origin checking 2018-05-04 16:35:42 +02:00
Bas Brands bfe00e67a3 MDL-62200 Theme boost: popup in popup styles 2018-05-04 13:56:46 +02:00
Eloy Lafuente (stronk7) fcb7459569 Merge branch 'wip-MDL-61876-master-fix' of https://github.com/marinaglancy/moodle 2018-05-04 12:11:16 +02:00
David Monllao 62c9d0877a MDL-61937 mod_data: Target the correct area 2018-05-04 11:55:25 +02:00
Frédéric Massart 9967731256 MDL-61877 core_competency: False negative due to random ordering 2018-05-04 11:18:42 +02:00
Marina Glancy 7529031368 MDL-61876 admin: for upgraded sites keep forceclean=0 2018-05-04 15:55:45 +08:00
Zig Tan bdf271df80 MDL-61693 core_calendar: Simplified get calendar event details SQL 2018-05-04 13:01:37 +08:00
Jake Dallimore 209f6e1eda Merge branch 'MDL-61407-master' of git://github.com/andrewnicols/moodle 2018-05-04 11:50:26 +08:00
Andrew Nicols 9b3159246f Merge branch 'wip-MDL-61937-master' of git://github.com/marinaglancy/moodle 2018-05-04 11:29:24 +08:00
Jun Pataleta cb596d823f Merge branch 'MDL-61877-master' of git://github.com/FMCorz/moodle 2018-05-04 11:25:49 +08:00
Marina Glancy ab803d697c MDL-61937 mod_data: implement privacy provider 2018-05-04 11:22:15 +08:00
Marina Glancy 1c4b87cb97 MDL-61937 tag: new method in privacy api 2018-05-04 11:22:14 +08:00
Marina Glancy e5ec530a44 MDL-61937 rating: delete method in privacy provider 2018-05-04 11:22:14 +08:00
Marina Glancy 70703d4c51 MDL-61937 comment: do not use API in provider
When exporting comments we can not use comments API because:
- we don't need to call component validate, comments may be disabled right now but
  are still stored
- we don't need to call display callback
- we need to export all comments and not only first 15 of them
2018-05-04 11:22:14 +08:00
Marina Glancy a7a6be90c8 MDL-61937 mod_data: generator for all field types 2018-05-04 11:22:14 +08:00
cescobedo ed54f4e11e MDL-61937 datafield: Add privacy implementation for all datafield 2018-05-04 11:22:13 +08:00
Andrew Nicols 2cae92c6c3 Merge branch 'MDL-62218-master' of git://github.com/ryanwyllie/moodle 2018-05-04 11:11:14 +08:00
Ryan Wyllie 28b870b4de MDL-62218 analytics: make privacy test class name more unique 2018-05-04 11:04:24 +08:00
Jun Pataleta 66156ad245 Merge branch 'MDL-62151-master' of git://github.com/bmbrands/moodle 2018-05-04 10:55:50 +08:00
Jake Dallimore 1c73a11a20 Merge branch 'MDL-62046-master' of git://github.com/mihailges/moodle 2018-05-04 10:26:41 +08:00
Jake Dallimore 6ef8a82599 Merge branch 'MDL-62032-master' of git://github.com/mihailges/moodle 2018-05-04 09:56:37 +08:00
Jake Dallimore e46c60db2a Merge branch 'MDL-62030-master' of git://github.com/mihailges/moodle 2018-05-04 09:51:39 +08:00
Jake Dallimore 2566b18a31 Merge branch 'MDL-59845-master' of git://github.com/andrewnicols/moodle 2018-05-04 09:43:14 +08:00
Jake Dallimore 4fb76c0fac Merge branch 'MDL-62007-master' of git://github.com/mihailges/moodle 2018-05-04 09:19:51 +08:00
Andrew Nicols 67e2ee0b65 Merge branch 'wip-MDL-61876-master' of git://github.com/marinaglancy/moodle 2018-05-04 08:03:48 +08:00
Eloy Lafuente (stronk7) 6c0b4929c6 Merge branch 'MDL-62315-master-privacycoreform' of git://github.com/mudrd8mz/moodle 2018-05-03 23:40:37 +02:00
Eloy Lafuente (stronk7) 3149bea2c4 Merge branch 'MDL-61800-master' of git://github.com/andrewnicols/moodle 2018-05-03 23:27:51 +02:00
Eloy Lafuente (stronk7) 6a23fb2171 MDL-62202 privacy: fix behat tests to point to new location under users 2018-05-03 22:48:29 +02:00
David Mudrák fa7297aff0 MDL-62315 form: Implement the privacy API for core_form subsystem
Forms subsystem has no own database table. The only personal data is the
user preference 'filemanager_recentviewmode' that stores the view mode
of the file picker element.

It was discussed whether handling of this preference should be added
here into core_form, or rather into core_repository subsystem. The final
decision was to consider the preference as owned by the core_form
because it is affecting the display of a form element, not the
repository backend (even though the strings describing it had been put
into core_repository strings).
2018-05-03 19:05:39 +02:00
David Monllao bf694d4724 Merge branch 'MDL-62254_master' of https://github.com/nadavkav/moodle 2018-05-03 16:08:11 +02:00
David Monllao 6474044464 Merge branch 'MDL-62227-master' of git://github.com/bmbrands/moodle 2018-05-03 15:49:43 +02:00
David Monllao 97b0a6cbfc MDL-62218 analytics: Privacy API implementation 2018-05-03 15:28:22 +02:00
Mark Nelson 04cd1b3f31 MDL-61919 core_message: implement privacy provider 2018-05-03 20:44:05 +08:00
Sara Arjona 3d34aa5c29 MDL-62062 tool_policy: Avoid access to consent page after agreeding
Once the policies have been accepted, only users with permission to
revoke them will be able to access again to the user consent page.
2018-05-03 13:59:21 +02:00
Andrew Nicols 545e2b4ec7 Merge branch 'MDL-62240-master' of git://github.com/sarjona/moodle 2018-05-03 18:57:28 +08:00
Jake Dallimore 9ab7a315d1 Merge branch 'MDL-62310-master' of git://github.com/cescobedo/moodle 2018-05-03 18:00:40 +08:00
Bas Brands ae0629d284 MDL-62171 Theme boost: modal accessibility focus 2018-05-03 11:54:26 +02:00
Jun Pataleta 46cb408e30 MDL-62291 tool_dataprivacy: Change DPO mapping setting to multi-checkbox 2018-05-03 17:26:31 +08:00
Jun Pataleta c166b700d8 Merge branch 'MDL-61194-master' of git://git.cameron1729.xyz/moodle 2018-05-03 17:07:57 +08:00
Cameron Ball 23fe1e2bed MDL-61194 assignfeedback_editpdf: Check if we can convert online text
This patch adds a check to make sure there is a converter available that
will be able to handle html->pdf conversion before adding onlinetext
submissions to the list of files to combine.
2018-05-03 16:44:27 +08:00
Mihail Geshoski fd4c7d6461 MDL-62046 profilefield_textarea: Add privacy files and unit tests 2018-05-03 16:25:58 +08:00
David Monllao 3128529784 Merge branch 'MDL-61693-master' of git://github.com/zig-moodle/moodle 2018-05-03 10:24:02 +02:00
Andrew Nicols 50f5a097a2 MDL-61407 privacy: Reset the writer after writer tests
The writer was not reset after these tests which had the potential to
affect the next test after these were run.
2018-05-03 16:14:38 +08:00
Andrew Nicols 09a9b8f1ac MDL-61407 quizaccess: Add null_writer implementations 2018-05-03 16:14:38 +08:00
Andrew Nicols 2e85118fb3 MDL-61407 quiz: Add reports for quiz 2018-05-03 16:14:37 +08:00
Andrew Nicols 05014132b6 MDL-61407 qtype: Add null_writer for all qtypes 2018-05-03 16:14:37 +08:00
Andrew Nicols 80b0c733f2 MDL-61407 qformat: Add null_writer for all qformats
Questions formats do not store any personal data.
Any contrib format which does store personal data should utilise the
standard export interfaces as this subsystem will not call them directly.
2018-05-03 16:14:36 +08:00
Andrew Nicols fa9eba7d84 MDL-61407 question: Add null_writer for all behaviours
Behaviours do not store any personal data.
Any contrib behaviour which does store personal data should utilise the
standard export interfaces as this subsystem will not call them directly.
2018-05-03 16:14:36 +08:00
Andrew Nicols 4c729982c7 MDL-61407 mod_quiz: Add initial privacy implementation 2018-05-03 16:14:36 +08:00
Andrew Nicols d44064cdd6 MDL-61407 question: Add initial privacy implementation 2018-05-03 16:14:35 +08:00
David Monllao 4e7232b82e Merge branch 'MDL-62058-master' of git://github.com/bmbrands/moodle 2018-05-03 10:11:28 +02:00
David Monllao 205994bafe Merge branch 'MDL-62059-master' of git://github.com/rezaies/moodle 2018-05-03 10:08:09 +02:00
Mihail Geshoski cf6a77fbbb MDL-62032 profilefield_text: Add privacy files and unit tests 2018-05-03 16:07:55 +08:00
Mihail Geshoski a15712ef6e MDL-62030 profilefield_menu: Add privacy files and unit tests 2018-05-03 15:51:52 +08:00
Jun Pataleta f2cb3952af Merge branch 'MDL-62282-master' of git://github.com/ryanwyllie/moodle 2018-05-03 15:40:55 +08:00
Bas Brands 5e92cf33c5 MDL-62151 Theme boost: mod_chat styles 2018-05-03 09:34:40 +02:00
Mihail Geshoski 8ddb08c9d9 MDL-62007 profilefield_checkbox: Modify privacy provider 2018-05-03 15:27:50 +08:00
Sara Arjona 7a3707797d MDL-62240 privacy: Avoid error when path is higher than allowed
A new method shorten_filenames has been added to moodlelib.
2018-05-03 09:16:13 +02:00
Mihail Geshoski 76b61aa064 MDL-62007 profilefield_datetime: Add privacy files and unit tests 2018-05-03 14:57:41 +08:00
Jun Pataleta 2bb0f8923c Merge branch 'MDL-61956-master' of https://github.com/lucaboesch/moodle 2018-05-03 14:46:52 +08:00
Andrew Nicols 76cb5a56ea MDL-62240 file: Add includehash to shorten_filename + unit tests 2018-05-03 08:27:12 +02:00
Jake Dallimore 2891577f61 Merge branch 'MDL-62264-master' of git://github.com/cescobedo/moodle 2018-05-03 14:03:29 +08:00
Jun Pataleta 198c797a73 Merge branch 'MDL-62202-master' of git://github.com/sarjona/moodle 2018-05-03 13:31:53 +08:00
Andrew Nicols d28429859e Merge branch 'MDL-62009-master' of git://github.com/FMCorz/moodle 2018-05-03 12:15:54 +08:00
Andrew Nicols bacaac3a12 Merge branch 'wip-MDL-62312-master' of git://github.com/marinaglancy/moodle 2018-05-03 11:09:23 +08:00
Jake Dallimore bb35eb7da7 Merge branch 'MDL-62280-master' of git://github.com/andrewnicols/moodle 2018-05-03 10:52:48 +08:00
Marina Glancy 3127fe10b8 MDL-62312 course: Move core_publish renderer methods to course renderer 2018-05-03 10:08:37 +08:00
Jun Pataleta d16be43c05 Merge branch 'MDL-60414-master' of https://github.com/lucaboesch/moodle 2018-05-03 09:33:20 +08:00
Andrew Nicols 27013d397c Merge branch 'MDL-62163-master' of git://github.com/bmbrands/moodle 2018-05-03 09:25:55 +08:00
Andrew Nicols 159f057751 Merge branch 'wip-MDL-62276-master' of https://github.com/Beedell/moodle 2018-05-03 09:18:29 +08:00
AMOS bot 350fb7829a Automatically generated installer lang files 2018-05-03 00:06:17 +00:00
Jun Pataleta 3cfbce12a6 MDL-62312 admin: Move moodleorg_registration_message to admin renderer 2018-05-02 23:41:59 +08:00
Bas Brands ede6125a08 MDL-62163 Theme boost: add flex-grow 1 to atto felement content 2018-05-02 15:41:17 +02:00
Sara Arjona 54659fd14d MDL-62286 tool_policy: Add Policies link to the site footer 2018-05-02 14:20:49 +02:00
Andrew Nicols cc98914e85 MDL-61407 question: Allow formatters to ignore caps 2018-05-02 19:13:30 +08:00
Andrew Nicols 04507546ec MDL-61407 privacy: Track changes in any context 2018-05-02 19:13:30 +08:00
cescobedo 7ee1f3dede MDL-62310 core_admin: Implement Privacy API 2018-05-02 11:55:11 +02:00
Andrew Nicols b3359e2a3b MDL-62135 tool_log: Use plugintype_class_callback 2018-05-02 17:47:35 +08:00
Luca Bösch 000355f223 MDL-60414 theme_boost: bootstrap dropdown menu html spec char rendering 2018-05-02 11:40:51 +02:00
Andrew Nicols 028fb5c1f6 Merge branch 'MDL-62135-master' of git://github.com/FMCorz/moodle 2018-05-02 17:37:47 +08:00
cescobedo 61547a4d68 MDL-62264 core_mnet: Implement Privacy API 2018-05-02 11:17:03 +02:00
Frédéric Massart fc6cf1802e MDL-62135 logstore_legacy: Implement privacy API 2018-05-02 17:15:48 +08:00
Frédéric Massart 0f3bcbb92e MDL-62135 logstore_database: Implement privacy API 2018-05-02 17:15:48 +08:00
Frédéric Massart ab01e8a970 MDL-62135 logstore_standard: Implement privacy API 2018-05-02 17:15:48 +08:00
Frédéric Massart 2bc753db41 MDL-62135 tool_log: Implement privacy API 2018-05-02 17:15:47 +08:00
David Monllao cee06411b5 MDL-62218 mlbackend: Null privacy providers for core mlbackends 2018-05-02 11:14:33 +02:00
David Mudrák ea2ede2cf6 MDL-61905 workshop: Drop the old 1.x era tables
These tables may still contain relics of old personal data if the site
was installed before Moodle 2.0. We do not need these tables any more.
Following the "privacy by design" principles, we are dropping them now.
2018-05-02 10:35:01 +02:00
Luca Bösch 0188d5b5b4 MDL-61956 calendar: allow to see hidden course with switch role 2018-05-02 09:37:32 +02:00
Sara Arjona a97af76c0b MDL-62202 privacy: Move Privacy and policies category to Users page 2018-05-02 08:44:33 +02:00
Jun Pataleta 1b35627570 Merge branch 'MDL-62253-master' of git://github.com/FMCorz/moodle 2018-05-02 13:53:43 +08:00
Jun Pataleta c39c3edb8e Merge branch 'MDL-61898-master' of git://github.com/andrewnicols/moodle 2018-05-02 13:46:56 +08:00
Andrew Nicols fa43449aad Merge branch 'MDL-62150-master' of git://github.com/bmbrands/moodle 2018-05-02 13:38:07 +08:00
Andrew Nicols f7bac7ac8d MDL-61898 cache: Implement privacy API for all stores and locks 2018-05-02 13:36:07 +08:00
Andrew Nicols d1430a1e83 Merge branch 'MDL-62214-master-fixup' of https://github.com/FMCorz/moodle 2018-05-02 13:09:12 +08:00
Andrew Nicols 475fddc99a Merge branch 'MDL-61920_master' of git://github.com/markn86/moodle 2018-05-02 12:52:50 +08:00
Frédéric Massart f43d811e96 MDL-62214 tool_messageinbound: False negative due to time() usage
PHP Unit is so fast that my test couldn't cope with it!
2018-05-02 12:44:42 +08:00
Andrew Nicols 88ab001b25 Merge branch 'MDL-62266-master' of git://github.com/sarjona/moodle 2018-05-02 12:33:40 +08:00
Andrew Nicols db65b09188 Merge branch 'MDL-62214-master' of git://github.com/FMCorz/moodle 2018-05-02 12:28:52 +08:00
Jun Pataleta 030c58dcb9 Merge branch 'MDL-62189-master' of git://github.com/bmbrands/moodle 2018-05-02 12:24:20 +08:00
Ryan Wyllie 3041fe6faa MDL-62285 privacy: use context id when generating context path
moodle_content_writer::get_context_path() now includes the context
id in the path to ensure that it will always generate unique paths
even when the context names are not unique.
2018-05-02 12:08:58 +08:00
Andrew Nicols cd305dc26b MDL-61800 task: Reset the OUTPUT and PAGE on execution 2018-05-02 11:57:13 +08:00
Andrew Nicols 499f2ee9ce Merge branch 'MDL-62236-master' of git://github.com/FMCorz/moodle 2018-05-02 10:51:50 +08:00
Jake Dallimore e1ceb10ee8 Merge branch 'MDL-62268-master' of git://github.com/bmbrands/moodle 2018-05-02 10:12:32 +08:00
Andrew Nicols f4524f8da3 MDL-59845 tool_lp: Add js tracking around UI refresh 2018-05-02 09:15:43 +08:00
Andrew Nicols 850dfdeadb Merge branch 'MDL-62212-master' of git://github.com/bmbrands/moodle 2018-05-02 08:27:34 +08:00
Andrew Nicols 2a17a616a3 Merge branch 'MDL-62149-master' of git://github.com/ryanwyllie/moodle 2018-05-02 07:39:30 +08:00
Andrew Nicols 0fc5b4e17a Merge branch 'MDL-62186-master' of git://github.com/bmbrands/moodle 2018-05-02 07:31:27 +08:00
Eloy Lafuente (stronk7) 446d8483a3 on-demand release 3.5beta+ 2018-05-01 23:04:48 +02:00
Eloy Lafuente (stronk7) 40938ae2d8 Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install 2018-05-01 23:04:45 +02:00
Frédéric Massart 048b266f14 MDL-62167 core_badges: Remove usage of a reserved keyword of Oracle 2018-05-01 15:21:46 +02:00
Frédéric Massart 91161a6325 MDL-62167 core_badges: Prevent SQL error with Oracle 2018-05-01 15:21:46 +02:00
John Beedell a3893e1b4d MDL-62276 Quiz: Review options section poor layout in boost theme 2018-05-01 11:26:22 +01:00
Andrew Nicols 16f18cb984 Merge branch 'wip-MDL-61308-master-unitfix' of git://github.com/abgreeve/moodle 2018-05-01 12:21:55 +02:00
Adrian Greeve bfb57d2c16 MDL-61308 assignsubmission_comments: Unit test ordering fix. 2018-05-01 12:21:47 +02:00
Andrew Nicols 7085e286c0 Merge branch 'MDL-62231-master' of git://github.com/rezaies/moodle 2018-05-01 12:21:36 +02:00
Bas Brands d42139ace6 MDL-62189 Theme boost: add filepicker container fix padding 2018-05-01 09:05:44 +02:00
Shamim Rezaie 1de2a1598e MDL-62231 block_recent_activity: Implement null provider for the block 2018-05-01 14:07:17 +10:00
Andrew Nicols 39b17e9933 Merge branch 'wip-MDL-62138-master' of git://github.com/marinaglancy/moodle 2018-05-01 11:46:27 +08:00
Andrew Nicols cb3cd93b39 Merge branch 'MDL-62209-master' of git://github.com/junpataleta/moodle 2018-05-01 11:42:44 +08:00
Ryan Wyllie 3dee4ca6d9 MDL-62282 boost: centre no progress icon in overview block 2018-05-01 11:41:06 +08:00
Andrew Nicols c9591cdde6 Merge branch 'MDL-61411-master' of git://github.com/Kathrin84/moodle 2018-05-01 11:21:23 +08:00
Andrew Nicols 06cab84176 Merge branch 'MDL-62167-master' of git://github.com/FMCorz/moodle 2018-05-01 11:10:31 +08:00
Andrew Nicols 3dd3ed3af2 MDL-62280 xhprof: Support tideways_xhprof 2018-05-01 11:02:27 +08:00
Frédéric Massart 3fcfc19743 MDL-62009 core_grades: Implement privacy API 2018-05-01 10:47:04 +08:00
Jun Pataleta 1891ed7bb0 Merge branch 'MDL-62060-master' of git://github.com/rezaies/moodle 2018-05-01 10:44:59 +08:00
Shamim Rezaie c5d720ab51 MDL-62060 core_privacy: Make sure the exported json is human readable 2018-05-01 12:35:00 +10:00
Frédéric Massart d35d17a14c MDL-62214 tool_messageinbound: Implement privacy API 2018-05-01 09:21:56 +08:00
Frédéric Massart 98a2f802c4 MDL-62214 tool_messageinbound: Delete old messages pending verification 2018-05-01 09:21:56 +08:00
Frédéric Massart 1582d54bb0 MDL-62253 core_auth: Implement privacy API 2018-05-01 09:14:58 +08:00
Jake Dallimore 15729d9ffa Merge branch 'MDL-62005-master' of git://github.com/rezaies/moodle 2018-05-01 09:10:18 +08:00
AMOS bot 1e096d96a2 Automatically generated installer lang files 2018-05-01 00:06:50 +00:00
Jun Pataleta 3b1d98ba73 Merge branch 'MDL-62201-master' of git://github.com/bmbrands/moodle 2018-04-30 21:54:35 +08:00
Jun Pataleta 732ebbe0fe Merge branch 'MDL-62248-master-fix' of git://github.com/andrewnicols/moodle 2018-04-30 21:23:14 +08:00
Andrew Nicols 9b7527defc MDL-62248 core_privacy: Add getter for user context prefs 2018-04-30 20:35:56 +08:00
Bas Brands 264b7dffb4 MDL-62268 Theme boost: messaging interface rows 2018-04-30 12:51:32 +02:00
Mark Nelson 3467e8ce3d MDL-61920 mod_lti: implement privacy provider 2018-04-30 18:43:19 +08:00
Jun Pataleta 7816cac9fe Merge branch 'MDL-61012_master' of https://github.com/troywilliams/moodle 2018-04-30 17:29:25 +08:00
Sara Arjona edbee4c4b6 MDL-62266 core_availability: Implement privacy API 2018-04-30 11:20:16 +02:00
Mark Nelson 13fadc8767 MDL-61920 ltiservice_toolsettings: implement privacy provider 2018-04-30 17:08:00 +08:00
Mark Nelson 897a6e824f MDL-61920 ltiservice_toolproxy: implement privacy provider 2018-04-30 17:08:00 +08:00
Mark Nelson 583f84d7cb MDL-61920 ltiservice_profile: implement privacy provider 2018-04-30 17:08:00 +08:00
Mark Nelson 03d6e06400 MDL-61920 ltiservice_memberships: implement privacy provider 2018-04-30 17:07:59 +08:00
Mark Nelson 2b3c4538f1 MDL-61920 ltiservice_gradebookservices: implement privacy provider 2018-04-30 17:07:59 +08:00
Ryan Wyllie 8f0f0a5903 MDL-62149 quiz: include tag ids in random question link 2018-04-30 17:03:03 +08:00
Shamim Rezaie 31b76f4f59 MDL-62005 block_comments: Add implementation of Privacy API 2018-04-30 18:20:04 +10:00
Andrew Nicols 81fa241788 MDL-62248 core_privacy: Unit test for get_user_preferences 2018-04-30 16:00:24 +08:00
Andrew Nicols 970be4496e Merge branch 'MDL-62248-master' of git://github.com/FMCorz/moodle 2018-04-30 15:59:39 +08:00
Bas Brands 3f86772ae2 MDL-62201 Theme boost: block myoverview course cards height
- remove css for setting a fixed height on course cards
on large devices for block overview
2018-04-30 09:54:35 +02:00
Bas Brands 6c88a36f8f MDL-62150 Theme boost: warning position "x" in atto
hardcode position of "x" because of failing calc function
2018-04-30 09:28:09 +02:00
Frédéric Massart cd7baa11c9 MDL-62248 core_block: Implement privacy API 2018-04-30 15:23:41 +08:00
Frédéric Massart 97b05c8f1e MDL-62248 core_privacy: Test writer writes user prefs in right contexts 2018-04-30 15:23:41 +08:00
Shamim Rezaie c287f0fdd9 MDL-62059 mod_book: Implement null provider for mod_book 2018-04-30 16:14:33 +10:00
Andrew Nicols 26bb44e5f4 MDL-61898 core_cache: Implement Privacy API 2018-04-30 14:07:27 +08:00
Andrew Nicols 2203ef4f74 Merge branch 'MDL-62235-master' of git://github.com/FMCorz/moodle 2018-04-30 13:57:48 +08:00
Frédéric Massart c9f95d296a MDL-62235 core_files: Implement privacy API 2018-04-30 13:41:20 +08:00
Nadav Kavalerchik 7092ba71de MDL-62254 Theme: Fix accesshide class in RTL mode 2018-04-30 08:24:59 +03:00
Andrew Nicols a88287c67b Merge branch 'MDL-62208-master' of git://github.com/FMCorz/moodle 2018-04-30 13:07:52 +08:00
Andrew Nicols e054f43f5b Merge branch 'MDL-62213-master' of git://github.com/bmbrands/moodle 2018-04-30 13:03:13 +08:00
Andrew Nicols 52f9b15d64 Merge branch 'MDL-62177-master' of git://github.com/bmbrands/moodle 2018-04-30 11:49:05 +08:00
Andrew Nicols a924b2616a Merge branch 'MDL-62241-master-typehint' of git://github.com/mudrd8mz/moodle 2018-04-30 11:43:46 +08:00
Andrew Nicols bebff35401 Merge branch 'MDL-62219-master' of git://github.com/bmbrands/moodle 2018-04-30 10:34:09 +08:00
Andrew Nicols 11dc37dacc Merge branch 'MDL-61959-master' of git://github.com/sarjona/moodle 2018-04-30 10:16:23 +08:00
Andrew Nicols 11446e8dfc Merge branch 'MDL-62155-master' of git://github.com/bmbrands/moodle 2018-04-30 09:37:14 +08:00
Andrew Nicols 915fd9e6c3 Merge branch 'MDL-62173-master' of git://github.com/bmbrands/moodle 2018-04-30 09:28:53 +08:00
Andrew Nicols 3d110a5f6f MDL-61308 mod_assign: Whitespace fix 2018-04-30 09:27:12 +08:00
Andrew Nicols 1c42b1d6ed Merge branch 'wip-MDL-61308-master' of git://github.com/abgreeve/moodle 2018-04-30 09:15:25 +08:00
AMOS bot 2750523534 Automatically generated installer lang files 2018-04-30 00:06:16 +00:00
Frédéric Massart d21c5e384d MDL-62236 core_webservice: Implement privacy API 2018-04-29 10:08:40 +08:00
Bas Brands ebdfc3c935 MDL-62058 Theme boost: restore scrolling in long navbar dropdowns 2018-04-28 13:23:55 +02:00
David Mudrák 15ce5b7e9b MDL-62241 privacy: Include the unit tests from the 3.3 branch
The tests do not actually do anything, they just make sure that the
method signature is correct on the given PHP version. They are mostly
relevant and useful on 3.3.
2018-04-27 15:25:38 +02:00
Eloy Lafuente (stronk7) af099b484c on-demand release 3.5beta+ 2018-04-27 13:16:41 +02:00
Eloy Lafuente (stronk7) c4a5c8440a MDL-61993 profilefield_checkbox: take rid of oracle reserved word 2018-04-27 10:41:46 +02:00
Frédéric Massart 739b59610a MDL-61877 core_competency: Handle comments in privacy API 2018-04-27 11:29:30 +08:00
Frédéric Massart f8a6a533c2 MDL-61877 core_competency: Implement privacy API 2018-04-27 11:29:29 +08:00
Frédéric Massart d498287fd7 MDL-62167 core_badges: Implement privacy API 2018-04-27 10:49:48 +08:00
Jun Pataleta d8844b3f8c Merge branch 'MDL-62172-master' of git://github.com/bmbrands/moodle 2018-04-27 09:55:08 +08:00
Eloy Lafuente (stronk7) 9827740b01 Merge branch 'MDL-60882-master' of git://github.com/jleyva/moodle 2018-04-27 01:36:11 +02:00
Eloy Lafuente (stronk7) 33a258b573 Merge branch 'wip_MDL-62011_master_approval_of_course_request' of https://github.com/grabs/moodle 2018-04-27 01:29:47 +02:00
Eloy Lafuente (stronk7) a52770f010 Merge branch 'MDL-61993-master' of git://github.com/mihailges/moodle 2018-04-27 00:56:42 +02:00
Eloy Lafuente (stronk7) a76032ce8d Merge branch 'MDL-62152-master' of git://github.com/bmbrands/moodle 2018-04-26 22:44:39 +02:00
Eloy Lafuente (stronk7) 06f49a5eb2 Merge branch 'MDL-61863-master' of git://github.com/zig-moodle/moodle 2018-04-26 22:31:26 +02:00
Eloy Lafuente (stronk7) e6c1e22197 Merge branch 'MDL-61715-master' of https://github.com/lethevinh/moodle 2018-04-26 22:22:43 +02:00
Eloy Lafuente (stronk7) e754bce399 Merge branch 'MDL-62070-master' of git://github.com/bmbrands/moodle 2018-04-26 22:13:47 +02:00
Sara Arjona df7cc2cc7f MDL-61959 mod_scorm: Implement privacy API 2018-04-26 19:56:33 +02:00
Sara Arjona b078358fa7 MDL-61959 scormreport_objectives: Implement privacy API 2018-04-26 19:00:18 +02:00
sarjona 3127efef0c MDL-61959 scormreport_interactions: Implement privacy API 2018-04-26 19:00:18 +02:00
Sara Arjona 353f1bfb37 MDL-61959 scormreport_graphs: Implement privacy API 2018-04-26 19:00:18 +02:00
sarjona 0b01f6f391 MDL-61959 scormreport_basic: Implement privacy API 2018-04-26 19:00:18 +02:00
Jun Pataleta bb874345cf Merge branch 'MDL-61986' of git://github.com/stronk7/moodle 2018-04-27 00:47:02 +08:00
Eloy Lafuente (stronk7) 75bf06128f MDL-61986 mod_lesson: oracle order by clob fix 2018-04-26 18:30:40 +02:00
Bas Brands 540ea1bc7f MDL-62155 Theme boost: forum post element positioning issues 2018-04-26 18:23:41 +02:00
Bas Brands c3f2290d11 MDL-62227 lib_form: add .alert-warning to unstyled alert 2018-04-26 18:06:02 +02:00
Eloy Lafuente (stronk7) e2afdc9c21 Merge branch 'MDL-62224_master' of git://github.com/markn86/moodle 2018-04-26 17:38:14 +02:00
Bas Brands 9fd2b9ac5f MDL-62219 Theme boost: add cursor not allowed for disabled input 2018-04-26 17:27:27 +02:00
Bas Brands 5726577fd4 MDL-62213 Theme boost: calendar filter icon positions 2018-04-26 16:41:07 +02:00
Bas Brands c7522f838c MDL-62212 Theme boost: Compatibility with row-fluid
- used in messaging mustache templates
2018-04-26 15:54:43 +02:00
David Monllao 7dc966479c Merge branch 'MDL-62069-master' of git://github.com/bmbrands/moodle 2018-04-26 15:49:26 +02:00
Zig Tan 49d6fb0379 MDL-61693 core_calendar: Implemented privacy providers 2018-04-26 21:10:49 +08:00
Bas Brands 78d77c4223 MDL-62173 Theme bootstrapbase: clean backup restore UI
-fix display of detail-pair. These are not rendered well
on theme clean for small devices
2018-04-26 14:55:59 +02:00
Mark Nelson 97fcf562ba MDL-62224 core_rss: implement correct provider 2018-04-26 20:16:22 +08:00
Bas Brands 716b5ffa09 MDL-62069 Theme boost: force 100% width on accepted filetypes 2018-04-26 13:17:26 +02:00
Kathrin Osswald 07b203db88 MDL-61411 theme Boost: Close navigation on all items (on small screens) 2018-04-26 13:17:12 +02:00
Bas Brands 5f29d191d9 MDL-62070 Theme boost: quiz update for BS4 stable
- update .sr-only attributes for editing drag and drop on image
    - set box-sizing for drop field
2018-04-26 13:12:05 +02:00
Juan Leyva 8c9a196409 MDL-61521 course: Add missing text formatting for category name 2018-04-26 12:21:22 +02:00
David Monllao 258bf5c4e8 Merge branch 'MDL-62024-master' of git://github.com/bmbrands/moodle 2018-04-26 11:34:00 +02:00
Juan Leyva 10b67ef9f1 MDL-60882 mod_choice: Avoid teacher delete all responses
Teachers should be able to delete all responses but when indicating the
response ids.
When the response ids are not passed, only their own responses should
be deleted (as the function documentation said).
2018-04-26 10:25:45 +02:00
Andrew Nicols 46e7b6eecf Merge branch 'MDL-61947-master' of git://github.com/zig-moodle/moodle 2018-04-26 14:51:19 +08:00
Jun Pataleta aad67f481a Merge branch 'MDL-62188-master' of git://github.com/sarjona/moodle 2018-04-26 14:50:04 +08:00
Adrian Greeve f8650bb7a3 MDL-61308 assign_feedback: Privacy code for user rights. 2018-04-26 14:17:55 +08:00
Adrian Greeve 5843543338 MDL-61308 assign_submission: Privacy code for user rights. 2018-04-26 14:17:55 +08:00
Adrian Greeve 5c41cd77f8 MDL-61308 mod_assign: Adding privacy classes. 2018-04-26 14:17:55 +08:00
Sara Arjona 697bed6bb7 MDL-62188 enrol: Avoid ignoring the Recover grades default setting 2018-04-26 08:13:13 +02:00
Frédéric Massart 790c89b34d MDL-62208 core_fileconverter: Implement privacy API 2018-04-26 12:54:01 +08:00
Zig Tan 25e213ad8f MDL-61947 core_my: Implemented privacy providers 2018-04-26 12:50:22 +08:00
Jun Pataleta 38ffa48cf5 MDL-62209 tool_dataprivacy: Sort requests by ascending status
Sort requests by ascending status and request date so that the DPO
can go through the list in a FIFO fashion
2018-04-26 12:17:24 +08:00
Jun Pataleta 6ef65b8a85 Merge branch 'MDL-61669-master' of git://github.com/jleyva/moodle 2018-04-26 11:51:22 +08:00
Jun Pataleta 8e82081176 Merge branch 'MDL-62072-master' of git://github.com/bmbrands/moodle 2018-04-26 11:17:27 +08:00
Jun Pataleta e55b7082be Merge branch 'MDL-62176-master' of git://github.com/bmbrands/moodle 2018-04-26 11:00:17 +08:00
Mihail Geshoski 079114d70b MDL-61993 profilefield_checkbox: Add privacy files and unit tests 2018-04-26 10:28:14 +08:00
Eloy Lafuente (stronk7) 1b4983fc90 Merge branch 'MDL-61979-master' of git://github.com/zig-moodle/moodle 2018-04-26 02:44:16 +02:00
Eloy Lafuente (stronk7) 7bff0ee145 Merge branch 'MDL-61998-master' of git://github.com/sarjona/moodle 2018-04-26 02:39:17 +02:00
Andrew Nicols c8fc9c61a2 Merge branch 'MDL-61944-master' of git://github.com/cescobedo/moodle 2018-04-26 08:17:24 +08:00
Eloy Lafuente (stronk7) e971596371 Merge branch 'MDL-62181-master-enfix' of git://github.com/mudrd8mz/moodle 2018-04-25 19:09:35 +02:00
David Monllao 969d649b79 Merge branch 'MDL-62160-master' of git://github.com/bmbrands/moodle 2018-04-25 18:10:13 +02:00
David Monllao e06a28b539 Merge branch 'MDL-62159-master' of git://github.com/bmbrands/moodle 2018-04-25 17:57:37 +02:00
David Monllao 6fb429dd79 Merge branch 'MDL-62175-master' of git://github.com/jleyva/moodle 2018-04-25 17:37:22 +02:00
David Monllao 0d1707f053 Merge branch 'MDL-61986-master' of git://github.com/FMCorz/moodle 2018-04-25 16:26:03 +02:00
cescobedo 30e44bfcc4 MDL-61944 tool_mobile: Implement Privacy API 2018-04-25 15:33:50 +02:00
Bas Brands ed0e21c0ef MDL-62177 Theme boost: set max height for navbar logo 2018-04-25 14:32:42 +02:00
Bas Brands a24bf64ffd MDL-62176 Theme boost: show advance form options need display: flex 2018-04-25 13:43:35 +02:00
Bas Brands d24fab1182 MDL-62172 Theme boostrapbase: align advanced checkbox in mform 2018-04-25 12:26:47 +02:00
Bas Brands e58362ed1e MDL-62160 Theme boost: center buttons on notification popup 2018-04-25 10:18:36 +02:00
Bas Brands b6ebda707f MDL-62159 Theme boost: prevent rtl flipping dropdowns 2018-04-25 09:41:22 +02:00
David Mudrák 60ecb39fb6 MDL-62181 lang: Fix changed admin node title in behat scenario 2018-04-24 22:05:30 +02:00
Helen Foster 970c1a4bf1 MDL-62181 lang: Merge English strings from the en_fix language pack
Significant string changes:

* sitepolicies, core_admin - 'Site policies' changed to 'Site security
  settings'
* processexpirationstask, enrol_paypal - more explanatory name
* sitepolicy_help and sitepolicyguest_help, core_admin - setting will
  only have an effect if the site policy handler is set to default
* sitepolicyhandler_desc,core_admin - improved wording
* pathtodot_help, core_admin - Windows and Mac paths
* debugstringids_desc, core_admin - improved explanation
2018-04-24 22:05:30 +02:00
Bas Brands 31c04fec7c MDL-62072 Theme boost: position dropdowns for autocompletion fields 2018-04-24 16:22:32 +02:00
Juan Leyva 04df75ceeb MDL-61669 tool_mobile: Return new app policy setting via WS 2018-04-24 16:10:25 +02:00
Juan Leyva acee831165 MDL-61669 tool_mobile: New app policy setting 2018-04-24 16:10:25 +02:00
Bas Brands 323ce3952b MDL-62152 Theme boost: reset link colour to WCAG 2.0 approved contrast 2018-04-24 15:57:18 +02:00
Bas Brands 10acff4ada MDL-62186 Theme boost: change padding of quiz editing input field 2018-04-24 14:52:34 +02:00
Juan Leyva e709853ee6 MDL-62175 auth_email: Fix site policy enabled check 2018-04-24 11:48:00 +02:00
vinhle 8e0bebd1cd MDL-61715 question: qbank_chooser is not intelligent about headings 2018-04-24 16:11:47 +07:00
Troy Williams f9a7a98a4a MDL-61012 course: Allow extending class to set 2018-04-24 16:13:29 +12:00
Zig Tan 7d5667ee57 MDL-61863 core_notes: Implemented privacy providers 2018-04-24 12:06:46 +08:00
Bas Brands c706699a0e MDL-62024 Theme boost: fix icon alignment for actionmenu 2018-04-23 21:09:26 +02:00
Sara Arjona 5a86bf1448 MDL-61998 cohort: Implement privacy API 2018-04-23 11:41:04 +02:00
Marina Glancy dde3d58eb1 MDL-62138 core: remove core_register and core_publish
These components have no classes and no strings and have no purspose. Component core_hub is responsible
for storing data, defining classes and strings for site registration and course publishing

This also reverts MDL-61945
2018-04-23 13:17:12 +08:00
Frédéric Massart 83ef2b2cf8 MDL-61986 mod_lesson: Implement privacy API 2018-04-22 17:31:00 +08:00
Andreas Grabs 9f9d7bbe87 MDL-62011 course: Approval of a course request fails 2018-04-21 20:55:59 +02:00
Zig Tan bcc08d3b5e MDL-61979 tool_cohortroles: Implemented privacy providers 2018-04-18 10:50:27 +08:00
Marina Glancy c182f060fa MDL-61876 admin: set default forceclean=1 2018-04-17 09:39:22 +08:00
Marina Glancy b197da8770 MDL-61876 question: apply format_text to input only 2018-04-17 09:39:22 +08:00
1176 changed files with 61216 additions and 2751 deletions
+1 -1
View File
@@ -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,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';
}
}
}
+1 -1
View File
@@ -110,7 +110,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();
-41
View File
@@ -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]);
}
}
+9
View File
@@ -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;
}
+379
View File
@@ -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;
}
}
+477
View File
@@ -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;
}
}
+6 -1
View File
@@ -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));
-80
View File
@@ -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);
}
-3
View File
@@ -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,
-1
View File
@@ -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')));
+46
View File
@@ -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);
}
+2 -2
View File
@@ -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).
+3 -3
View File
@@ -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';
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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 date/time of when the cohort role mapping was created.';
$string['privacy:metadata:tool_cohortroles:timemodified'] = 'The date/time of 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);
}
}
}
+2 -2
View File
@@ -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(
+2 -2
View File
@@ -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)
+66 -7
View File
@@ -37,6 +37,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;
@@ -186,8 +187,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 (!api::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.
@@ -218,20 +236,34 @@ class api {
* @throws dml_exception
*/
public static function get_data_requests($userid = 0) {
global $USER;
global $DB, $USER;
$results = [];
$sort = 'status ASC, timemodified ASC';
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$select = "userid = :userid OR requestedby = :requestedby";
$select = "(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);
$select .= " AND userid $insql";
$params = array_merge($params, $inparams);
$results = data_request::get_records_select($select, $params, $sort);
} 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', '');
$results = data_request::get_records(null, $sort, '');
}
}
@@ -289,17 +321,19 @@ 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);
}
$datarequest->set('dpocomment', $comment);
return $datarequest->update();
}
@@ -417,7 +451,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 +487,19 @@ 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);
}
/**
* Creates a new data purpose.
*
@@ -477,6 +524,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 +815,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 +824,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 +926,7 @@ class api {
}
$contexts = [];
}
$contexts[] = $record->contextid;
$lastcomponent = $record->component;
}
@@ -91,6 +91,7 @@ abstract class expired_contexts_manager {
}
$privacymanager = new \core_privacy\manager();
$privacymanager->set_observer(new \tool_dataprivacy\manager_observer());
foreach ($this->get_context_levels() as $level) {
@@ -115,12 +115,35 @@ class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
* @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)) {
$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;
+2 -2
View File
@@ -96,8 +96,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;
@@ -55,6 +55,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 +80,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 +89,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');
}
}
@@ -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);
}
@@ -108,4 +108,42 @@ class helper {
throw new moodle_exception('errorinvalidrequeststatus', '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;
}
}
@@ -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);
@@ -122,11 +128,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;
}
@@ -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;
}
/**
@@ -73,6 +73,11 @@ class data_requests_page implements renderable, templatable {
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
$data->newdatarequesturl->param('manage', true);
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');
@@ -67,18 +67,39 @@ 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]);
$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);
if ($request->get('userid') != $USER->id) {
// 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) {
@@ -102,7 +123,7 @@ 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) {
if ($candownload && $usercontext) {
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $requestid, '/',
'export.zip', true);
$downloadtext = get_string('download', 'tool_dataprivacy');
@@ -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);
}
@@ -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);
@@ -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;
}
@@ -87,6 +88,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();
@@ -109,6 +112,8 @@ class process_data_request_task extends adhoc_task {
// Delete the data.
$manager = new \core_privacy\manager();
$manager->set_observer(new \tool_dataprivacy\manager_observer());
$manager->delete_data_for_user($approvedclcollection);
}
@@ -181,23 +186,27 @@ class process_data_request_task extends adhoc_task {
}
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 to requester as well if this request was made on behalf of another user who's not a DPO,
// and has the capability to make data requests for the user (e.g. Parent).
if (!api::is_site_dpo($request->requestedby) && $foruser->id != $request->requestedby) {
// Ensure the requester has the capability to make data requests for this user.
if (api::can_create_data_request_for_user($request->userid, $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 ($emailonly) {
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
} else {
message_send($message);
// Send message.
if ($emailonly) {
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
} else {
message_send($message);
}
mtrace('Message sent to requester: ' . $messagetextdata['username']);
}
mtrace('Message sent to requester: ' . $messagetextdata['username']);
}
if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
+8 -2
View File
@@ -57,7 +57,7 @@ if (!\tool_dataprivacy\api::can_contact_dpo()) {
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), \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 +68,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,6 +38,9 @@ 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.
*
@@ -48,7 +52,8 @@ class tool_dataprivacy_data_request_form extends moodleform {
global $DB, $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
@@ -58,27 +63,12 @@ class tool_dataprivacy_data_request_form extends moodleform {
} 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');
+8
View File
@@ -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'
],
];
@@ -37,7 +37,10 @@ $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';
@@ -46,7 +49,7 @@ $string['confirmapproval'] = 'Do you really want to approve this data request?';
$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_desc'] = 'If enabled, users will be able to contact the Data Protection Officer and make a data request via a link on their profile page.';
$string['contextlevelname10'] = 'Site';
$string['contextlevelname30'] = 'Users';
$string['contextlevelname40'] = 'Course categories';
@@ -54,15 +57,17 @@ $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 Data Protection 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['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['daterequested'] = 'Date requested';
@@ -78,7 +83,7 @@ $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['dporolemapping_desc'] = 'The Data Protection Officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a Data Protection Officer role mapping option.';
$string['editcategories'] = 'Edit categories';
$string['editcategory'] = 'Edit category';
$string['editcategories'] = 'Edit categories';
@@ -89,18 +94,21 @@ $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 occured while processing privacy data";
$string['exceptionnotificationbody'] = "<p>Exception occured while calling <b>{\$a->fullmethodname}</b>.<br>This means that plugin <b>{\$a->component}</b> did not complete processing data. Below you can find exception information that can be passed to the plugin developer.</p><pre>{\$a->message}<br>\n\n{\$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['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';
@@ -136,11 +144,13 @@ $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['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})';
@@ -155,7 +165,7 @@ $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';
@@ -168,12 +178,15 @@ $string['privacy:metadata:request'] = 'Information from personal data requests (
$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 Data Protection Officer regarding the request.';
$string['privacy:metadata:request:timecreated'] = 'The timestamp indicating when the request was made by the user.';
$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';
@@ -181,12 +194,13 @@ $string['replyto'] = 'Reply to';
$string['requestactions'] = 'Actions';
$string['requestby'] = '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['requestemailintro'] = 'You have received a data request:';
$string['requestfor'] = 'Requesting for';
$string['requeststatus'] = 'Status';
$string['requestsubmitted'] = 'Your request has been submitted to the site\'s Data Protection Officer';
$string['requesttype'] = 'Type';
$string['requesttypeuser'] = '{$a->typename} ({$a->user})';
$string['requesttype_help'] = 'Select the reason why you would like to contact the site\'s Data Protection Officer';
$string['requesttypedelete'] = 'Delete all of my personal data';
$string['requesttypedeleteshort'] = 'Delete';
@@ -200,6 +214,7 @@ $string['resultdeleted'] = 'You recently requested to have your account and pers
$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';
@@ -216,6 +231,7 @@ $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';
+1 -1
View File
@@ -49,7 +49,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)
);
+13 -1
View File
@@ -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,13 @@
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;
}
@@ -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>
@@ -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}}
@@ -95,6 +95,10 @@
}
}}
{{#httpsite}}
{{> core/notification_warning}}
{{/httpsite}}
<div data-region="datarequests">
<div class="m-t-1 m-b-1">
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
@@ -75,6 +75,10 @@
}
}}
{{#httpsite}}
{{> core/notification_warning}}
{{/httpsite}}
<div data-region="datarequests">
<div class="m-t-1 m-b-1">
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
@@ -67,7 +67,7 @@
<table class="generaltable fullwidth">
<caption class="accesshide">{{#str}}purposeslist, tool_dataprivacy{{/str}}</caption>
<thead>
<tr style="display: flex;">
<tr>
<th scope="col" class="col-md-2">{{#str}}name{{/str}}</th>
<th scope="col" class="col-md-3">{{#str}}description{{/str}}</th>
<th scope="col" class="col-md-2">{{#str}}lawfulbases, tool_dataprivacy{{/str}}</th>
+194
View File
@@ -57,6 +57,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
public function test_update_request_status() {
$generator = new testing_data_generator();
$s1 = $generator->create_user();
$this->setUser($s1);
// Create the sample data request.
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
@@ -145,6 +146,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
// Create the sample data request.
$this->setUser($s1);
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
$requestid = $datarequest->get('id');
@@ -186,6 +188,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
// Create the sample data request.
$this->setUser($s1);
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
$requestid = $datarequest->get('id');
@@ -203,6 +206,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$teacher = $generator->create_user();
// Create the sample data request.
$this->setUser($student);
$datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
$requestid = $datarequest->get('id');
@@ -286,6 +290,71 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
$this->assertEquals($user->id, $datarequest->get('userid'));
$this->assertEquals($user->id, $datarequest->get('requestedby'));
$this->assertEquals(0, $datarequest->get('dpo'));
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
$this->assertEquals($comment, $datarequest->get('comments'));
// Test adhoc task creation.
$adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
$this->assertCount(1, $adhoctasks);
}
/**
* Test for api::create_data_request() made by DPO.
*/
public function test_create_data_request_by_dpo() {
global $USER;
$generator = new testing_data_generator();
$user = $generator->create_user();
$comment = 'sample comment';
// Login as DPO (Admin is DPO by default).
$this->setAdminUser();
// Test data request creation.
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
$this->assertEquals($user->id, $datarequest->get('userid'));
$this->assertEquals($USER->id, $datarequest->get('requestedby'));
$this->assertEquals($USER->id, $datarequest->get('dpo'));
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
$this->assertEquals($comment, $datarequest->get('comments'));
// Test adhoc task creation.
$adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
$this->assertCount(1, $adhoctasks);
}
/**
* Test for api::create_data_request() made by a parent.
*/
public function test_create_data_request_by_parent() {
global $DB;
$generator = new testing_data_generator();
$user = $generator->create_user();
$parent = $generator->create_user();
$comment = 'sample comment';
// Get the teacher role pretend it's the parent roles ;).
$systemcontext = context_system::instance();
$usercontext = context_user::instance($user->id);
$parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
// Give the manager role with the capability to manage data requests.
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
// Assign the parent to user.
role_assign($parentroleid, $parent->id, $usercontext->id);
// Login as the user's parent.
$this->setUser($parent);
// Test data request creation.
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
$this->assertEquals($user->id, $datarequest->get('userid'));
$this->assertEquals($parent->id, $datarequest->get('requestedby'));
$this->assertEquals(0, $datarequest->get('dpo'));
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
$this->assertEquals($comment, $datarequest->get('comments'));
@@ -351,8 +420,10 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$comment = 'sample comment';
// Make a data request as user 1.
$this->setUser($user1);
$d1 = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
// Make a data request as user 2.
$this->setUser($user2);
$d2 = api::create_data_request($user2->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
// Fetching data requests of specific users.
@@ -406,6 +477,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
$user1 = $generator->create_user();
// Make a data request as user 1.
$this->setUser($user1);
$request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
// Set the status.
api::update_request_status($request->get('id'), $status);
@@ -1073,4 +1145,126 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
[$module1, $module2]
];
}
/**
* Test that delete requests filter out protected purpose contexts.
*/
public function test_add_request_contexts_with_status_delete() {
$data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
$contextids = $data->list->get_contextids();
$this->assertCount(1, $contextids);
$this->assertEquals($data->contexts->unprotected, $contextids);
}
/**
* Test that export requests don't filter out protected purpose contexts.
*/
public function test_add_request_contexts_with_status_export() {
$data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
$contextids = $data->list->get_contextids();
$this->assertCount(2, $contextids);
$this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
}
/**
* Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
*
* @param int $type The type of request to create
* @return \stdClass
*/
protected function setup_test_add_request_contexts_with_status($type) {
$this->setAdminUser();
// User under test.
$s1 = $this->getDataGenerator()->create_user();
// Create three sample contexts.
// 1 which should not be returned; and
// 1 which will be returned and is not protected; and
// 1 which will be returned and is protected.
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$c3 = $this->getDataGenerator()->create_course();
$ctx1 = \context_course::instance($c1->id);
$ctx2 = \context_course::instance($c2->id);
$ctx3 = \context_course::instance($c3->id);
$unprotected = api::create_purpose((object)[
'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
$protected = api::create_purpose((object) [
'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
$cat1 = api::create_category((object)['name' => 'a']);
// Set the defaults.
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
\context_helper::get_class_for_level(CONTEXT_SYSTEM)
);
set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
$contextinstance1 = api::set_context_instance((object) [
'contextid' => $ctx1->id,
'purposeid' => $unprotected->get('id'),
'categoryid' => $cat1->get('id'),
]);
$contextinstance2 = api::set_context_instance((object) [
'contextid' => $ctx2->id,
'purposeid' => $unprotected->get('id'),
'categoryid' => $cat1->get('id'),
]);
$contextinstance3 = api::set_context_instance((object) [
'contextid' => $ctx3->id,
'purposeid' => $protected->get('id'),
'categoryid' => $cat1->get('id'),
]);
$collection = new \core_privacy\local\request\contextlist_collection($s1->id);
$contextlist = new \core_privacy\local\request\contextlist();
$contextlist->set_component('tool_dataprivacy');
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
'ctx2' => $ctx2->id,
'ctx3' => $ctx3->id,
]);
$collection->add_contextlist($contextlist);
// Create the sample data request.
$datarequest = api::create_data_request($s1->id, $type);
$requestid = $datarequest->get('id');
// Add the full collection with contexts 2, and 3.
api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
// Mark it as approved.
api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
// Fetch the list.
$approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
return (object) [
'contexts' => (object) [
'unused' => [
$ctx1->id,
],
'used' => [
$ctx2->id,
$ctx3->id,
],
'unprotected' => [
$ctx2->id,
],
'protected' => [
$ctx3->id,
],
],
'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
];
}
}
@@ -84,6 +84,22 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$this->getDataGenerator()->enrol_user($user3->id, $course2->id, 'student');
$this->getDataGenerator()->enrol_user($user4->id, $course3->id, 'student');
// Add an activity and some data for user 2.
$assignmod = $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
$data = (object) [
'assignment' => $assignmod->id,
'userid' => $user2->id,
'timecreated' => time(),
'timemodified' => time(),
'status' => 'new',
'groupid' => 0,
'attemptnumber' => 0,
'latest' => 1,
];
$DB->insert_record('assign_submission', $data);
// We should have one record in the assign submission table.
$this->assertEquals(1, $DB->count_records('assign_submission'));
// Users without lastaccess are skipped as well as users enroled in courses with no end date.
$expired = new \tool_dataprivacy\expired_user_contexts();
$numexpired = $expired->flag_expired();
@@ -109,6 +125,9 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$deleted = $expired->delete();
$this->assertEquals(0, $deleted);
// No user data left in mod_assign.
$this->assertEquals(0, $DB->count_records('assign_submission'));
// The user is deleted.
$deleteduser = \core_user::get_user($user2->id, 'id, deleted', IGNORE_MISSING);
$this->assertEquals(1, $deleteduser->deleted);
@@ -0,0 +1,117 @@
<?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/>.
/**
* Tests for the manager observer.
*
* @package tool_dataprivacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* API tests.
*
* @package tool_dataprivacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_dataprivacy_manager_observer_testcase extends advanced_testcase {
/**
* Helper to set andn return two users who are DPOs.
*/
protected function setup_site_dpos() {
global $DB;
$this->resetAfterTest();
$generator = new testing_data_generator();
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$context = context_system::instance();
// Give the manager role with the capability to manage data requests.
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
// Assign both users as manager.
role_assign($managerroleid, $u1->id, $context->id);
role_assign($managerroleid, $u2->id, $context->id);
// Only map the manager role to the DPO role.
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
return \tool_dataprivacy\api::get_site_dpos();
}
/**
* Ensure that when users are configured as DPO, they are sent an message upon failure.
*/
public function test_handle_component_failure() {
$this->resetAfterTest();
// Create another user who is not a DPO.
$this->getDataGenerator()->create_user();
// Create the DPOs.
$dpos = $this->setup_site_dpos();
$observer = new \tool_dataprivacy\manager_observer();
// Handle the failure, catching messages.
$mailsink = $this->redirectMessages();
$mailsink->clear();
$observer->handle_component_failure(new \Exception('error'), 'foo', 'bar', 'baz', ['foobarbaz', 'bum']);
// Messages should be sent to both DPOs only.
$this->assertEquals(2, $mailsink->count());
$messages = $mailsink->get_messages();
$messageusers = array_map(function($message) {
return $message->useridto;
}, $messages);
$this->assertEquals(array_keys($dpos), $messageusers, '', 0.0, 0, true);
}
/**
* Ensure that when no user is configured as DPO, the message is sent to admin instead.
*/
public function test_handle_component_failure_no_dpo() {
$this->resetAfterTest();
// Create another user who is not a DPO or admin.
$this->getDataGenerator()->create_user();
$observer = new \tool_dataprivacy\manager_observer();
$mailsink = $this->redirectMessages();
$mailsink->clear();
$observer->handle_component_failure(new \Exception('error'), 'foo', 'bar', 'baz', ['foobarbaz', 'bum']);
// Messages should have been sent only to the admin.
$this->assertEquals(1, $mailsink->count());
$messages = $mailsink->get_messages();
$message = reset($messages);
$admin = \core_user::get_user_by_username('admin');
$this->assertEquals($admin->id, $message->useridto);
}
}
@@ -72,8 +72,9 @@ class tool_dataprivacy_metadata_registry_testcase extends advanced_testcase {
}
// Let's check core subsystems.
// The Privacy API adds an extra component in the form of 'core'.
$coresubsystems = \core_component::get_core_subsystems();
$this->assertEquals(count($coresubsystems), count($data['core']));
$this->assertEquals(count($coresubsystems) + 1, count($data['core']));
}
/**
+2 -2
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2018040500;
$plugin->requires = 2018040500; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->version = 2018051401;
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->component = 'tool_dataprivacy';
+2 -2
View File
@@ -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_dbtransfer'; // Full name of the plugin (used for diagnostics).
+2 -2
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017111300;
$plugin->requires = 2017110800;
$plugin->version = 2018051400;
$plugin->requires = 2018050800;
$plugin->component = 'tool_filetypes';
+2 -2
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017111300;
$plugin->requires = 2017110800;
$plugin->version = 2018051400;
$plugin->requires = 2018050800;
$plugin->component = 'tool_generator';
+2 -2
View File
@@ -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_health'; // Full name of the plugin (used for diagnostics)
$plugin->maturity = MATURITY_ALPHA; // this version's maturity level
+2 -2
View File
@@ -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_httpsreplace'; // Full name of the plugin (used for diagnostics).
+2 -2
View File
@@ -25,6 +25,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_innodb'; // Full name of the plugin (used for diagnostics)
+2 -2
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'tool_installaddon';
$plugin->version = 2017111300;
$plugin->requires = 2017110800;
$plugin->version = 2018051400;
$plugin->requires = 2018050800;
$plugin->maturity = MATURITY_STABLE;
+2 -2
View File
@@ -25,6 +25,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_langimport'; // Full name of the plugin (used for diagnostics)
@@ -0,0 +1,148 @@
<?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 helper.
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_log\local\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\request\transform;
/**
* Privacy helper class.
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Returns an event from a standard record.
*
* @see \logstore_standard\log\store::get_log_event()
* @param object $data Log data.
* @return \core\event\base
*/
protected static function restore_event_from_standard_record($data) {
$extra = ['origin' => $data->origin, 'ip' => $data->ip, 'realuserid' => $data->realuserid];
$data = (array) $data;
$id = $data['id'];
$data['other'] = unserialize($data['other']);
if ($data['other'] === false) {
$data['other'] = [];
}
unset($data['origin']);
unset($data['ip']);
unset($data['realuserid']);
unset($data['id']);
if (!$event = \core\event\base::restore($data, $extra)) {
return null;
}
return $event;
}
/**
* Transform a standard log record for a user.
*
* @param object $record The record.
* @param int $userid The user ID.
* @return array
*/
public static function transform_standard_log_record_for_userid($record, $userid) {
// Restore the event to try to get the name, description and other field.
$restoredevent = static::restore_event_from_standard_record($record);
if ($restoredevent) {
$name = $restoredevent->get_name();
$description = $restoredevent->get_description();
$other = $restoredevent->other;
} else {
$name = $record->eventname;
$description = "Unknown event ({$name})";
$other = unserialize($record->other);
}
$realuserid = $record->realuserid;
$isauthor = $record->userid == $userid;
$isrelated = $record->relateduserid == $userid;
$isrealuser = $realuserid == $userid;
$ismasqueraded = $realuserid !== null && $record->userid != $realuserid;
$ismasquerading = $isrealuser && !$isauthor;
$isanonymous = $record->anonymous;
$data = [
'name' => $name,
'description' => $description,
'timecreated' => transform::datetime($record->timecreated),
'ip' => $record->ip,
'origin' => static::transform_origin($record->origin),
'other' => $other ? $other : []
];
if ($isanonymous) {
$data['action_was_done_anonymously'] = transform::yesno($isanonymous);
}
if ($isauthor || !$isanonymous) {
$data['authorid'] = transform::user($record->userid);
$data['author_of_the_action_was_you'] = transform::yesno($isauthor);
}
if ($record->relateduserid) {
$data['relateduserid'] = transform::user($record->relateduserid);
$data['related_user_was_you'] = transform::yesno($isrelated);
}
if ($ismasqueraded) {
$data['author_of_the_action_was_masqueraded'] = transform::yesno(true);
if ($ismasquerading || !$isanonymous) {
$data['masqueradinguserid'] = transform::user($realuserid);
$data['masquerading_user_was_you'] = transform::yesno($ismasquerading);
}
}
return $data;
}
/**
* Transform origin.
*
* @param string $origin The page request origin.
* @return string
*/
public static function transform_origin($origin) {
switch ($origin) {
case 'cli':
case 'restore':
case 'web':
case 'ws':
return get_string('privacy:request:origin:' . $origin, 'tool_log');
break;
}
return $origin;
}
}
@@ -0,0 +1,78 @@
<?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/>.
/**
* Logstore provider interface.
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_log\local\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
/**
* Logstore provider interface.
*
* Logstore subplugins providers must implement this interface.
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface logstore_provider extends \core_privacy\local\request\plugin\subplugin_provider {
/**
* Add contexts that contain user information for the specified user.
*
* @param contextlist $contextlist The contextlist to add the contexts to.
* @param int $userid The user to find the contexts for.
* @return void
*/
public static function add_contexts_for_userid(contextlist $contextlist, $userid);
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
* @return void
*/
public static function export_user_data(approved_contextlist $contextlist);
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
* @return void
*/
public static function delete_data_for_all_users_in_context(context $context);
/**
* 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.
* @return void
*/
public static function delete_data_for_user(approved_contextlist $contextlist);
}
@@ -0,0 +1,123 @@
<?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/>.
/**
* Moodle database: export and delete.
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_log\local\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\writer;
/**
* Moodle database: export and delete trait.
*
* This is to be used with logstores which use a database and table with the same columns
* as the core plugin 'logstore_standard'.
*
* This trait expects the following methods to be present in the object:
*
* - public static function get_database_and_table(): [moodle_database|null, string|null]
* - public static function get_export_subcontext(): []
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait moodle_database_export_and_delete {
/**
* 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) {
list($db, $table) = static::get_database_and_table();
if (!$db || !$table) {
return;
}
$userid = $contextlist->get_user()->id;
list($insql, $inparams) = $db->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$sql = "(userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3) AND contextid $insql";
$params = array_merge($inparams, [
'userid1' => $userid,
'userid2' => $userid,
'userid3' => $userid,
]);
$path = static::get_export_subcontext();
$flush = function($lastcontextid, $data) use ($path) {
$context = context::instance_by_id($lastcontextid);
writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
};
$lastcontextid = null;
$data = [];
$recordset = $db->get_recordset_select($table, $sql, $params, 'contextid, timecreated, id');
foreach ($recordset as $record) {
if ($lastcontextid && $lastcontextid != $record->contextid) {
$flush($lastcontextid, $data);
$data = [];
}
$data[] = helper::transform_standard_log_record_for_userid($record, $userid);
$lastcontextid = $record->contextid;
}
if ($lastcontextid) {
$flush($lastcontextid, $data);
}
$recordset->close();
}
/**
* 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) {
list($db, $table) = static::get_database_and_table();
if (!$db || !$table) {
return;
}
$db->delete_records($table, ['contextid' => $context->id]);
}
/**
* 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) {
list($db, $table) = static::get_database_and_table();
if (!$db || !$table) {
return;
}
list($insql, $inparams) = $db->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$params = array_merge($inparams, ['userid' => $contextlist->get_user()->id]);
$db->delete_records_select($table, "userid = :userid AND contextid $insql", $params);
}
}
+110
View File
@@ -0,0 +1,110 @@
<?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/>.
/**
* Data provider.
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_log\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use tool_log\log\manager;
/**
* Data provider class.
*
* @package tool_log
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @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 {
/**
* Returns metadata.
*
* @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 {
$collection->add_plugintype_link('logstore', [], 'privacy:metadata:logstore');
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) : \core_privacy\local\request\contextlist {
$contextlist = new \core_privacy\local\request\contextlist();
static::call_subplugins_method_with_args('add_contexts_for_userid', [$contextlist, $userid]);
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) {
static::call_subplugins_method_with_args('export_user_data', [$contextlist]);
}
/**
* 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) {
static::call_subplugins_method_with_args('delete_data_for_all_users_in_context', [$context]);
}
/**
* 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) {
static::call_subplugins_method_with_args('delete_data_for_user', [$contextlist]);
}
/**
* Invoke the subplugins method with arguments.
*
* @param string $method The method name.
* @param array $args The arguments.
* @return void
*/
protected static function call_subplugins_method_with_args($method, array $args = []) {
$interface = \tool_log\local\privacy\logstore_provider::class;
\core_privacy\manager::plugintype_class_callback('logstore', $interface, $method, $args);
}
}
+7 -1
View File
@@ -26,7 +26,13 @@ $string['actlogshdr'] = 'Available log stores';
$string['configlogplugins'] = 'Please enable all required plugins and arrange them in appropriate order.';
$string['logging'] = 'Logging';
$string['managelogging'] = 'Manage log stores';
$string['reportssupported'] = 'Reports supported';
$string['pluginname'] = 'Log store manager';
$string['privacy:metadata:logstore'] = 'The log stores';
$string['privacy:path:logs'] = 'Logs';
$string['privacy:request:origin:cli'] = 'Command line tool';
$string['privacy:request:origin:restore'] = 'Backup being restored';
$string['privacy:request:origin:web'] = 'Standard web request';
$string['privacy:request:origin:ws'] = 'Mobile app or web service';
$string['reportssupported'] = 'Reports supported';
$string['subplugintype_logstore'] = 'Log store';
$string['subplugintype_logstore_plural'] = 'Log stores';
@@ -0,0 +1,114 @@
<?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/>.
/**
* Data provider.
*
* @package logstore_database
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace logstore_database\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
/**
* Data provider class.
*
* @package logstore_database
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\tool_log\local\privacy\logstore_provider {
use \tool_log\local\privacy\moodle_database_export_and_delete;
/**
* Returns metadata.
*
* @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 {
$collection->add_external_location_link('log', [
'eventname' => 'privacy:metadata:log:eventname',
'userid' => 'privacy:metadata:log:userid',
'relateduserid' => 'privacy:metadata:log:relateduserid',
'anonymous' => 'privacy:metadata:log:anonymous',
'other' => 'privacy:metadata:log:other',
'timecreated' => 'privacy:metadata:log:timecreated',
'origin' => 'privacy:metadata:log:origin',
'ip' => 'privacy:metadata:log:ip',
'realuserid' => 'privacy:metadata:log:realuserid',
], 'privacy:metadata:log');
return $collection;
}
/**
* Add contexts that contain user information for the specified user.
*
* @param contextlist $contextlist The contextlist to add the contexts to.
* @param int $userid The user to find the contexts for.
* @return void
*/
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
list($db, $table) = static::get_database_and_table();
if (!$db || !$table) {
return;
}
$sql = 'userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3';
$params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
$contextids = $db->get_fieldset_select($table, 'DISTINCT contextid', $sql, $params);
if (empty($contextids)) {
return;
}
$sql = implode(' UNION ', array_map(function($id) use ($db) {
return 'SELECT ' . $id . $db->sql_null_from_clause();
}, $contextids));
$contextlist->add_from_sql($sql, []);
}
/**
* Get the database object.
*
* @return array Containing moodle_database, string, or null values.
*/
protected static function get_database_and_table() {
$manager = get_log_manager();
$store = new \logstore_database\log\store($manager);
$db = $store->get_extdb();
return $db ? [$db, $store->get_config_value('dbtable')] : [null, null];
}
/**
* Get the path to export the logs to.
*
* @return array
*/
protected static function get_export_subcontext() {
return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')];
}
}
@@ -44,6 +44,16 @@ $string['other'] = 'Other';
$string['participating'] = 'Participating';
$string['pluginname'] = 'External database log';
$string['pluginname_desc'] = 'A log plugin that stores log entries in an external database table.';
$string['privacy:metadata:log'] = 'A collection of past events';
$string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous';
$string['privacy:metadata:log:eventname'] = 'The event name';
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
$string['privacy:metadata:log:origin'] = 'The origin of the event';
$string['privacy:metadata:log:other'] = 'Additional information about the event';
$string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.';
$string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event';
$string['privacy:metadata:log:timecreated'] = 'The time at which the event occurred';
$string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event';
$string['read'] = 'Read';
$string['tablenotfound'] = 'Specified table was not found';
$string['teaching'] = 'Teaching';
@@ -0,0 +1,416 @@
<?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/>.
/**
* Data provider tests.
*
* @package logstore_database
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
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;
use logstore_database\privacy\provider;
require_once(__DIR__ . '/fixtures/event.php');
/**
* Data provider testcase class.
*
* This testcase is almost identical to the logstore_standard testcase, aside from the
* initialisation of the relevant logstore obviously.
*
* @package logstore_database
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class logstore_database_privacy_testcase extends provider_testcase {
public function setUp() {
global $CFG;
$this->resetAfterTest();
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
// Fake the settings, we will abuse the standard plugin table here...
set_config('dbdriver', $CFG->dblibrary . '/' . $CFG->dbtype, 'logstore_database');
set_config('dbhost', $CFG->dbhost, 'logstore_database');
set_config('dbuser', $CFG->dbuser, 'logstore_database');
set_config('dbpass', $CFG->dbpass, 'logstore_database');
set_config('dbname', $CFG->dbname, 'logstore_database');
set_config('dbtable', $CFG->prefix . 'logstore_standard_log', 'logstore_database');
if (!empty($CFG->dboptions['dbpersist'])) {
set_config('dbpersist', 1, 'logstore_database');
} else {
set_config('dbpersist', 0, 'logstore_database');
}
if (!empty($CFG->dboptions['dbsocket'])) {
set_config('dbsocket', $CFG->dboptions['dbsocket'], 'logstore_database');
} else {
set_config('dbsocket', '', 'logstore_database');
}
if (!empty($CFG->dboptions['dbport'])) {
set_config('dbport', $CFG->dboptions['dbport'], 'logstore_database');
} else {
set_config('dbport', '', 'logstore_database');
}
if (!empty($CFG->dboptions['dbschema'])) {
set_config('dbschema', $CFG->dboptions['dbschema'], 'logstore_database');
} else {
set_config('dbschema', '', 'logstore_database');
}
if (!empty($CFG->dboptions['dbcollation'])) {
set_config('dbcollation', $CFG->dboptions['dbcollation'], 'logstore_database');
} else {
set_config('dbcollation', '', 'logstore_database');
}
if (!empty($CFG->dboptions['dbhandlesoptions'])) {
set_config('dbhandlesoptions', $CFG->dboptions['dbhandlesoptions'], 'logstore_database');
} else {
set_config('dbhandlesoptions', false, 'logstore_database');
}
}
public function test_get_contexts_for_userid() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$c2 = $this->getDataGenerator()->create_course();
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$cm1ctx = context_module::instance($cm1->cmid);
$cm2ctx = context_module::instance($cm2->cmid);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
$e = \logstore_database\event\unittest_executed::create(['context' => $cm1ctx]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
// User 2 is the related user.
$this->setUser(0);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
$e = \logstore_database\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]);
// Admin user is the real user.
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
$this->setAdminUser();
\core\session\manager::loginas($u3->id, $sysctx);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
// By admin user masquerading u1 related to u3.
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
$this->setAdminUser();
\core\session\manager::loginas($u1->id, context_system::instance());
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
}
public function test_delete_data_for_user() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// Confirm data present.
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
// Delete all the things!
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_database', [$c1ctx->id]));
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
public function test_delete_data_for_all_users_in_context() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// Confirm data present.
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
// Delete all the things!
provider::delete_data_for_all_users_in_context($c1ctx);
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
public function test_export_data_for_user() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$u4 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$c2 = $this->getDataGenerator()->create_course();
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$cm1ctx = context_module::instance($cm1->cmid);
$cm2ctx = context_module::instance($cm2->cmid);
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')];
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]);
$e->trigger();
// User 2 is related.
$this->setUser(0);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id,
'other' => ['i' => 1]]);
$e->trigger();
// Admin user masquerades u3, which is related to u4.
$this->setAdminUser();
\core\session\manager::loginas($u3->id, $sysctx);
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id,
'other' => ['i' => 2]]);
$e->trigger();
// Confirm data present for u1.
provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
$this->assertSame(0, $data->logs[0]['other']['i']);
// Confirm data present for u2.
writer::reset();
provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
$this->assertSame(1, $data->logs[0]['other']['i']);
// Confirm data present for u3.
writer::reset();
provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
$this->assertSame(2, $data->logs[0]['other']['i']);
// Confirm data present for u4.
writer::reset();
provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
$this->assertSame(2, $data->logs[0]['other']['i']);
// Add anonymous events.
$this->setUser($u1);
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id,
'anonymous' => true]);
$e->trigger();
$this->setAdminUser();
\core\session\manager::loginas($u3->id, $sysctx);
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id,
'anonymous' => true]);
$e->trigger();
// Confirm data present for u1.
provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
// Confirm data present for u2.
writer::reset();
provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
// Confirm data present for u3.
writer::reset();
provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
// Confirm data present for u4.
writer::reset();
provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
}
/**
* Assert the content of a context list.
*
* @param contextlist $contextlist The collection.
* @param array $expected List of expected contexts or IDs.
* @return void
*/
protected function assert_contextlist_equals($contextlist, array $expected) {
$expectedids = array_map(function($context) {
if (is_object($context)) {
return $context->id;
}
return $context;
}, $expected);
$contextids = array_map('intval', $contextlist->get_contextids());
sort($contextids);
sort($expectedids);
$this->assertEquals($expectedids, $contextids);
}
/**
* Enable logging.
*
* @return void
*/
protected function enable_logging() {
set_config('enabled_stores', 'logstore_database', 'tool_log');
set_config('buffersize', 0, 'logstore_database');
set_config('logguests', 1, 'logstore_database');
get_log_manager(true);
}
/**
* Get the contextlist for a user.
*
* @param object $user The user.
* @return contextlist
*/
protected function get_contextlist_for_user($user) {
$contextlist = new contextlist();
provider::add_contexts_for_userid($contextlist, $user->id);
return $contextlist;
}
}
+2 -2
View File
@@ -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 = 'logstore_database'; // Full name of the plugin (used for diagnostics).
@@ -0,0 +1,220 @@
<?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/>.
/**
* Data provider.
*
* @package logstore_legacy
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace logstore_legacy\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use tool_log\local\privacy\helper;
/**
* Data provider class.
*
* @package logstore_legacy
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\tool_log\local\privacy\logstore_provider {
/**
* Returns metadata.
*
* @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 {
$collection->add_external_location_link('log', [
'time' => 'privacy:metadata:log:time',
'userid' => 'privacy:metadata:log:userid',
'ip' => 'privacy:metadata:log:ip',
'action' => 'privacy:metadata:log:action',
'url' => 'privacy:metadata:log:url',
'info' => 'privacy:metadata:log:info',
], 'privacy:metadata:log');
return $collection;
}
/**
* Add contexts that contain user information for the specified user.
*
* @param contextlist $contextlist The contextlist to add the contexts to.
* @param int $userid The user to find the contexts for.
* @return void
*/
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
$sql = "
SELECT ctx.id
FROM {context} ctx
JOIN {log} l
ON (l.cmid = 0 AND l.course = ctx.instanceid AND ctx.contextlevel = :courselevel)
OR (l.cmid > 0 AND l.cmid = ctx.instanceid AND ctx.contextlevel = :modulelevel)
OR (l.course <= 0 AND ctx.id = :syscontextid)
WHERE l.userid = :userid";
$params = [
'courselevel' => CONTEXT_COURSE,
'modulelevel' => CONTEXT_MODULE,
'syscontextid' => SYSCONTEXTID,
'userid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
}
/**
* 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;
$userid = $contextlist->get_user()->id;
list($insql, $inparams) = static::get_sql_where_from_contexts($contextlist->get_contexts());
if (empty($insql)) {
return;
}
$sql = "userid = :userid AND $insql";
$params = array_merge($inparams, ['userid' => $userid]);
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')];
$flush = function($lastcontextid, $data) use ($path) {
$context = context::instance_by_id($lastcontextid);
writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
};
$lastcontextid = null;
$data = [];
$recordset = $DB->get_recordset_select('log', $sql, $params, 'course, cmid, time, id');
foreach ($recordset as $record) {
$event = \logstore_legacy\event\legacy_logged::restore_legacy($record);
$context = $event->get_context();
if ($lastcontextid && $lastcontextid != $context->id) {
$flush($lastcontextid, $data);
$data = [];
}
$extra = $event->get_logextra();
$data[] = [
'name' => $event->get_name(),
'description' => $event->get_description(),
'timecreated' => transform::datetime($event->timecreated),
'ip' => $extra['ip'],
'origin' => helper::transform_origin($extra['origin']),
];
$lastcontextid = $context->id;
}
if ($lastcontextid) {
$flush($lastcontextid, $data);
}
$recordset->close();
}
/**
* 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;
list($sql, $params) = static::get_sql_where_from_contexts([$context]);
if (empty($sql)) {
return;
}
$DB->delete_records_select('log', $sql, $params);
}
/**
* 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;
list($sql, $params) = static::get_sql_where_from_contexts($contextlist->get_contexts());
if (empty($sql)) {
return;
}
$userid = $contextlist->get_user()->id;
$DB->delete_records_select('log', "$sql AND userid = :userid", array_merge($params, ['userid' => $userid]));
}
/**
* Get an SQL where statement from a list of contexts.
*
* @param array $contexts The contexts.
* @return array [$sql, $params]
*/
protected static function get_sql_where_from_contexts(array $contexts) {
global $DB;
$sorted = array_reduce($contexts, function ($carry, $context) {
$level = $context->contextlevel;
if ($level == CONTEXT_MODULE || $level == CONTEXT_COURSE) {
$carry[$level][] = $context->instanceid;
} else if ($level == CONTEXT_SYSTEM) {
$carry[$level] = $context->id;
}
return $carry;
}, [
CONTEXT_COURSE => [],
CONTEXT_MODULE => [],
CONTEXT_SYSTEM => null,
]);
$sqls = [];
$params = [];
if (!empty($sorted[CONTEXT_MODULE])) {
list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_MODULE], SQL_PARAMS_NAMED);
$sqls[] = "cmid $insql";
$params = array_merge($params, $inparams);
}
if (!empty($sorted[CONTEXT_COURSE])) {
list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_COURSE], SQL_PARAMS_NAMED);
$sqls[] = "cmid = 0 AND course $insql";
$params = array_merge($params, $inparams);
}
if (!empty($sorted[CONTEXT_SYSTEM])) {
$sqls[] = "course <= 0";
}
if (empty($sqls)) {
return [null, null];
}
return ['((' . implode(') OR (', $sqls) . '))', $params];
}
}
@@ -27,4 +27,11 @@ $string['loglegacy'] = 'Log legacy data';
$string['loglegacy_help'] = 'This plugin records log data to the legacy log table (mdl_log). This functionality has been replaced by newer, richer and more efficient logging plugins, so you should only run this plugin if you have old custom reports that directly query the old log table. Writing to the legacy logs will increase load, so it is recommended that you disable this plugin for performance reasons when it is not needed.';
$string['pluginname'] = 'Legacy log';
$string['pluginname_desc'] = 'A log plugin that stores log entries in the legacy log table.';
$string['privacy:metadata:log'] = 'A collection of past events';
$string['privacy:metadata:log:action'] = 'A description of the action';
$string['privacy:metadata:log:info'] = 'Additional information';
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
$string['privacy:metadata:log:time'] = 'The date at wich the action took place';
$string['privacy:metadata:log:url'] = 'The URL related to the event';
$string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action';
$string['taskcleanup'] = 'Legacy log table cleanup';
@@ -0,0 +1,344 @@
<?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/>.
/**
* Data provider tests.
*
* @package logstore_legacy
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
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;
use logstore_legacy\privacy\provider;
use logstore_legacy\event\unittest_executed;
require_once(__DIR__ . '/fixtures/event.php');
/**
* Data provider testcase class.
*
* @package logstore_legacy
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class logstore_legacy_privacy_testcase extends provider_testcase {
public function setUp() {
$this->resetAfterTest();
}
public function test_get_contexts_for_userid() {
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$cm1ctx = context_module::instance($cm1->cmid);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
// User 2 is the author.
$this->setUser($u2);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm1ctx]);
// User 3 is the author.
$this->setUser($u3);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx]);
}
public function test_delete_data_for_user() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$cm1ctx = context_module::instance($cm1->cmid);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
$e->trigger();
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
$e->trigger();
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
$e->trigger();
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
$e->trigger();
// Assert what we have.
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
// Delete other context.
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c2ctx->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
// Delete system.
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
// Delete course.
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id]));
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
// Delete course.
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id]));
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
}
public function test_delete_data_for_all_users_in_context() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$cm1ctx = context_module::instance($cm1->cmid);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
$e->trigger();
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
$e->trigger();
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
$e->trigger();
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
$e->trigger();
// Assert what we have.
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
// Delete other context.
provider::delete_data_for_all_users_in_context($c2ctx);
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
// Delete system.
provider::delete_data_for_all_users_in_context($sysctx);
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u2->id]));
// Delete course.
provider::delete_data_for_all_users_in_context($c1ctx);
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('log', ['userid' => $u2->id]));
// Delete course.
provider::delete_data_for_all_users_in_context($cm1ctx);
$this->assertFalse($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id]));
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u2->id]));
}
public function test_export_data_for_user() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$cm1ctx = context_module::instance($cm1->cmid);
$this->enable_logging();
$manager = get_log_manager(true);
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')];
// User 1 is the author.
$this->setUser($u1);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
$e->trigger();
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
$e->trigger();
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
$e->trigger();
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
$e->trigger();
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
$e->trigger();
// Test export.
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id]));
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($cm1ctx)->get_data($path);
$this->assertCount(2, $data->logs);
writer::reset();
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id]));
$data = writer::with_context($cm1ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
writer::reset();
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id]));
$data = writer::with_context($sysctx)->get_data($path);
$this->assertCount(1, $data->logs);
}
/**
* Assert the content of a context list.
*
* @param contextlist $contextlist The collection.
* @param array $expected List of expected contexts or IDs.
* @return void
*/
protected function assert_contextlist_equals($contextlist, array $expected) {
$expectedids = array_map(function($context) {
if (is_object($context)) {
return $context->id;
}
return $context;
}, $expected);
$contextids = array_map('intval', $contextlist->get_contextids());
sort($contextids);
sort($expectedids);
$this->assertEquals($expectedids, $contextids);
}
/**
* Enable logging.
*
* @return void
*/
protected function enable_logging() {
set_config('enabled_stores', 'logstore_legacy', 'tool_log');
set_config('loglegacy', 1, 'logstore_legacy');
get_log_manager(true);
}
/**
* Get the contextlist for a user.
*
* @param object $user The user.
* @return contextlist
*/
protected function get_contextlist_for_user($user) {
$contextlist = new contextlist();
provider::add_contexts_for_userid($contextlist, $user->id);
return $contextlist;
}
}
+2 -2
View File
@@ -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 = 'logstore_legacy'; // Full name of the plugin (used for diagnostics).
@@ -0,0 +1,107 @@
<?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/>.
/**
* Data provider.
*
* @package logstore_standard
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace logstore_standard\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
/**
* Data provider class.
*
* @package logstore_standard
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\tool_log\local\privacy\logstore_provider {
use \tool_log\local\privacy\moodle_database_export_and_delete;
/**
* Returns metadata.
*
* @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 {
$collection->add_database_table('logstore_standard_log', [
'eventname' => 'privacy:metadata:log:eventname',
'userid' => 'privacy:metadata:log:userid',
'relateduserid' => 'privacy:metadata:log:relateduserid',
'anonymous' => 'privacy:metadata:log:anonymous',
'other' => 'privacy:metadata:log:other',
'timecreated' => 'privacy:metadata:log:timecreated',
'origin' => 'privacy:metadata:log:origin',
'ip' => 'privacy:metadata:log:ip',
'realuserid' => 'privacy:metadata:log:realuserid',
], 'privacy:metadata:log');
return $collection;
}
/**
* Add contexts that contain user information for the specified user.
*
* @param contextlist $contextlist The contextlist to add the contexts to.
* @param int $userid The user to find the contexts for.
* @return void
*/
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
$sql = "
SELECT l.contextid
FROM {logstore_standard_log} l
WHERE l.userid = :userid1
OR l.relateduserid = :userid2
OR l.realuserid = :userid3";
$contextlist->add_from_sql($sql, [
'userid1' => $userid,
'userid2' => $userid,
'userid3' => $userid,
]);
}
/**
* Get the database object.
*
* @return array Containing moodle_database, string, or null values.
*/
protected static function get_database_and_table() {
global $DB;
return [$DB, 'logstore_standard_log'];
}
/**
* Get the path to export the logs to.
*
* @return array
*/
protected static function get_export_subcontext() {
return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
}
}
@@ -25,4 +25,14 @@
$string['buffersize'] = 'Write buffer size';
$string['pluginname'] = 'Standard log';
$string['pluginname_desc'] = 'A log plugin stores log entries in a Moodle database table.';
$string['privacy:metadata:log'] = 'A collection of past events';
$string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous';
$string['privacy:metadata:log:eventname'] = 'The event name';
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
$string['privacy:metadata:log:origin'] = 'The origin of the event';
$string['privacy:metadata:log:other'] = 'Additional information about the event';
$string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.';
$string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event';
$string['privacy:metadata:log:timecreated'] = 'The time at which the event occurred';
$string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event';
$string['taskcleanup'] = 'Log table cleanup';
@@ -0,0 +1,373 @@
<?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/>.
/**
* Data provider tests.
*
* @package logstore_standard
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
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;
use logstore_standard\privacy\provider;
require_once(__DIR__ . '/fixtures/event.php');
/**
* Data provider testcase class.
*
* @package logstore_standard
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class logstore_standard_privacy_testcase extends provider_testcase {
public function setUp() {
$this->resetAfterTest();
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
}
public function test_get_contexts_for_userid() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$c2 = $this->getDataGenerator()->create_course();
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$cm1ctx = context_module::instance($cm1->cmid);
$cm2ctx = context_module::instance($cm2->cmid);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
$e = \logstore_standard\event\unittest_executed::create(['context' => $cm1ctx]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
// User 2 is the related user.
$this->setUser(0);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
$e = \logstore_standard\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]);
// Admin user is the real user.
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
$this->setAdminUser();
\core\session\manager::loginas($u3->id, $sysctx);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
// By admin user masquerading u1 related to u3.
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
$this->setAdminUser();
\core\session\manager::loginas($u1->id, context_system::instance());
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]);
$e->trigger();
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]);
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
}
public function test_delete_data_for_user() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// Confirm data present.
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
// Delete all the things!
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_standard', [$c1ctx->id]));
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
public function test_delete_data_for_all_users_in_context() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
$e->trigger();
// Confirm data present.
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
// Delete all the things!
provider::delete_data_for_all_users_in_context($c1ctx);
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
public function test_export_data_for_user() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
$u4 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
$c2 = $this->getDataGenerator()->create_course();
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
$sysctx = context_system::instance();
$c1ctx = context_course::instance($c1->id);
$c2ctx = context_course::instance($c2->id);
$cm1ctx = context_module::instance($cm1->cmid);
$cm2ctx = context_module::instance($cm2->cmid);
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]);
$e->trigger();
// User 2 is related.
$this->setUser(0);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id,
'other' => ['i' => 1]]);
$e->trigger();
// Admin user masquerades u3, which is related to u4.
$this->setAdminUser();
\core\session\manager::loginas($u3->id, $sysctx);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id,
'other' => ['i' => 2]]);
$e->trigger();
// Confirm data present for u1.
provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
$this->assertSame(0, $data->logs[0]['other']['i']);
// Confirm data present for u2.
writer::reset();
provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
$this->assertSame(1, $data->logs[0]['other']['i']);
// Confirm data present for u3.
writer::reset();
provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
$this->assertSame(2, $data->logs[0]['other']['i']);
// Confirm data present for u4.
writer::reset();
provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
$this->assertSame(2, $data->logs[0]['other']['i']);
// Add anonymous events.
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id,
'anonymous' => true]);
$e->trigger();
$this->setAdminUser();
\core\session\manager::loginas($u3->id, $sysctx);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id,
'anonymous' => true]);
$e->trigger();
// Confirm data present for u1.
provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
// Confirm data present for u2.
writer::reset();
provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
// Confirm data present for u3.
writer::reset();
provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
// Confirm data present for u4.
writer::reset();
provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id]));
$data = writer::with_context($c2ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
}
/**
* Assert the content of a context list.
*
* @param contextlist $contextlist The collection.
* @param array $expected List of expected contexts or IDs.
* @return void
*/
protected function assert_contextlist_equals($contextlist, array $expected) {
$expectedids = array_map(function($context) {
if (is_object($context)) {
return $context->id;
}
return $context;
}, $expected);
$contextids = array_map('intval', $contextlist->get_contextids());
sort($contextids);
sort($expectedids);
$this->assertEquals($expectedids, $contextids);
}
/**
* Enable logging.
*
* @return void
*/
protected function enable_logging() {
set_config('enabled_stores', 'logstore_standard', 'tool_log');
set_config('buffersize', 0, 'logstore_standard');
set_config('logguests', 1, 'logstore_standard');
}
/**
* Get the contextlist for a user.
*
* @param object $user The user.
* @return contextlist
*/
protected function get_contextlist_for_user($user) {
$contextlist = new contextlist();
provider::add_contexts_for_userid($contextlist, $user->id);
return $contextlist;
}
}
+2 -2
View File
@@ -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 = 'logstore_standard'; // Full name of the plugin (used for diagnostics).
+176
View File
@@ -0,0 +1,176 @@
<?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/>.
/**
* Data provider tests.
*
* @package tool_log
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
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;
use tool_log\privacy\provider;
require_once($CFG->dirroot . '/admin/tool/log/store/standard/tests/fixtures/event.php');
/**
* Data provider testcase class.
*
* We're not testing the full functionality, just that the provider passes the requests
* down to at least one of its subplugin. Each subplugin should have tests to cover the
* different provider methods in depth.
*
* @package tool_log
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_log_privacy_testcase extends provider_testcase {
public function setUp() {
$this->resetAfterTest();
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
}
public function test_get_contexts_for_userid() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c1ctx = context_course::instance($c1->id);
$this->enable_logging();
$manager = get_log_manager(true);
$this->setUser($u1);
$this->assertEmpty(provider::get_contexts_for_userid($u1->id)->get_contextids(), []);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$this->assertEquals($c1ctx->id, provider::get_contexts_for_userid($u1->id)->get_contextids()[0]);
}
public function test_delete_data_for_user() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c1ctx = context_course::instance($c1->id);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
// Confirm data present.
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
// Delete all the things!
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_standard', [$c1ctx->id]));
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
public function test_delete_data_for_all_users_in_context() {
global $DB;
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c1ctx = context_course::instance($c1->id);
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
// User 2 is the author.
$this->setUser($u2);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
$e->trigger();
// Confirm data present.
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
// Delete all the things!
provider::delete_data_for_all_users_in_context($c1ctx);
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
}
public function test_export_data_for_user() {
$admin = \core_user::get_user(2);
$u1 = $this->getDataGenerator()->create_user();
$c1 = $this->getDataGenerator()->create_course();
$c1ctx = context_course::instance($c1->id);
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
$this->enable_logging();
$manager = get_log_manager(true);
// User 1 is the author.
$this->setUser($u1);
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 123]]);
$e->trigger();
// Confirm data present for u1.
provider::export_user_data(new approved_contextlist($u1, 'tool_log', [$c1ctx->id]));
$data = writer::with_context($c1ctx)->get_data($path);
$this->assertCount(1, $data->logs);
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
$this->assertSame(123, $data->logs[0]['other']['i']);
}
/**
* Enable logging.
*
* @return void
*/
protected function enable_logging() {
set_config('enabled_stores', 'logstore_standard', 'tool_log');
set_config('buffersize', 0, 'logstore_standard');
set_config('logguests', 1, 'logstore_standard');
}
}
+2 -2
View File
@@ -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_log'; // Full name of the plugin (used for diagnostics).
File diff suppressed because one or more lines are too long
+9 -1
View File
@@ -130,6 +130,11 @@ define(['jquery',
* @return {Promise}
*/
PlanActions.prototype._callAndRefresh = function(calls, planData) {
// Because this function causes a refresh, we must track the JS completion from start to finish to prevent
// stale reference issues in Behat.
var callKey = 'tool_lp/planactions:_callAndRefresh-' + Math.floor(Math.random() * Math.floor(1000));
M.util.js_pending(callKey);
var self = this;
calls.push({
methodname: self._contextMethod,
@@ -141,7 +146,10 @@ define(['jquery',
.then(function() {
return self._renderView(arguments[arguments.length - 1]);
})
.fail(notification.exception);
.fail(notification.exception)
.always(function() {
return M.util.js_complete(callKey);
});
};
/**
+2 -2
View File
@@ -25,6 +25,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_lp'; // Full name of the plugin (used for diagnostics).
+3 -3
View File
@@ -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_lpimportcsv'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array('tool_lp' => 2017110800);
$plugin->dependencies = array('tool_lp' => 2018050800);
+2 -2
View File
@@ -24,8 +24,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_lpmigrate'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
'tool_lp' => ANY_VERSION
@@ -310,6 +310,16 @@ class manager {
return true;
}
/**
* Remove older verification failures.
*
* @return void
*/
public function tidy_old_verification_failures() {
global $DB;
$DB->delete_records_select('messageinbound_messagelist', 'timecreated < :time', ['time' => time() - DAYSECS]);
}
/**
* Process a message and pass it through the Inbound Message handling systems.
*
@@ -0,0 +1,173 @@
<?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/>.
/**
* Data provider.
*
* @package tool_messageinbound
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_messageinbound\privacy;
defined('MOODLE_INTERNAL') || die();
use context;
use context_user;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
/**
* Data provider class.
*
* @package tool_messageinbound
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @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 metadata.
*
* @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 {
$collection->add_database_table('messageinbound_messagelist', [
'messageid' => 'privacy:metadata:messagelist:messageid',
'userid' => 'privacy:metadata:messagelist:userid',
'address' => 'privacy:metadata:messagelist:address',
'timecreated' => 'privacy:metadata:messagelist:timecreated',
], 'privacy:metadata:messagelist');
// Arguably the keys are handled by \core\message\inbound\address_manager and thus could/should be handled by core.
$collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:coreuserkey');
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) : \core_privacy\local\request\contextlist {
$contextlist = new \core_privacy\local\request\contextlist();
// Always add the user context so we're sure we're not dodging user keys, besides it's not costly to do so.
$contextlist->add_user_context($userid);
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 (!static::approved_contextlist_contains_my_context($contextlist)) {
// We only care about the user's user context.
return;
}
$userid = $contextlist->get_user()->id;
$context = context_user::instance($userid);
$path = [get_string('messageinbound', 'tool_messageinbound')];
// Export user keys.
\core_userkey\privacy\provider::export_userkeys($context, $path, 'messageinbound_handler');
// Export the message list.
$data = [];
$recordset = $DB->get_recordset('messageinbound_messagelist', ['userid' => $userid], 'timecreated, id');
foreach ($recordset as $record) {
$data[] = [
'received_at' => $record->address,
'timecreated' => transform::datetime($record->timecreated),
];
}
$recordset->close();
writer::with_context($context)->export_data($path, (object) ['messages_pending_validation' => $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;
if ($context->contextlevel != CONTEXT_USER) {
return;
}
static::delete_user_data($context->instanceid);
}
/**
* 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 (!static::approved_contextlist_contains_my_context($contextlist)) {
// We only care about the user's user context.
return;
}
static::delete_user_data($contextlist->get_user()->id);
}
/**
* Delete a user's data.
*
* @param int $userid The user ID.
* @return void
*/
protected static function delete_user_data($userid) {
global $DB;
$DB->delete_records_select('messageinbound_messagelist', 'userid = :userid', ['userid' => $userid]);
\core_userkey\privacy\provider::delete_userkeys('messageinbound_handler', $userid);
}
/**
* Return whether the contextlist contains our own context.
*
* @param approved_contextlist $contextlist The contextlist
* @return bool
*/
protected static function approved_contextlist_contains_my_context(approved_contextlist $contextlist) {
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) {
return true;
}
}
return false;
}
}
@@ -49,6 +49,7 @@ class cleanup_task extends \core\task\scheduled_task {
*/
public function execute() {
$manager = new \tool_messageinbound\manager();
return $manager->tidy_old_messages();
$manager->tidy_old_messages();
$manager->tidy_old_verification_failures();
}
}
@@ -24,7 +24,7 @@
$string['classname'] = 'Class name';
$string['component'] = 'Component';
$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, you can use the [server]:[port], for example mail.example.com:587 format. If you leave this field blank, Moodle will use the default port for the type of mail server you specify.';
$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, use [server]:[port], for example mail.example.com:587. If a port isn\'t specified, the default port for the type of mail server will be used.';
$string['defaultexpiration'] = 'Default address expiry period';
$string['defaultexpiration_help'] = 'When an email address is generated by the handler, it can be set to automatically expire after a period of time, so that it can no longer be used. It is advisable to set an expiry period.';
$string['description'] = 'Description';
@@ -45,7 +45,7 @@ $string['invalid_recipient_handler'] = 'If a valid message is received but the s
This handler processes those replies.
It is not possible to disable sender verification of this handler because the user may reply from an incorrect email address if their email client configuration is incorrect.';
$string['invalid_recipient_handler_name'] = 'Invalid recipient handler';
$string['invalid_recipient_handler_name'] = 'Invalid sender handler';
$string['invalidrecipientdescription'] = 'The message "{$a->subject}" could not be authenticated, since it was sent from a different email address than in your user profile. For the message to be authenticated, you need to reply to this message.';
$string['invalidrecipientdescriptionhtml'] = 'The message "{$a->subject}" could not be authenticated, since it was sent from a different email address than in your user profile. For the message to be authenticated, you need to reply to this message.';
$string['invalidrecipientfinal'] = 'The message "{$a->subject}" could not be authenticated. Please check that you are sending your message from the same email address as in your profile.';
@@ -94,6 +94,12 @@ $string['onehour'] = 'One hour';
$string['oneweek'] = 'One week';
$string['oneyear'] = 'One year';
$string['pluginname'] = 'Inbound message configuration';
$string['privacy:metadata:coreuserkey'] = 'User\'s keys to validate the email received';
$string['privacy:metadata:messagelist'] = 'A list of message identifiers which failed validation and requires further authorisation';
$string['privacy:metadata:messagelist:address'] = 'The address at which the email was sent';
$string['privacy:metadata:messagelist:messageid'] = 'The message ID';
$string['privacy:metadata:messagelist:timecreated'] = 'The time at which the record was made';
$string['privacy:metadata:messagelist:userid'] = 'The ID of user who need to approve the message';
$string['replysubjectprefix'] = 'Re:';
$string['requirevalidation'] = 'Validate sender address';
$string['name'] = 'Name';
@@ -0,0 +1,97 @@
<?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/>.
/**
* Manager tests.
*
* @package tool_messageinbound
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use tool_messageinbound\privacy\provider;
/**
* Manager testcase class.
*
* @package tool_messageinbound
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_messageinbound_manager_testcase extends provider_testcase {
public function setUp() {
global $CFG;
$this->resetAfterTest();
// Pretend the system is enabled.
$CFG->messageinbound_enabled = true;
$CFG->messageinbound_mailbox = 'mailbox';
$CFG->messageinbound_domain = 'example.com';
}
public function test_tidy_old_verification_failures() {
global $DB;
$now = time();
$stale = $now - DAYSECS - 1; // Make a second older because PHP Unit is too damn fast!!
$this->create_messagelist(['timecreated' => $now]);
$this->create_messagelist(['timecreated' => $now - HOURSECS]);
$this->create_messagelist(['timecreated' => $stale]);
$this->create_messagelist(['timecreated' => $stale - HOURSECS]);
$this->create_messagelist(['timecreated' => $stale - YEARSECS]);
$this->assertEquals(5, $DB->count_records('messageinbound_messagelist', []));
$this->assertEquals(3, $DB->count_records_select('messageinbound_messagelist', 'timecreated < :t', ['t' => $stale + 1]));
$manager = new \tool_messageinbound\manager();
$manager->tidy_old_verification_failures();
$this->assertEquals(2, $DB->count_records('messageinbound_messagelist', []));
$this->assertEquals(0, $DB->count_records_select('messageinbound_messagelist', 'timecreated < :t', ['t' => $stale + 1]));
}
/**
* Create a message to validate.
*
* @param array $params The params.
* @return stdClass
*/
protected function create_messagelist(array $params) {
global $DB, $USER;
$record = (object) array_merge([
'messageid' => 'abc',
'userid' => $USER->id,
'address' => 'text@example.com',
'timecreated' => time(),
], $params);
$record->id = $DB->insert_record('messageinbound_messagelist', $record);
return $record;
}
}
@@ -0,0 +1,201 @@
<?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/>.
/**
* Data provider tests.
*
* @package tool_messageinbound
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use tool_messageinbound\privacy\provider;
/**
* Data provider testcase class.
*
* @package tool_messageinbound
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_messageinbound_privacy_testcase extends provider_testcase {
public function setUp() {
global $CFG;
$this->resetAfterTest();
// Pretend the system is enabled.
$CFG->messageinbound_enabled = true;
$CFG->messageinbound_mailbox = 'mailbox';
$CFG->messageinbound_domain = 'example.com';
}
public function test_get_contexts_for_userid() {
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u1ctx = context_user::instance($u1->id);
$u2ctx = context_user::instance($u2->id);
$contexts = provider::get_contexts_for_userid($u1->id)->get_contexts();
$this->assertCount(1, $contexts);
$this->assertEquals($u1ctx->id, $contexts[0]->id);
$contexts = provider::get_contexts_for_userid($u2->id)->get_contexts();
$this->assertCount(1, $contexts);
$this->assertEquals($u2ctx->id, $contexts[0]->id);
}
public function test_delete_data_for_user() {
global $DB;
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u1ctx = context_user::instance($u1->id);
$u2ctx = context_user::instance($u2->id);
$addressmanager = new \core\message\inbound\address_manager();
$addressmanager->set_handler('\tool_messageinbound\message\inbound\invalid_recipient_handler');
$addressmanager->set_data(123);
// Create a user key for both users.
$addressmanager->generate($u1->id);
$addressmanager->generate($u2->id);
// Create a messagelist for both users.
$this->create_messagelist(['userid' => $u1->id]);
$this->create_messagelist(['userid' => $u2->id]);
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
// Passing another user's context does not do anything.
provider::delete_data_for_user(new approved_contextlist($u1, 'tool_messageinbound', [$u2ctx->id]));
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
// Deleting user 1.
provider::delete_data_for_user(new approved_contextlist($u1, 'tool_messageinbound', [$u1ctx->id]));
$this->assertFalse($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
$this->assertFalse($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
}
public function test_delete_data_for_all_users_in_context() {
global $DB;
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u1ctx = context_user::instance($u1->id);
$u2ctx = context_user::instance($u2->id);
$addressmanager = new \core\message\inbound\address_manager();
$addressmanager->set_handler('\tool_messageinbound\message\inbound\invalid_recipient_handler');
$addressmanager->set_data(123);
// Create a user key for both users.
$addressmanager->generate($u1->id);
$addressmanager->generate($u2->id);
// Create a messagelist for both users.
$this->create_messagelist(['userid' => $u1->id]);
$this->create_messagelist(['userid' => $u2->id]);
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
// Deleting user 1.
provider::delete_data_for_all_users_in_context($u1ctx);
$this->assertFalse($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
$this->assertFalse($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
}
public function test_export_data_for_user() {
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u1ctx = context_user::instance($u1->id);
$u2ctx = context_user::instance($u2->id);
$addressmanager = new \core\message\inbound\address_manager();
$addressmanager->set_handler('\tool_messageinbound\message\inbound\invalid_recipient_handler');
$addressmanager->set_data(123);
// Create a user key for both users.
$addressmanager->generate($u1->id);
$addressmanager->generate($u2->id);
// Create a messagelist for both users.
$this->create_messagelist(['userid' => $u1->id, 'address' => 'u1@example1.com']);
$this->create_messagelist(['userid' => $u1->id, 'address' => 'u1@example2.com']);
$this->create_messagelist(['userid' => $u2->id, 'address' => 'u2@example1.com']);
// Export for user.
$this->setUser($u1);
provider::export_user_data(new approved_contextlist($u1, 'tool_messageinbound', [$u1ctx->id, $u2ctx->id]));
$data = writer::with_context($u2ctx)->get_data([get_string('messageinbound', 'tool_messageinbound')]);
$this->assertEmpty($data);
$data = writer::with_context($u1ctx)->get_data([get_string('messageinbound', 'tool_messageinbound')]);
$this->assertCount(2, $data->messages_pending_validation);
$this->assertEquals('u1@example1.com', $data->messages_pending_validation[0]['received_at']);
$this->assertEquals('u1@example2.com', $data->messages_pending_validation[1]['received_at']);
$data = writer::with_context($u2ctx)->get_related_data([get_string('messageinbound', 'tool_messageinbound')], 'userkeys');
$this->assertEmpty($data);
$data = writer::with_context($u1ctx)->get_related_data([get_string('messageinbound', 'tool_messageinbound')], 'userkeys');
$this->assertCount(1, $data->keys);
$this->assertEquals('messageinbound_handler', $data->keys[0]->script);
}
/**
* Create a message to validate.
*
* @param array $params The params.
* @return stdClass
*/
protected function create_messagelist(array $params) {
global $DB, $USER;
$record = (object) array_merge([
'messageid' => 'abc',
'userid' => $USER->id,
'address' => 'text@example.com',
'timecreated' => time(),
], $params);
$record->id = $DB->insert_record('messageinbound_messagelist', $record);
return $record;
}
}
+2 -2
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017111300;
$plugin->requires = 2017110800;
$plugin->version = 2018051400;
$plugin->requires = 2018050800;
$plugin->component = 'tool_messageinbound';
+1
View File
@@ -277,6 +277,7 @@ class api {
$settings->tool_mobile_customlangstrings = get_config('tool_mobile', 'customlangstrings');
$settings->tool_mobile_disabledfeatures = get_config('tool_mobile', 'disabledfeatures');
$settings->tool_mobile_custommenuitems = get_config('tool_mobile', 'custommenuitems');
$settings->tool_mobile_apppolicy = get_config('tool_mobile', 'apppolicy');
}
return $settings;
@@ -0,0 +1,142 @@
<?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_mobile.
*
* @package tool_mobile
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_mobile\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\request\writer;
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;
/**
* Privacy provider for tool_mobile.
*
* @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\user_preference_provider,
\core_privacy\local\request\plugin\provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised item collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection) : collection {
// There is a one user preference.
$collection->add_user_preference('tool_mobile_autologin_request_last',
'privacy:metadata:preference:tool_mobile_autologin_request_last');
$collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:core_userkey');
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 {
$sql = "SELECT ctx.id
FROM {user_private_key} k
JOIN {user} u ON k.userid = u.id
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
WHERE k.userid = :userid AND k.script = 'tool_mobile'";
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
$contextlist = new contextlist();
$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) {
// If the user has data, then only the CONTEXT_USER 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;
}
// Export associated userkeys.
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile');
}
/**
* 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) {
$autologinrequestlast = get_user_preferences('tool_mobile_autologin_request_last', null, $userid);
if ($autologinrequestlast !== null) {
$time = transform::datetime($autologinrequestlast);
writer::export_user_preference('tool_mobile',
'tool_mobile_autologin_request_last',
$time,
get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile')
);
}
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
// Sanity check that context is at the user context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
return;
}
$userid = $context->instanceid;
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $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) {
// If the user has 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 all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
}
}
+5 -1
View File
@@ -25,6 +25,8 @@
$string['adodbdebugwarning'] = 'ADOdb debugging is enabled. It should be disabled in the external database authentication or external database enrolment plugin settings.';
$string['androidappid'] = 'Android app\'s unique identifier';
$string['androidappid_desc'] = 'This setting may be left as default unless you have a custom Android app.';
$string['apppolicy'] = 'App policy URL';
$string['apppolicy_help'] = 'The URL of a policy for app users which is listed on the About page in the app. If the field is left empty, the site policy URL will be used instead.';
$string['autologinkeygenerationlockout'] = 'Auto-login key generation is blocked. You need to wait 6 minutes between requests.';
$string['autologinnotallowedtoadmins'] = 'Auto-login is not allowed for site admins.';
$string['cachedef_plugininfo'] = 'This stores the list of plugins with mobile addons';
@@ -65,7 +67,7 @@ $string['invalidcertificatechainwarning'] = 'It seems that the certificate chain
$string['invalidcertificateexpiredatewarning'] = 'It seems that the HTTPS certificate for the site has expired.';
$string['invalidcertificatestartdatewarning'] = 'It seems that the HTTPS certificate for the site is not yet valid (with a start date in the future).';
$string['invalidprivatetoken'] = 'Invalid private token. Token should not be empty or passed via GET parameter.';
$string['invaliduserquotawarning'] = 'The user quota (userquota) is set to an invalid number. It should be set to a valid number (an integer value) in Site policies.';
$string['invaliduserquotawarning'] = 'The user quota (userquota) is set to an invalid number. It should be set to a valid number (an integer value) in Site security settings.';
$string['iosappid'] = 'iOS app\'s unique identifier';
$string['iosappid_desc'] = 'This setting may be left as default unless you have a custom iOS app.';
$string['loginintheapp'] = 'Via the app';
@@ -92,3 +94,5 @@ $string['smartappbanners'] = 'App Banners';
$string['typeoflogin'] = 'Type of login';
$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
$string['getmoodleonyourmobile'] = 'Get the mobile app';
$string['privacy:metadata:preference:tool_mobile_autologin_request_last'] = 'The date of the last auto-login key request. Between each request 6 minutes are required.';
$string['privacy:metadata:core_userkey'] = 'User\'s keys used to create auto-login key for the current user.';
+3
View File
@@ -43,6 +43,9 @@ if ($hassiteconfig) {
new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default));
}
$temp->add(new admin_setting_configtext('tool_mobile/apppolicy', new lang_string('apppolicy', 'tool_mobile'),
new lang_string('apppolicy_help', 'tool_mobile'), '', PARAM_URL));
$ADMIN->add('mobileapp', $temp);
// Show only mobile settings if the mobile service is enabled.
@@ -173,6 +173,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
array('name' => 'tool_mobile_customlangstrings', 'value' => ''),
array('name' => 'tool_mobile_disabledfeatures', 'value' => ''),
array('name' => 'tool_mobile_custommenuitems', 'value' => ''),
array('name' => 'tool_mobile_apppolicy', 'value' => ''),
);
$this->assertCount(0, $result['warnings']);
$this->assertEquals($expected, $result['settings']);
@@ -0,0 +1,132 @@
<?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/>.
/**
* Base class for unit tests for tool_mobile.
*
* @package tool_mobile
* @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_privacy\local\request\writer;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\approved_contextlist;
use \tool_mobile\privacy\provider;
/**
* Unit tests for the tool_mobile implementation of the privacy API.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_mobile_privacy_testcase extends \core_privacy\tests\provider_testcase {
/**
* Basic setup for these tests.
*/
public function setUp() {
$this->resetAfterTest(true);
}
/**
* Test to check export_user_preferences.
* returns user preferences data.
*/
public function test_export_user_preferences() {
$user = $this->getDataGenerator()->create_user();
$expectedtime = time();
set_user_preference('tool_mobile_autologin_request_last', time(), $user);
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$prefs = $writer->get_user_preferences('tool_mobile');
$time = transform::datetime($expectedtime);
$this->assertEquals($time, $prefs->tool_mobile_autologin_request_last->value);
$this->assertEquals(get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile'),
$prefs->tool_mobile_autologin_request_last->description);
}
/**
* Test getting the context for the user ID related to this plugin.
*/
public function test_get_contexts_for_userid() {
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$key = get_user_key('tool_mobile', $user->id);
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertEquals($context->id, $contextlist->current()->id);
}
/**
* Test that data is exported correctly for this plugin.
*/
public function test_export_user_data() {
global $DB;
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$keyvalue = get_user_key('tool_mobile', $user->id);
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
// Validate exported data.
$this->setUser($user);
$writer = writer::with_context($context);
$this->assertFalse($writer->has_any_data());
$this->export_context_data_for_user($user->id, $context, 'tool_mobile');
$userkeydata = $writer->get_related_data([], 'userkeys');
$this->assertCount(1, $userkeydata->keys);
$this->assertEquals($key->script, reset($userkeydata->keys)->script);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context() {
global $DB;
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$keyvalue = get_user_key('tool_mobile', $user->id);
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
// Before deletion, we should have 1 user_private_key.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(1, $count);
// Delete data.
provider::delete_data_for_all_users_in_context($context);
// After deletion, the user_private_key entries should have been deleted.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(0, $count);
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user() {
global $DB;
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$keyvalue = get_user_key('tool_mobile', $user->id);
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
// Before deletion, we should have 1 user_private_key.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(1, $count);
// Delete data.
$contextlist = provider::get_contexts_for_userid($user->id);
$approvedcontextlist = new approved_contextlist($user, 'tool_mobile', $contextlist->get_contextids());
provider::delete_data_for_user($approvedcontextlist);
// After deletion, the user_private_key entries should have been deleted.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(0, $count);
}
}
+3 -3
View File
@@ -23,9 +23,9 @@
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017111301; // 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_mobile'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = array(
'webservice_rest' => 2017110800
'webservice_rest' => 2018050800
);

Some files were not shown because too many files have changed in this diff Show More