Compare commits

...

289 Commits

Author SHA1 Message Date
Jun Pataleta ae82333cf2 weekly release 3.3.9+ 2019-02-01 08:57:00 +08:00
Jun Pataleta 0c5e1ca0d2 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2019-02-01 08:56:58 +08:00
Eloy Lafuente (stronk7) 1ef6a4c080 Merge branch 'MDL-64737-33-attoprivacysql' of git://github.com/mudrd8mz/moodle into MOODLE_33_STABLE 2019-01-31 22:48:43 +01:00
David Mudrák c2b73b54eb MDL-64737 editor_atto: Fix the privacy export_user_data() implementation
Multiple issues fixed:

* Fixed order of the statements to avoid access to undefined variables.
* Fixed population of the SQL query parameter to make sure they match
  the placeholder in the query.
* Fixed missing table aliases in the second query to make sure the query
  actually works as expected.
2019-01-31 19:24:26 +01:00
David Mudrák 1f47c7fc7b MDL-64737 privacy: Fix privacy provider testsuite file names
Only files ending with "_test.php" suffix are implicit part of a
testsuite. So these two were not actually executed.
2019-01-31 19:24:26 +01:00
AMOS bot 4da970984c Automatically generated installer lang files 2019-01-30 00:10:40 +00:00
AMOS bot d3b93323c4 Automatically generated installer lang files 2019-01-22 00:10:22 +00:00
AMOS bot b68e858798 Automatically generated installer lang files 2019-01-10 00:09:05 +00:00
AMOS bot d02dc6a763 Automatically generated installer lang files 2018-12-19 00:09:35 +00:00
AMOS bot 12cd92baa6 Automatically generated installer lang files 2018-12-07 00:09:55 +00:00
AMOS bot d99ea30311 Automatically generated installer lang files 2018-11-30 00:08:02 +00:00
AMOS bot 472746d97e Automatically generated installer lang files 2018-11-29 00:08:48 +00:00
David Monllao d23d50025e weekly release 3.3.9+ 2018-11-28 11:23:24 +01:00
David Monllao 43f7bb1815 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-11-28 11:23:22 +01:00
David Mudrák c041f919f0 MDL-64205 privacy: Delete orphaned data request records on upgrade
As a result of the bug, some requests can be orphaned - the linked user
record may not exist any more. This is a cleanup of such records.
2018-11-27 09:52:11 +01:00
David Mudrák 32b1faf8b7 MDL-64205 users: Do not delete the actual records of unconfirmed users
There is an automatic data privacy request created to get rid of all the
personal data upon the user deletion. With the actual user record
absent, the data privacy requests page throws an error. Let's do here
same as we do in `delete_incomplete_users_task` and let us not delete
the actual user record.
2018-11-27 09:49:42 +01:00
AMOS bot d2a2855501 Automatically generated installer lang files 2018-11-23 00:07:49 +00:00
David Monllao f66db7a52b weekly release 3.3.9+ 2018-11-20 15:12:07 +01:00
David Monllao a6e6765bf1 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-11-20 15:12:06 +01:00
David Mudrák a00ea08b58 MDL-63994 auth_cas: Include the login token in the simulated login form
The CAS login process relies on the standard authenticate_user_login()
call to set up the user. So we need to inject the login token to pass
the validation.
2018-11-15 21:20:13 +01:00
David Mudrák 1fcfd1b90e MDL-63994 login: Improve the logintoken param input
The logintoken is supposed to arrive as a part of the login form ($frm)
together with the username and password. So it should be handled the
same way - including the opportunity for the auth plugins to provide the
form data via the loginpage_hook().

This also implies that only logintoken coming as a part of the POST
request are taken into account, which is a good thing and another thin
layer in this security mechanism.
2018-11-15 21:20:12 +01:00
David Mudrák 8324204f6d MDL-63994 login: Clarify inline comments on loginpage_hook() usage
While working on the issue, I found these inline comments outdated and
confusing. The auth plugin types can implement a loginpage_hook()
method, the purpose of which is to inject the $user and/or $frm into
this login/index.php script. The new comments should make it more clear.

Note the second comment mentioned a prelogin_hook() which was an old
name of what is now called loginpage_hook(). It had nothing to do with
the existing pre_loginpage_hook() and was only confusing.
2018-11-15 21:20:12 +01:00
AMOS bot 00282f1c56 Automatically generated installer lang files 2018-11-13 00:07:55 +00:00
Eloy Lafuente (stronk7) 5c5ee48bf6 Moodle release 3.3.9 2018-11-10 20:36:16 +01:00
Eloy Lafuente (stronk7) 040430abff weekly release 3.3.8+ 2018-11-10 12:53:14 +01:00
Eloy Lafuente (stronk7) 2582943f92 Merge branch 'MDL-63960-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-11-10 10:28:39 +01:00
Andrew Nicols 97e8c0abce MDL-63960 dataprivacy: Do not perform strict type checks for id
When inherit is specified in the data registry it is stored as a
string and we cannot perform a strict comparison with it.

We should still compare strict comparison checks against null, or false,
but not for the NOTSET (0) or INHERIT values (-1).
2018-11-10 15:34:11 +08:00
Eloy Lafuente (stronk7) 51894a816f MDL-63919 tool_dataprivacy: admin & notify fixes
This includes the following:

1) Replace $ADMIN->id by get_admin()->id. The former doesn't exist.
2) Only change the notify parameter when it has not been specified
   at creation time (null). If specified, observe it.
3) Set the current user in tests to admin, able to create those
   requests.
2018-11-10 11:01:14 +08:00
Andrew Nicols c0ac0bd256 MDL-62564 tool_dataprivacy: Do not delete deleted user
This also ensures that we do not call delete_user on an already deleted
user.
2018-11-09 17:37:27 +08:00
Jun Pataleta a5c5651a0f Merge branch 'MDL-63942-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-11-09 11:42:11 +08:00
Andrew Nicols 01e64f89c7 Merge branch 'MDL-63919-33' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-11-09 10:53:48 +08:00
Andrew Nicols a505a4abd7 MDL-63942 dataprivacy: Disable deleted user task by default 2018-11-09 09:39:35 +08:00
Eloy Lafuente (stronk7) c587194dee Merge branch 'MDL-62564-integration-33-1' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-11-08 16:50:45 +01:00
Mihail Geshoski 76c9202996 MDL-63919 privacy: Store the creation method of the data request 2018-11-08 14:50:36 +08:00
Mihail Geshoski 752b63d574 MDL-63919 privacy: Disable notifications for auto created data requests 2018-11-08 14:50:36 +08:00
Mihail Geshoski 4cd4b14df2 MDL-62564 privacy: Create request for deleted users when setting enabled 2018-11-08 10:27:26 +08:00
Mihail Geshoski a49f5519e2 MDL-62564 privacy: Improve bulk deletion 2018-11-08 10:26:40 +08:00
Mihail Geshoski d494848985 MDL-62564 privacy: Add unit tests 2018-11-08 10:15:59 +08:00
Jun Pataleta 950da9db66 MDL-63183 auth_shibboleth: Don't render page when already logged in 2018-11-06 19:10:58 +01:00
Jun Pataleta d3c9d9306c MDL-63183 auth_shibboleth: Add hidden login token in guest login form 2018-11-06 19:10:58 +01:00
Damyon Wiese ff7941f00e MDL-63183 auth: Login protection
CSRF protection for the login form. The authenticate_user_login function was
extended to validate the token (in \core\session\manager) but by default it
does not perform the extra validation. Existing uses of this function from
auth plugins and features like "change password" will continue to work without
changes. New config value $CFG->disablelogintoken can bypass this check.
2018-11-06 19:10:58 +01:00
Eloy Lafuente (stronk7) a02c72d8bb weekly release 3.3.8+ 2018-11-06 19:06:35 +01:00
Eloy Lafuente (stronk7) 3d1ee2568e Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-11-06 19:06:33 +01:00
Andrew Nicols bb560c7012 Merge branch 'MDL-62563-33-3' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-11-06 09:57:19 +08:00
AMOS bot 0e8ebb0ead Automatically generated installer lang files 2018-11-06 00:07:43 +00:00
Mihail Geshoski 0375eb8488 MDL-62563 privacy: Create delete data request for existing deleted users 2018-11-05 16:29:47 +08:00
Mihail Geshoski 1f258e679a MDL-62563 privacy: Add unit tests 2018-11-05 16:22:17 +08:00
Eloy Lafuente (stronk7) 13d2621e23 weekly release 3.3.8+ 2018-11-02 21:08:30 +01:00
David Monllao 2709ae4dcc Merge branch 'MDL-62601-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-10-31 06:47:59 +01:00
Andrew Nicols 9aa05ccfdd MDL-62601 core_privacy: Ensure providers can handle deleted users 2018-10-31 13:44:51 +08:00
Andrew Nicols 6855ebb1eb MDL-62601 editor_atto: Do not use context_user 2018-10-31 13:44:51 +08:00
Eloy Lafuente (stronk7) 3d1949af87 weekly release 3.3.8+ 2018-10-30 18:23:58 +01:00
Andrew Nicols 3cda2cab0e Merge branch 'MDL-63626-33' of git://github.com/rezaies/moodle into MOODLE_33_STABLE 2018-10-30 11:00:43 +08:00
Andrew Nicols e899bccd8e MDL-63267 privacy: Remove use of int hints 2018-10-28 18:16:12 +08:00
Andrew Nicols a94a0c565b MDL-63267 core_grading: More Fix 5.6 type hints 2018-10-27 19:55:13 +08:00
Andrew Nicols 1171be0c0a MDL-63267 core_grading: Fix 5.6 type hints 2018-10-27 19:52:37 +08:00
Andrew Nicols 12963c1460 Merge branch 'MDL-63267-33' of git://github.com/lameze/moodle into MOODLE_33_STABLE 2018-10-27 19:30:23 +08:00
Tim Lock d5fea5ba37 MDL-63267 tool_privacy: Handle modules without privacy support. 2018-10-26 18:51:38 -04:00
Adrian Greeve 76a361a821 MDL-63267 grading methods: Unit test update for fix. 2018-10-26 18:51:38 -04:00
Adrian Greeve fb1740d897 MDL-63267 grading methods: Fix for multiple intances.
We were only exporting the one instance per item id.
Instead we should have been exporting all instances for each
item id.
2018-10-26 18:51:38 -04:00
Adrian Greeve 95433e3763 MDL-63267 tool_dataprivacy: Registry shows deprecated interfaces. 2018-10-26 18:51:38 -04:00
Adrian Greeve af7362c323 MDL-63267 privacy: Create deprecated provider interface.
Any interfaces that have become deprecated should extend
this interface.
2018-10-26 18:51:38 -04:00
Adrian Greeve 07d283e56e MDL-63267 core_grading: Unit test for new methods. 2018-10-26 18:51:38 -04:00
Adrian Greeve f1608e1184 MDL-63267 mod_assign: Update to providers to include adv grading. 2018-10-26 18:51:38 -04:00
Adrian Greeve 958dfb534d MDL-63267 core_grading: Update to providers and interface.
The previous interface was inadequate for retrieving user
information stored in the sub-plugins.

A new interface and methods have been added to successfully
deal with user data.
2018-10-26 18:51:31 -04:00
Eloy Lafuente (stronk7) aa561d76e8 weekly release 3.3.8+ 2018-10-26 18:05:00 +02:00
Eloy Lafuente (stronk7) fd497585a8 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-10-26 18:04:58 +02:00
Eloy Lafuente (stronk7) 62c1be5b19 Merge branch 'MDL-62904-33' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-10-26 00:04:09 +02:00
Andrew Nicols 258057604b MDL-63619 tool_dataprivacy: Cache purposes in form 2018-10-24 08:56:04 +08:00
Andrew Nicols e21e6d3d5b MDL-63619 tool_dataprivacy: Performance improvement 2018-10-24 08:56:04 +08:00
Andrew Nicols dbf4da68de MDL-63619 tool_dataprivacy: Fix inheritance from parent contexts
Inheritance should behave such that all contexts inherit from their
parent context.

Prior to this fix, if the value was not set on a context, then it was
getting a default of 'Inherit', but instead of inheritting from the
parent context, it was inheritting from its parent context _level_ which
is just wrong.
2018-10-24 08:56:04 +08:00
AMOS bot 20f17d1ee3 Automatically generated installer lang files 2018-10-22 00:07:36 +00:00
Eloy Lafuente (stronk7) a0d09bb67e weekly release 3.3.8+ 2018-10-19 17:10:47 +02:00
Eloy Lafuente (stronk7) fc77529235 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-10-19 17:10:45 +02:00
AMOS bot de2c1990eb Automatically generated installer lang files 2018-10-19 00:08:27 +00:00
Eloy Lafuente (stronk7) f8191ba10f Merge branch 'MDL-63632-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-10-18 02:39:43 +02:00
AMOS bot 3ea8d9d89a Automatically generated installer lang files 2018-10-18 00:07:44 +00:00
Andrew Nicols ceb8d8dc60 MDL-63632 forum: Delete where not discussion author 2018-10-18 07:20:29 +08:00
Jun Pataleta 4f232817c2 MDL-62904 tool_dataprivacy: Add data request links in profile page 2018-10-17 16:44:09 +08:00
Eloy Lafuente (stronk7) dabce49529 weekly release 3.3.8+ 2018-10-16 16:05:28 +02:00
Eloy Lafuente (stronk7) 67cc6f70c1 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-10-16 16:05:26 +02:00
Andrew Nicols 33adcf9926 Merge branch 'MDL-63421_33' of git://github.com/stronk7/moodle into MOODLE_33_STABLE 2018-10-16 08:56:20 +08:00
AMOS bot 7d3536fb48 Automatically generated installer lang files 2018-10-15 00:07:35 +00:00
Shamim Rezaie 0d7f464d4b MDL-63626 mod_quiz: Fixed a bug when there was no attempt on the quiz 2018-10-12 01:02:16 +11:00
Eloy Lafuente (stronk7) 629a97d5c1 weekly release 3.3.8+ 2018-10-11 13:22:43 +02:00
Eloy Lafuente (stronk7) 16f8828e9f Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-10-11 13:22:40 +02:00
AMOS bot fbfa3a0bd2 Automatically generated installer lang files 2018-10-11 00:07:33 +00:00
AMOS bot bb9bb21546 Automatically generated installer lang files 2018-10-10 00:07:30 +00:00
Eloy Lafuente (stronk7) b1da31a63e Merge branch 'MDL-62395-33' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-10-09 23:07:07 +02:00
Eloy Lafuente (stronk7) c165ee4890 Merge branch 'MDL-63401-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-10-09 11:36:58 +02:00
Adrian Greeve 0446552496 MDL-62491 core_privacy: php 5.6 fix for using current() 2018-10-09 16:22:25 +08:00
Jun Pataleta 7e954344d2 Merge branch 'MDL-62491-33' of git://github.com/abgreeve/moodle into MOODLE_33_STABLE 2018-10-09 14:39:25 +08:00
Adrian Greeve efd6748031 MDL-62491 navigation: Update to tree.js to allow callbacks. 2018-10-09 14:33:16 +08:00
Adrian Greeve 493f79cc6a MDL-62491 mod_assign: Fill in the rewrite plugin urls properly. 2018-10-09 14:33:16 +08:00
Adrian Greeve 0231ce53b1 MDL-62491 core_privacy: Add html files to the user data export.
This adds html to the data export that allows for easier navigation
and reading of data.
2018-10-09 14:33:16 +08:00
Andrew Nicols 222c900018 MDL-63401 tool_dataprivacy: Change default for require end date
We should preserve the existing behaviour.
2018-10-09 08:32:18 +08:00
Andrew Nicols e4d181c4de MDL-63401 tool_dataprivacy: Simplify where processing 2018-10-09 08:22:26 +08:00
Andrew Nicols 56b96699a6 MDL-63401 tool_dataprivacy: Move final cap check to endpoint 2018-10-09 08:22:26 +08:00
Andrew Nicols 5bc61c9686 MDL-63401 tool_dataprivacy: Allow expiriration of users without end date 2018-10-08 20:54:22 +08:00
Andrew Nicols 62165df6dd MDL-63401 tool_dataprivacy: Rewrite expired deletion handling
This change rewrites the way in which expiry is calculated and addresses
a number of closely related issues:

Users can customise, and add blocks with data to, their dashboard.  When
a user had done so, the user could be flagged for deletion, but the
blocks in their Dashboard were subject to the default block retention
policy. In addition there is no way to override the retention policy for
user blocks.

This change modifies the structure of the expiry mechanism:
- to consider any subcontext of the user context to be a part of the user
  context during calculation. User child contexts are not the property
  of the system, and should not be treated separately.
- the way in which the context expiry mechanism worked was to select
  use a multiple different managers based solely on the context level.
  Because of the use of user blocks, this proved to be unreliable as
  blocks has been attributed purely to courses.
  This has been changed to a single manager which is aware of hierarchy
  and deletions as a whole.
- to prepare for upcoming work relating to more detailed expiry
  mechanisms, a new expiry_info class is introduced and used to
  merge the expiry of child contexts into a working in-memory view.

This changeset includes extensive unit tests.
2018-10-08 20:54:20 +08:00
Andrew Nicols 0cd43cccbf MDL-63401 core: Do not delete the user context
As long as we have a user record, we should not delete the context which
relates to it.
2018-10-08 20:54:00 +08:00
Andrew Nicols 0b9cf6ba5e MDL-63401 tool_dataprivacy: Move cap checks to endpoints from API 2018-10-08 20:54:00 +08:00
Eloy Lafuente (stronk7) 6ef7759473 MDL-63421 env: Moodle 3.4.x and 3.5.x do not support PHP 7.3 2018-10-05 13:38:40 +02:00
Eloy Lafuente (stronk7) 6816976c3b weekly release 3.3.8+ 2018-10-05 12:27:53 +02:00
Jun Pataleta 6e7a73c16d MDL-62395 tool_dataprivacy: Purpose name and description in one column
* Show the purpose's name and description under one column.
2018-10-05 09:29:12 +08:00
Jun Pataleta 6fc275038c MDL-62395 tool_dataprivacy: Fix tables display
* Use standard Bootstrap4 table classes for the categories and purposes
  tables.
* Set 25% width for the name and description columns of the purposes
  table.
* Set 50% width for the description column of the categories table.
2018-10-05 09:29:12 +08:00
AMOS bot 41548fb26e Automatically generated installer lang files 2018-10-04 00:07:37 +00:00
Eloy Lafuente (stronk7) 082ef8ef3c weekly release 3.3.8+ 2018-09-27 15:17:50 +02:00
Andrew Nicols 371168e848 Merge branch 'MDL-63346_33' of https://github.com/stronk7/moodle into MOODLE_33_STABLE 2018-09-25 09:13:44 +08:00
Eloy Lafuente (stronk7) 982839422f weekly release 3.3.8+ 2018-09-20 12:11:59 +02:00
Eloy Lafuente (stronk7) cc22614602 MDL-63346 nodejs: Bump to created from scratch shrinkwrap file
Steps:

- remove node_modules & npm-shrinkwrap.json
- npm cache clear --force
- npm install
- npm shrinkwrap
2018-09-20 10:56:29 +02:00
Eloy Lafuente (stronk7) 094fa8ce8a MDL-63346 travis: GRUNT job immune to npm-shrinkwrap.json changes
When upgrading nodejs/npm versions, there are modifications to
different packages or to the format of the npm-shrinkwrap.json
file. Usually that leads to failed jobs untill the bundled file
is updated.

To avoid that, we just ignore the npm-shrinkwrap.json file, so
just the REAL modifications in css/js/ignore stuff are considered.
2018-09-20 10:55:20 +02:00
Eloy Lafuente (stronk7) eca5403915 Merge branch 'MDL-63116-33-1' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-09-18 23:54:41 +02:00
Mihail Geshoski 5924e71021 MDL-61908 privacy: Fix heading in data privacy pages 2018-09-18 18:51:43 +02:00
Eloy Lafuente (stronk7) b9706f8cf3 Merge branch 'MDL-63109-33' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-09-18 18:32:26 +02:00
Mihail Geshoski b17a7d65a7 MDL-63109 privacy: Add behat tests 2018-09-18 15:08:54 +08:00
Mihail Geshoski 558126aee6 MDL-63109 privacy: Fix the initialisation of the policy acceptance modal 2018-09-18 15:05:15 +08:00
Eloy Lafuente (stronk7) 1cec2b9041 Merge branch 'MDL-63009-33' of git://github.com/mickhawkins/moodle into MOODLE_33_STABLE 2018-09-17 23:31:38 +02:00
Eloy Lafuente (stronk7) 5279b38c8b Merge branch 'MDL-63184-33-2' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-09-17 23:11:54 +02:00
Jun Pataleta 286e9ef666 MDL-63184 tool_dataprivacy: Ensure PO roles have correct capability 2018-09-17 22:36:16 +08:00
David Monllao 026b1ffe51 Merge branch 'MDL-62554-33' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-09-17 13:46:17 +02:00
Mihail Geshoski 6eddcd1310 MDL-63116 privacy: Add bulk actions to the privacy request table 2018-09-17 08:28:28 +08:00
Mihail Geshoski 4bb3e3fccf MDL-63116 privacy: Add behat tests 2018-09-17 08:28:28 +08:00
Mihail Geshoski be23442e17 MDL-63116 privacy: Add unit tests 2018-09-17 08:28:28 +08:00
Eloy Lafuente (stronk7) e75470d531 weekly release 3.3.8+ 2018-09-14 11:06:45 +02:00
Eloy Lafuente (stronk7) c9a28c645b MDL-61169 iplookup: Change to another block still remaining to SF 2018-09-13 19:07:07 +02:00
Jun Pataleta df84f822bf MDL-62554 tool_dataprivacy: Integration review fixes 2018-09-13 14:58:27 +08:00
Michael Hawkins 4ca488c639 MDL-63009 tool_dataprivacy: Added site name/link to request emails 2018-09-12 17:50:38 +08:00
Jun Pataleta 745f6e7f61 MDL-62554 tool_dataprivacy: Bump version for new WS functions 2018-09-10 12:21:40 +08:00
Jun Pataleta f5b95df245 MDL-62554 tool_dataprivacy: Fix display of activity options 2018-09-10 12:21:40 +08:00
Jun Pataleta 12131e4806 MDL-62554 tool_dataprivacy: Fix template for Bootstrapbase compatibility 2018-09-10 12:21:40 +08:00
Jun Pataleta 2dfd3b9839 MDL-62554 tool_dataprivacy: Remove defaults form
With the changes in this issue, this form is no longer being used.
2018-09-10 12:21:40 +08:00
Jun Pataleta f68fe6bc00 MDL-62554 tool_dataprivacy: Behat tests for managing registry defaults
* Also new step definitions and generator for:
  - Creating categories and purposes
  - Assigning category and purpose to context instances.
2018-09-10 12:21:40 +08:00
Jun Pataleta ea8b23542a MDL-62554 tool_dataprivacy: Unit tests for new WS/API functions 2018-09-10 12:21:40 +08:00
Jun Pataleta 1a228ad894 MDL-62554 tool_dataprivacy: Fix fetching of default contexts
* We also need to handle default contexts for activities. If defaults
  for an activity is set, fetch that. If not, fetch the defaults for
  the context level.
2018-09-10 12:21:40 +08:00
Jun Pataleta 324e9a2800 MDL-62554 tool_dataprivacy: Allow setting of activity defaults
* Allow the setting of data registry defaults for activity modules.
* Rewrite the defaults page so that it uses templates.
* Use a tabbed layout for the defaults page that shows the default
  category and purpose per context level.
* New API and web service functions that enables the setting of the
  defaults.
2018-09-10 12:21:40 +08:00
Eloy Lafuente (stronk7) c37329dc10 Moodle release 3.3.8 2018-09-08 16:49:39 +02:00
AMOS bot c3dbe66477 Automatically generated installer lang files 2018-09-08 00:07:55 +00:00
Jun Pataleta 3bfd32be9b weekly release 3.3.7+ 2018-09-07 11:11:40 +08:00
Jun Pataleta 85a42937b9 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-09-07 11:11:11 +08:00
Jake Dallimore c3db28fe90 Merge branch 'MDL-63302-33' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-09-06 09:25:10 +08:00
Jun Pataleta 2f1c8a398b MDL-63302 tool_dataprivacy: Increment version of the merged plugin
This is to distinguish the versions available from the plugins directory
from the versions that are part of Moodle standard distribution.
2018-09-06 08:54:23 +08:00
Eloy Lafuente (stronk7) e7c034a8d8 Merge branch 'MDL-63292-33-policyversion' of git://github.com/mudrd8mz/moodle into MOODLE_33_STABLE 2018-09-05 21:10:48 +02:00
David Mudrák 5a6185300a MDL-63292 tool_policy: Increment version of the merged plugin
This is to distinguish the versions available from the plugins directory
from the versions that are part of Moodle standard distribution.
2018-09-05 14:03:15 +02:00
Jake Dallimore 9ab746af27 MDL-62947 mod_feedback: fix feedback so it correctly uses forms API
Feedback code was doing the following, which is incompatible with the
sec-patched quickforms lib:
- appending '[0]' to non-group element names, meaning type checks in
getCleanType() were falling back to PARAM_RAW instead of PARAM_INT,
and meaning _findValue() was comparing elements it thought to be arrays
with scalar submit values (worked with eval() implementation as the 0th
element of a string was the character, but not working when patched).
- external unit tests incorrectly testing multichoice questions in the
process_page tests - patched lib highlighted failures.

It was also doing the following:
- actively setting null as default element values instead of 0 for
radio buttons and dropdowns (for 'not selected'). This is incompatible
with more recent quickforms lib (see MDL-63070).
- creating groups using addElement instead of addGroup - not recommended

This patch addresses the above.
2018-09-05 12:14:05 +08:00
Jake Dallimore a21f18e947 MDL-62947 core_form: fix remote code execution exploit in QuickForms
Applies the patch found upstream:
https://github.com/pear/HTML_QuickForm/commit/
d3a6d5c44dedf3c164c6c79198e4ef479bcedcd2 and make util methods static
for php7 compatibility.
2018-09-05 12:14:05 +08:00
AMOS bot 711d42778a Automatically generated installer lang files 2018-09-05 00:07:48 +00:00
Damyon Wiese d2a497ee03 MDL-62748 course: maxsections is a limit
For course formats shipped with core, maxsections should be a limit to the number of sections
allowed in the course.
2018-09-04 14:40:43 +02:00
Mihail Geshoski 3f8dfe2ab7 MDL-62857 theme_boost: Better filtering on navbar / breadcrumbs 2018-09-04 14:40:43 +02:00
Tim Hunt 09cbca8566 MDL-62880 qtype_ddwtos: drop support for legacy import format
It was more than 8 years since we wrote in that format.
2018-09-04 14:40:43 +02:00
David Monllao 1c2efd6f1b Merge branch 'MDL-62342-33' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-09-04 14:34:19 +02:00
Jun Pataleta 751bdfd50d Merge branch 'MDL-60901_33' of git://github.com/stronk7/moodle into MOODLE_33_STABLE 2018-09-04 10:17:54 +08:00
Eloy Lafuente (stronk7) 19c1e9845b MDL-60901 environment: 3.6 requirements
Changes include:
- MySQL 5.6 is required.
- PostgreSQL 8.4 is required.
- Oracle 11.2 is required.
- 64bits architecture is recommended.
2018-09-03 10:13:44 +02:00
Mihail Geshoski 64a736ee72 MDL-62342 privacy: Fix strings used in the consent on behalf modal 2018-09-03 13:02:16 +08:00
Mihail Geshoski 1f319dda3d MDL-62342 privacy: Use singular/plural form in labels 2018-09-03 13:02:16 +08:00
AMOS bot 5790e87939 Automatically generated installer lang files 2018-09-02 00:07:46 +00:00
AMOS bot 40260a258f Automatically generated installer lang files 2018-09-01 00:07:46 +00:00
Eloy Lafuente (stronk7) 86f3c074e9 weekly release 3.3.7+ 2018-08-30 19:04:15 +02:00
Eloy Lafuente (stronk7) cfac8bb5ab Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-08-30 19:04:12 +02:00
Jake Dallimore 772afa0f43 Merge branch 'MDL-63094-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-08-30 13:10:45 +08:00
Andrew Nicols 111ec01643 MDL-63094 tool_policy: Replace '×' with '.close'
This relates to MDL-59883, which was not backported to 33 and beyond.
2018-08-30 11:25:44 +08:00
AMOS bot cf21130cc2 Automatically generated installer lang files 2018-08-30 00:08:24 +00:00
Eloy Lafuente (stronk7) a454fd5f57 Merge branch 'MDL-63094-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-08-29 00:25:18 +02:00
Jun Pataleta 300e220dbc MDL-63187 tool_dataprivacy: Reopen autocomplete on legal basis selection
* The multiselect autocomplete element closes after selection on 33. The
feature where it keeps open on multiselection has only been implemented
since 34 and up. So to select another legal basis, we need to reopen
the autocomplete element again.
2018-08-28 21:05:14 +08:00
Jake Dallimore 5432075187 Merge branch 'MDL-63101-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-08-28 16:56:32 +08:00
Eloy Lafuente (stronk7) a51fa84f50 Merge branch 'MDL-62989-33' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-08-27 17:51:47 +02:00
Jun Pataleta 052d30081e MDL-63187 tool_dataprivacy: Behat fixes for confirmation lang strings 2018-08-27 22:45:20 +08:00
David Monllao ab883ad6b0 MDL-63187 tool_dataprivacy: Update delete purpose and category lang strings 2018-08-27 12:12:12 +02:00
David Monllao 6069d11046 Merge branch 'MDL-63187-33' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-08-27 11:59:37 +02:00
Andrew Nicols 1875af2207 MDL-63101 cache: Improve accuracy of cache event invalidation 2018-08-27 14:44:42 +08:00
Mihail Geshoski 65b8d2ab7d MDL-62989 privacy: Improve data request order for users 2018-08-24 15:35:23 +08:00
Jake Dallimore b39379cb98 weekly release 3.3.7+ 2018-08-24 13:08:51 +08:00
Jun Pataleta c840b579cb MDL-63187 tool_dataprivacy: Improve confirm message lang strings
* Updated the delete confirmation messages so that the category/purpose
names are shown in italics. This makes it easier for the privacy officer
to see the category/purpose that is being deleted.
2018-08-23 16:59:13 +08:00
Jun Pataleta 784c21b116 MDL-63187 tool_dataprivacy: Behat tests for purposes/categories CRUD 2018-08-23 16:59:13 +08:00
Jun Pataleta f423dd8ccf MDL-63187 tool_dataprivacy: Fix delete button text
* No need to add the purpose/category name for the delete button itself
* Fix delete confirmation lang strings.
* Add button titles to Add purpose/category buttons so they can be
  clicked on when running Behat tests.
2018-08-23 16:59:13 +08:00
Andrew Nicols f5d57e74bf MDL-63094 tool_policy: Fix the cookie banner to the bottom
Floating banners cause issues with clickability in Behat as it is unable
to understand that it cannot interact with the elements underneath the
floating banner, or that it needs to scroll the page so that the
required content is no longer beneath the floating banner.

Changing the banner to be fixed to the bottom of the page during Behat
runes is a reliable fix.
2018-08-22 11:00:08 +08:00
Andrew Nicols e67669e638 MDL-63094 js: Do not check visibility before hiding backdrop
It's possible for the backdrop to be created during the show, before the
modal is actually fully shown.

This check is unnecessary because the getBackdrop() function always
returns a value and will always succeed.
2018-08-22 10:29:33 +08:00
Andrew Nicols aa881b7717 MDL-63094 tool_policy: Fix race condition in modal display
The way in which the modal was displayed meant that there were no
pending JS items, whilst the modal was rendered, causing random behat
fails.

This JS has been restructured to create the Modal and pass it a set of
Promises for both the title, and body.
2018-08-22 10:28:45 +08:00
Andrew Nicols 19a94ef923 Merge branch 'MDL-62660_33' of https://github.com/stronk7/moodle into MOODLE_33_STABLE 2018-08-22 08:05:03 +08:00
Eloy Lafuente (stronk7) 866d7a064a MDL-62660 xmldb: fix tool_dataprivacy_request_>status to int(2)
So the new defined statuses fit in the column for all databases.
2018-08-22 00:08:01 +02:00
Andrew Nicols 1d5b6e94ee Merge branch 'MDL-62660-33' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-08-21 11:14:34 +08:00
Jun Pataleta 014628c4c4 MDL-62660 tool_dataprivacy: Behat fix for data deletion 2018-08-21 10:39:12 +08:00
Jun Pataleta 61b0e103b7 MDL-62660 tool_dataprivacy: CI fixes 2018-08-21 09:52:59 +08:00
Jun Pataleta 2a0c44b56d MDL-62660 phpunit: Remove setting of user when running adhoc tasks
Before 3.4 tasks can't run as specific users.
2018-08-21 09:52:59 +08:00
Michael Hawkins ab79c7bf95 MDL-62660 tool_dataprivacy: Behat additions for deletion and data expiry 2018-08-21 09:52:59 +08:00
Michael Hawkins 4415985085 MDL-62660 tool_dataprivacy: Add and update PHPUnit tests
Updated tests with latest statuses.
Added testing for request download expiry functionality and method.
Added assign_site_dpo within a parent class for unit tests.
2018-08-21 09:52:59 +08:00
Michael Hawkins 83c83473f8 MDL-62660 tool_dataprivacy: Add scheduled task to expire data requests 2018-08-21 09:52:59 +08:00
Jun Pataleta 680b9186c7 MDL-62660 tool_dataprivacy: Fix version number in upgrade code 2018-08-21 09:52:59 +08:00
Michael Hawkins 3717d20a33 MDL-62660 tool_dataprivacy: Add ability to expire data requests
Also replaced Completed status with situation specific statuses.
Also improved UX on request pages in line with expiries and the aadditional statuses.
2018-08-21 09:52:59 +08:00
Andrew Nicols e29b668d7b MDL-62660 tool_dataprivacy: Add method for unit tests to run adhoc tasks 2018-08-21 09:52:59 +08:00
Eloy Lafuente (stronk7) 8987e225da weekly release 3.3.7+ 2018-08-17 17:37:34 +02:00
Eloy Lafuente (stronk7) b96151e864 NOBUG: Fixed file access permissions 2018-08-17 17:37:34 +02:00
Eloy Lafuente (stronk7) 1c6f6505b9 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-08-17 17:37:32 +02:00
Damyon Wiese 884aa7b9f7 MDL-62884 privacy: Additional fix for popper.js
The older version of popper needs custom CSS to ensure the menus are
visible without scrolling.
2018-08-17 16:08:09 +08:00
Damyon Wiese 082f2f7dd8 MDL-62884 privacy: Fix styles
An action menu displayed inside a table needs to be tested on every theme. It is
required to ensure that the menu displays correctly, even when it would display outside
the scrollable region for the table.
2018-08-17 16:08:03 +08:00
Jake Dallimore 9bec7f417f Merge branch 'MDL-63050-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-08-16 02:34:29 +02:00
Andrew Nicols b3865f159b Merge branch 'MDL-62600-33' of git://github.com/mickhawkins/moodle into MOODLE_33_STABLE 2018-08-16 02:34:26 +02:00
AMOS bot f03a28c520 Automatically generated installer lang files 2018-08-16 00:08:36 +00:00
AMOS bot 68d882a927 Automatically generated installer lang files 2018-08-15 00:07:58 +00:00
Michael Hawkins 41b628707c MDL-62600 tool_dataprivacy: Corrected availability of PO pages to admins 2018-08-14 13:20:14 +08:00
Karen Holland 2d287b27e3 MDL-62896 Privacy: External plugin Additional label update. 2018-08-14 08:06:14 +08:00
Andrew Nicols b76d708315 Merge branch 'MDL-62799-33-2' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-08-13 13:55:20 +08:00
Jun Pataleta 1f2972764b MDL-62799 core: Add tool_dataprivacy to list of standard plugins 2018-08-13 12:45:35 +08:00
Jun Pataleta 2161e3294d MDL-62799 tool_dataprivacy: Add new data request modal type
* Add a new TYPE_ENQUIRY modal type for the data request modal AMD
  module in order for it to be able to render the appropriate template
  as required:
  1. Use the tool_dataprivacy/data_request_modal template for data
     export/deletion requests;
  2. Or use the tool_dataprivacy/mark_request_complete_modal template
     for general enquiries.
2018-08-13 12:27:07 +08:00
Jun Pataleta 898584944b MDL-62799 tool_dataprivacy: Fix CiBoT complaints 2018-08-13 12:27:07 +08:00
Jun Pataleta 642ad98e28 MDL-62799 tool_dataprivacy: Move user generation within the test
Patch originally from MDL-62962.
2018-08-13 12:26:46 +08:00
sam marshall 6892904225 MDL-62799 tool_dataprivacy: Add capabilities to control data downloads
Patch originally from MDL-61652.
2018-08-13 12:26:30 +08:00
Jun Pataleta 5f7519ba19 MDL-62799 tool_dataprivacy: Behat tests for contacting PO
Patch originally from MDL-62026.
2018-08-13 12:26:07 +08:00
Jun Pataleta 2c904a6c87 MDL-62799 tool_dataprivacy: Allow manual completion of general enquiries
Patch originally from MDL-62026.
2018-08-13 12:25:34 +08:00
Jun Pataleta 91b38b5c30 MDL-62799 tool_dataprivacy: Merge the data privacy tool in 3.3 2018-08-13 09:22:49 +08:00
Eloy Lafuente (stronk7) e60b5bd04d weekly release 3.3.7+ 2018-08-10 13:21:56 +02:00
Andrew Nicols d54b8bb7ee Merge branch 'MDL-62619_33' of git://github.com/stronk7/moodle into MOODLE_33_STABLE 2018-08-10 08:46:57 +08:00
Eloy Lafuente (stronk7) a300c3479d MDL-62619 privacy: Prevent action when boolean queries are involved
Before the patch, queries like:

SELECT 1 FROM dual UNION SELECT 2 FROM dual

were failing badly, with everything but the first numeric element
being ignored by the optimization.

So, being conservative, now we reduce the query being analysed,
ignoring any subquery, inline view (anything within parenthesis
in general) and, in the remaining query, if a boolean query (UNION,
MINUS, INTERSECT...) is found, we don't apply any optimization.
2018-08-09 16:12:58 +02:00
Eloy Lafuente (stronk7) 38174e03de Merge branch 'MDL-62619-33-privacycontexts' of git://github.com/mudrd8mz/moodle into MOODLE_33_STABLE 2018-08-08 01:03:29 +02:00
Andrew Nicols 0d21b5a2dc MDL-62800 behat: Correct path to P&P
This commit was originally a part of MDL-62950
2018-08-07 14:12:51 +08:00
Andrew Nicols a36a6e4491 Merge branch 'MDL-62800-33' of git://github.com/mihailges/moodle into MOODLE_33_STABLE 2018-08-07 12:14:25 +08:00
Mihail Geshoski 27621e4bfa MDL-62800 tool_policy: Add policy plugin in standard_plugins_list() 2018-08-07 11:21:44 +08:00
sarjona 939ba8562b MDL-62800 tool_policy: Merge the policy tool to 3.3
The following changes has been done from the original repo:
- Remove README.md, LICENSE.md and .travis.yml
- Review the version.php content, to look like a standard plugin
- Adapt jquery-eu-cookie-law-popup.js and readme_moodle.txt to remove the
eslint-ignore
2018-08-07 11:08:14 +08:00
Andrew Nicols d94a35a5a4 weekly release 3.3.7+ 2018-08-03 11:30:19 +08:00
Andrew Nicols 1f44dfad42 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-08-03 11:30:16 +08:00
Andrew Nicols 62ba30c11f MDL-63050 cachestore_redis: Update hExists to check empty 2018-08-02 11:06:05 +08:00
Andrew Nicols 663c41bf76 MDL-63050 redis: Make session check compatible with Redis 4.0 2018-08-02 11:06:05 +08:00
AMOS bot ded79d63d8 Automatically generated installer lang files 2018-07-28 00:09:05 +00:00
David Mudrák d6dcfee9f5 MDL-62619 privacy: Fix a missing alias in the blog provider class
The new implementation revealed another existing bug that did not expose
before due to coincidently same named table alias in the outer query.
2018-07-27 10:14:28 +02:00
David Mudrák bb478e8ddd MDL-62619 privacy: Fix some bits of performance improvements
* We need to use is_numeric() in this case as is_int() would never
  return true.
* Extend the supported cases, add support for SQL consisting just of
  numerical value or selectinga numerical constant.
* Do not rely on any particulat letter case in provided SQL.
* Add unit tests for the new method. Even when it is a protected one, it
  is an essential unit to be tested on its own.
2018-07-27 10:14:28 +02:00
Andrew Nicols be6fc5b122 MDL-62619 privacy: Improve performance of contexts loading
When possible, we want to use JOIN over WHERE IN. It is known that the
later performs badly in MySQL.
2018-07-27 10:14:28 +02:00
Jun Pataleta cad1e4f427 Merge branch 'MDL-62950-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-07-27 16:06:19 +08:00
Andrew Nicols 858cb072b1 MDL-62950 behat: Disable editing for boost test 2018-07-27 11:08:11 +08:00
Andrew Nicols 5ededd52c0 MDL-62950 behat: Correct path to P&P 2018-07-27 11:07:04 +08:00
Eloy Lafuente (stronk7) cf31c73791 weekly release 3.3.7+ 2018-07-26 19:56:40 +02:00
Eloy Lafuente (stronk7) 265894ee59 Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-07-26 19:56:37 +02:00
Jun Pataleta f1a57a3181 Merge branch 'MDL-62653-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-07-26 10:58:07 +08:00
Andrew Nicols ce48eff24e MDL-62653 cron: Fix tab => whitespace 2018-07-26 10:57:22 +08:00
Jun Pataleta 008b92bd0a Merge branch 'MDL-62653-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-07-26 10:46:27 +08:00
Andrew Nicols fe8ca8b78d MDL-62653 behat: Ensure that tasks run properly from behat 2018-07-26 09:52:00 +08:00
Andrew Nicols e5fe5da40c MDL-62653 cron: Move adhoc task to separate fn 2018-07-26 09:23:17 +08:00
Jun Pataleta d8d35d7c1f Merge branch 'MDL-62781-33' of git://github.com/junpataleta/moodle into MOODLE_33_STABLE 2018-07-18 10:03:46 +08:00
Jun Pataleta 680160dca4 MDL-62781 core_question: Remove unnecessary assertion 2018-07-18 10:00:05 +08:00
Tim Hunt 78315808b1 MDL-62781 question/privacy: fix tests with CodeRunner is installed
The tests were assuming that no plugin ever created questions on
install.

Also change the tests to do the counting in the DB. That is more
efficient.
2018-07-18 10:00:05 +08:00
AMOS bot b67c98ca83 Automatically generated installer lang files 2018-07-18 00:07:51 +00:00
AMOS bot d8eb85604f Automatically generated installer lang files 2018-07-17 00:08:38 +00:00
Eloy Lafuente (stronk7) 027b757f20 Moodle release 3.3.7 2018-07-07 06:49:34 +02:00
Eloy Lafuente (stronk7) a21351f43f Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-07-07 06:49:17 +02:00
AMOS bot 573fe725a4 Automatically generated installer lang files 2018-07-07 00:08:48 +00:00
AMOS bot 32bfc786df Automatically generated installer lang files 2018-07-05 00:08:41 +00:00
Tim Hunt 3504a2a99f MDL-62820 question import: properly escape output 2018-07-04 15:42:22 +08:00
Adrian Greeve eca3395a70 MDL-62616 tool_log: Add a setting to disable exporting logs. 2018-07-04 10:01:07 +08:00
Marina Glancy d5a8d310e0 MDL-62790 course: cap check in core_course_get_categories 2018-07-03 12:04:37 +08:00
Jake Dallimore af89e5a896 weekly release 3.3.6+ 2018-06-28 14:02:58 +08:00
Eloy Lafuente (stronk7) 31aa968f43 Merge branch 'wip-MDL-62755-33' of git://github.com/abgreeve/moodle into MOODLE_33_STABLE 2018-06-28 01:57:03 +02:00
Adrian Greeve 560ddebb05 MDL-62755 mod_assign: Update to unit tests.
Overrides are now checked to be exported properly.
2018-06-25 09:54:19 +08:00
Adrian Greeve 45654c4813 MDL-62755 mod_assign: Fix override export problem.
The cutoffdate and allowsubmissionsfromdate were not
being exported.
2018-06-25 09:54:19 +08:00
Eloy Lafuente (stronk7) ff3187ba50 weekly release 3.3.6+ 2018-06-21 14:01:00 +02:00
Eloy Lafuente (stronk7) f5d105b4e0 Merge branch 'MDL-62449-33' of git://github.com/sarjona/moodle into MOODLE_33_STABLE 2018-06-19 18:52:49 +02:00
David Monllao 6f2c8d1ca6 Merge branch 'MDL-62588-33' of git://github.com/rezaies/moodle into MOODLE_33_STABLE 2018-06-19 13:00:04 +02:00
Sara Arjona 10c6f9c88f MDL-62449 scorm: Move related data to data in the privacy export 2018-06-18 10:45:08 +02:00
Sara Arjona 33e3839fdc MDL-62449 scorm: Add SCORM data to privacy export (name, description) 2018-06-18 10:45:08 +02:00
Jun Pataleta f60980615c Merge branch 'MDL-62614-33' of git://github.com/zig-moodle/moodle into MOODLE_33_STABLE 2018-06-18 16:12:03 +08:00
Jun Pataleta 153f6546bb Merge branch 'wip-MDL-62474-33' of git://github.com/abgreeve/moodle into MOODLE_33_STABLE 2018-06-18 14:32:01 +08:00
Adrian Greeve 6acb8efc76 MDL-62474 theme_boost: Update to provider and unit test.
Theme boost contains a user preference which was not reported
before. Unit tests have been added to test the update.
2018-06-18 14:18:33 +08:00
Willian Mano 73e51962df MDL-62474 theme_boost: Fix Boost privacy provider 2018-06-18 14:12:20 +08:00
Huong Nguyen e56de91383 MDL-62614 Privacy: Core tag provider delete_item_tags function not work 2018-06-18 14:01:02 +08:00
Shamim Rezaie 68aabce476 MDL-62588 enrol_paypal: Add missing instanceid field 2018-06-18 15:20:22 +10:00
Eloy Lafuente (stronk7) 8d53360c70 weekly release 3.3.6+ 2018-06-14 10:55:54 +02:00
Eloy Lafuente (stronk7) 833d4aa42c Merge branch 'install_33_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_33_STABLE 2018-06-14 10:55:52 +02:00
AMOS bot 6e043dd5ec Automatically generated installer lang files 2018-06-13 00:08:30 +00:00
Jun Pataleta f1c8d8a374 Merge branch 'wip-MDL-62574-33' of git://github.com/abgreeve/moodle into MOODLE_33_STABLE 2018-06-12 11:44:58 +08:00
Adrian Greeve 8e31b3241a MDL-62574 core_privacy: Unit tests to check the context base.
This now tests the possibility of a context being deleted.
2018-06-12 11:29:37 +08:00
Adrian Greeve 86b5d678bc MDL-62574 core_privacy: Check the contextlist for real contexts.
There are situations when contexts will be deleted in the provider.
The context list is not updated and so we have introduced a try
catch.
2018-06-12 11:29:36 +08:00
Eloy Lafuente (stronk7) 1b9f20fdf9 weekly release 3.3.6+ 2018-06-07 16:59:53 +02:00
Andrew Nicols 5204184a9b Merge branch 'm33_MDL-61702_MariaDB_InnoDB10d3d1_Params_Removed' of https://github.com/scara/moodle into MOODLE_33_STABLE 2018-06-06 09:10:48 +08:00
Matteo Scaramuccia a2650c9a7d MDL-61702 dml: MariaDB 10.3 supports only the Barracuda file format.
More details about this change also in https://tracker.moodle.org/browse/MDL-59099

Ref.: https://mariadb.com/kb/en/library/mariadb-1031-release-notes/#other-variables
2018-05-31 23:02:49 +02:00
David Monllao 83832b98a3 weekly release 3.3.6+ 2018-05-31 08:39:31 +02:00
David Monllao 149552615c Merge branch 'm33_MDL-59596_DDL_Issues_With_MariaDB_10p2_SQLMode' of https://github.com/scara/moodle into MOODLE_33_STABLE 2018-05-28 11:49:13 +02:00
Andrew Nicols 33ef4d941d MDL-62543 mod_forum: Add RSS Unit tests 2018-05-28 08:55:32 +08:00
Andrew Nicols 5aa514982d Merge branch 'MDL-62543-33' of git://github.com/tobiasreischmann/moodle into MOODLE_33_STABLE 2018-05-28 08:55:24 +08:00
Eloy Lafuente (stronk7) 2ed6a293df Merge branch 'MDL-62408_33_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_33_STABLE 2018-05-28 02:15:49 +02:00
David Monllao 7c84c195e9 weekly release 3.3.6+ 2018-05-24 10:45:55 +02:00
Tobias Reischmann 5db5c6dcc3 MDL-62543 forum: Switched the usage of the deleted flag for rss
The deleted flag was introduced through the Privacy API and was used
for post selection for the rss feed. In there it was used in the
wrong way, which lead to the exclusion of all non deleted posts instead
of excluding all deleted ones.
This commit fixes this problem.
2018-05-23 14:24:19 +02:00
Eloy Lafuente (stronk7) 8fb6a1a4f3 Merge branch 'MDL-62500-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-05-21 13:39:29 +02:00
David Monllao 08c753034f Merge branch 'm33_MDL-61893_Accept_Any_Node_Carbon_Version' of https://github.com/scara/moodle into MOODLE_33_STABLE 2018-05-21 12:04:57 +02:00
David Monllao c54ed68179 Merge branch 'MDL-62516-33' of git://github.com/andrewnicols/moodle into MOODLE_33_STABLE 2018-05-21 11:15:21 +02:00
Andrew Nicols 485c8e1f77 MDL-62500 tag: Update checkbox label when updating tag 2018-05-21 15:47:21 +08:00
Andrew Nicols 62994208a1 MDL-62516 mod_forum: Only delete files for requested user 2018-05-21 14:13:03 +08:00
Matteo Scaramuccia 74b5f26b3b MDL-61893 JavaScript: Bumped npm packages
Performed by removing the current 'npm-shrinkwrap.json' file:
$ rm -Rf node_modules
$ rm -f npm-shrinkwrap.json
$ npm install
$ npm shrinkwrap
2018-05-18 00:33:03 +02:00
Matteo Scaramuccia 6bdc80eaf5 MDL-61893 JavaScript: Accept any node version but Carbon (LTS) 2018-05-18 00:33:03 +02:00
Mark Johnson 6a5d9d0705 MDL-62408 behat: Correct PHPUNIT_TEST checks in behat_config_util 2018-05-15 09:15:56 +01:00
Matteo Scaramuccia 23c1612fe1 MDL-59596 ddl: Support MariaDB 10.2.4+ SQL_MODE new default value. 2018-03-26 23:08:23 +02:00
Matteo Scaramuccia 7da1a29e3b MDL-59596 ddl: Added has_breaking_change_sqlmode() 2018-03-26 23:08:23 +02:00
381 changed files with 41140 additions and 1865 deletions
+1
View File
@@ -3,6 +3,7 @@
*/**/build/
node_modules/
vendor/
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
+1
View File
@@ -4,6 +4,7 @@ theme/clean/style/custom.css
theme/more/style/custom.css
node_modules/
vendor/
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/amd/src/popper.js
auth/cas/CAS/
+3 -1
View File
@@ -55,7 +55,7 @@ matrix:
include:
# Run grunt/npm install on highest version ('node' is an alias for the latest node.js version.)
- php: 7.1
env: DB=none TASK=GRUNT NVM_VERSION='8.9'
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
exclude:
# MySQL - it's just too slow.
@@ -264,7 +264,9 @@ script:
grunt ;
# Add all files to the git index and then run diff --cached to see all changes.
# This ensures that we get the status of all files, including new files.
# We ignore npm-shrinkwrap.json to make the tasks immune to npm changes.
git add . ;
git reset -- npm-shrinkwrap.json ;
git diff --cached --exit-code ;
fi
+183
View File
@@ -2097,6 +2097,7 @@
<VENDOR name="oracle" version="10.2" />
</DATABASE>
<PHP version="7.0.0" level="required">
<RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
@@ -2273,6 +2274,7 @@
<VENDOR name="oracle" version="10.2" />
</DATABASE>
<PHP version="7.0.0" level="required">
<RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
@@ -2435,4 +2437,185 @@
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
<MOODLE version="3.6" requires="3.1">
<UNICODE level="required">
<FEEDBACK>
<ON_ERROR message="unicoderequired" />
</FEEDBACK>
</UNICODE>
<DATABASE level="required">
<VENDOR name="mariadb" version="5.5.31" />
<VENDOR name="mysql" version="5.6" />
<VENDOR name="postgres" version="9.4" />
<VENDOR name="mssql" version="10.0" />
<VENDOR name="oracle" version="11.2" />
</DATABASE>
<PHP version="7.0.0" level="required">
</PHP>
<PCREUNICODE level="optional">
<FEEDBACK>
<ON_CHECK message="pcreunicodewarning" />
</FEEDBACK>
</PCREUNICODE>
<PHP_EXTENSIONS>
<PHP_EXTENSION name="iconv" level="required">
<FEEDBACK>
<ON_ERROR message="iconvrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="mbstring" level="optional">
<FEEDBACK>
<ON_CHECK message="mbstringrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="curl" level="required">
<FEEDBACK>
<ON_ERROR message="curlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="openssl" level="required">
<FEEDBACK>
<ON_ERROR message="opensslrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="tokenizer" level="optional">
<FEEDBACK>
<ON_CHECK message="tokenizerrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlrpc" level="optional">
<FEEDBACK>
<ON_CHECK message="xmlrpcrecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="soap" level="optional">
<FEEDBACK>
<ON_CHECK message="soaprecommended" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="ctype" level="required">
<FEEDBACK>
<ON_ERROR message="ctyperequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zip" level="required">
<FEEDBACK>
<ON_ERROR message="ziprequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="zlib" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="gd" level="required">
<FEEDBACK>
<ON_ERROR message="gdrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="simplexml" level="required">
<FEEDBACK>
<ON_ERROR message="simplexmlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="spl" level="required">
<FEEDBACK>
<ON_ERROR message="splrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="pcre" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="dom" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xml" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="xmlreader" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="intl" level="required">
<FEEDBACK>
<ON_ERROR message="intlrequired" />
</FEEDBACK>
</PHP_EXTENSION>
<PHP_EXTENSION name="json" level="required">
</PHP_EXTENSION>
<PHP_EXTENSION name="hash" level="required"/>
<PHP_EXTENSION name="fileinfo" level="required"/>
</PHP_EXTENSIONS>
<PHP_SETTINGS>
<PHP_SETTING name="memory_limit" value="96M" level="required">
<FEEDBACK>
<ON_ERROR message="settingmemorylimit" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="file_uploads" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="settingfileuploads" />
</FEEDBACK>
</PHP_SETTING>
<PHP_SETTING name="opcache.enable" value="1" level="optional">
<FEEDBACK>
<ON_CHECK message="opcacherecommended" />
</FEEDBACK>
</PHP_SETTING>
</PHP_SETTINGS>
<CUSTOM_CHECKS>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbstorageengine" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
<FEEDBACK>
<ON_ERROR message="quizattemptsupgradedmessage" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
<FEEDBACK>
<ON_CHECK message="slashargumentswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
<FEEDBACK>
<ON_CHECK message="unsupporteddbtablerowformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
<FEEDBACK>
<ON_CHECK message="unoconvwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
<FEEDBACK>
<ON_CHECK message="libcurlwarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfileformat" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddbfilepertable" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
<FEEDBACK>
<ON_ERROR message="unsupporteddblargeprefix" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
<FEEDBACK>
<ON_CHECK message="ishttpswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
<FEEDBACK>
<ON_CHECK message="incompleteunicodesupport" />
</FEEDBACK>
</CUSTOM_CHECK>
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
<FEEDBACK>
<ON_CHECK message="sixtyfourbitswarning" />
</FEEDBACK>
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
</COMPATIBILITY_MATRIX>
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/str","core/ajax","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g){var h={CATEGORY_LINK:'[data-add-element="category"]'},i=function(a){this.contextId=a;var c=[{key:"addcategory",component:"tool_dataprivacy"},{key:"save",component:"admin"}];this.strings=b.get_strings(c),this.registerEventListeners()};return i.prototype.contextId=0,i.prototype.strings=0,i.prototype.registerEventListeners=function(){var b=a(h.CATEGORY_LINK);b.on("click",function(){return this.strings.then(function(a){e.create({type:e.types.SAVE_CANCEL,title:a[0],body:""},b).done(function(b){this.setupFormModal(b,a[1])}.bind(this))}.bind(this)).fail(d.exception)}.bind(this))},i.prototype.getBody=function(a){var b=null;return"undefined"!=typeof a&&(b={jsonformdata:JSON.stringify(a)}),g.loadFragment("tool_dataprivacy","addcategory_form",this.contextId,b)},i.prototype.setupFormModal=function(a,b){a.setLarge(),a.setSaveButtonText(b),a.getRoot().on(f.hidden,this.destroy.bind(this)),a.setBody(this.getBody()),a.getRoot().on(f.save,this.submitForm.bind(this)),a.getRoot().on("submit","form",this.submitFormAjax.bind(this)),this.modal=a,a.show()},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.submitFormAjax=function(a){a.preventDefault();var b=this.modal.getRoot().find("form").serialize();c.call([{methodname:"tool_dataprivacy_create_category_form",args:{jsonformdata:JSON.stringify(b)},done:function(a){a.validationerrors?this.modal.setBody(this.getBody(b)):this.close()}.bind(this),fail:d.exception}])},i.prototype.close=function(){this.destroy(),document.location.reload()},i.prototype.destroy=function(){Y.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),this.modal.destroy()},i.prototype.removeListeners=function(){a(h.CATEGORY_LINK).off("click")},{getInstance:function(a){return new i(a)}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/str","core/ajax","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g){var h={PURPOSE_LINK:'[data-add-element="purpose"]'},i=function(a){this.contextId=a;var c=[{key:"addpurpose",component:"tool_dataprivacy"},{key:"save",component:"admin"}];this.strings=b.get_strings(c),this.registerEventListeners()};return i.prototype.contextId=0,i.prototype.strings=0,i.prototype.registerEventListeners=function(){var b=a(h.PURPOSE_LINK);b.on("click",function(){return this.strings.then(function(a){e.create({type:e.types.SAVE_CANCEL,title:a[0],body:""},b).done(function(b){this.setupFormModal(b,a[1])}.bind(this))}.bind(this)).fail(d.exception)}.bind(this))},i.prototype.getBody=function(a){var b=null;return"undefined"!=typeof a&&(b={jsonformdata:JSON.stringify(a)}),g.loadFragment("tool_dataprivacy","addpurpose_form",this.contextId,b)},i.prototype.setupFormModal=function(a,b){a.setLarge(),a.setSaveButtonText(b),a.getRoot().on(f.hidden,this.destroy.bind(this)),a.setBody(this.getBody()),a.getRoot().on(f.save,this.submitForm.bind(this)),a.getRoot().on("submit","form",this.submitFormAjax.bind(this)),this.modal=a,a.show()},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.submitFormAjax=function(a){a.preventDefault();var b=this.modal.getRoot().find("form").serialize();c.call([{methodname:"tool_dataprivacy_create_purpose_form",args:{jsonformdata:JSON.stringify(b)},done:function(a){a.validationerrors?this.modal.setBody(this.getBody(b)):this.close()}.bind(this),fail:d.exception}])},i.prototype.close=function(){this.destroy(),document.location.reload()},i.prototype.destroy=function(){Y.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),this.modal.destroy()},i.prototype.removeListeners=function(){a(h.PURPOSE_LINK).off("click")},{getInstance:function(a){return new i(a)}}});
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletecategory"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletecategory",component:"tool_dataprivacy"},{key:"deletecategorytext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_category",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-categoryid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){function g(a){var g=[{key:"confirm",component:"moodle"},{key:"confirmcontextdeletion",component:"tool_dataprivacy"}],h="tool_dataprivacy_confirm_contexts_for_deletion",i="";d.get_strings(g).then(function(a){i=a[0];var b=a[1];return e.create({title:i,body:b,type:e.types.SAVE_CANCEL})}).then(function(d){return d.setSaveButtonText(i),d.getRoot().on(f.save,function(){var d={ids:a},e={methodname:h,args:d};b.call([e])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d}).done(function(a){a.show()}).fail(c.exception)}var h={MARK_FOR_DELETION:'[data-action="markfordeletion"]',SELECT_ALL:'[data-action="selectall"]'},i={SELECTCONTEXT:".selectcontext"},j=function(){this.registerEvents()};return j.prototype.registerEvents=function(){a(h.MARK_FOR_DELETION).click(function(b){b.preventDefault();var c=[];a(i.SELECTCONTEXT).each(function(){var b=a(this);b.is(":checked")&&c.push(b.val())}),g(c)}),a(h.SELECT_ALL).change(function(b){b.preventDefault();var c=a(this);c.is(":checked")?a(i.SELECTCONTEXT).attr("checked","checked"):a(i.SELECTCONTEXT).removeAttr("checked")})},j});
+1
View File
@@ -0,0 +1 @@
define(["jquery","core/str","core/ajax","core/notification","core/templates","core/modal_factory","core/modal_events","core/fragment","tool_dataprivacy/add_purpose","tool_dataprivacy/add_category"],function(a,b,c,d,e,f,g,h,i,j){var k={TREE_NODES:"[data-context-tree-node=1]",FORM_CONTAINER:"#context-form-container"},l=function(a,b,c){this.systemContextId=a,this.currentContextLevel=b,this.currentContextId=c,this.init()};return l.prototype.systemContextId=0,l.prototype.currentContextLevel=0,l.prototype.currentContextId=0,l.prototype.addpurpose=null,l.prototype.addcategory=null,l.prototype.init=function(){this.addpurpose=i.getInstance(this.systemContextId),this.addcategory=j.getInstance(this.systemContextId);var a=[{key:"changessaved",component:"moodle"},{key:"contextpurposecategorysaved",component:"tool_dataprivacy"},{key:"noblockstoload",component:"tool_dataprivacy"},{key:"noactivitiestoload",component:"tool_dataprivacy"},{key:"nocoursestoload",component:"tool_dataprivacy"}];this.strings=b.get_strings(a),this.registerEventListeners(),this.currentContextId?this.loadForm("context_form",[this.currentContextId],this.submitContextFormAjax.bind(this)):this.loadForm("contextlevel_form",[this.currentContextLevel],this.submitContextLevelFormAjax.bind(this))},l.prototype.registerEventListeners=function(){a(k.TREE_NODES).on("click",function(b){b.preventDefault();var c=a(b.currentTarget);a(k.TREE_NODES).removeClass("active"),c.addClass("active");var d=c.data("contextlevel"),e=c.data("contextid");if(d)window.history.pushState({},null,"?contextlevel="+d),this.addpurpose.removeListeners(),this.addcategory.removeListeners(),this.currentContextLevel=d,this.loadForm("contextlevel_form",[this.currentContextLevel],this.submitContextLevelFormAjax.bind(this));else if(e)window.history.pushState({},null,"?contextid="+e),this.addpurpose.removeListeners(),this.addcategory.removeListeners(),this.currentContextId=e,this.loadForm("context_form",[this.currentContextId],this.submitContextFormAjax.bind(this));else{var f=c.data("expandcontextid"),g=c.data("expandelement"),h=c.data("expanded");g&&(h?this.collapse(c):!c.data("loaded")&&f&&g?(c.find("> i").removeClass("fa-plus"),c.find("> i").addClass("fa-circle-o-notch fa-spin"),this.loadExtra(c,f,g)):this.expand(c))}}.bind(this))},l.prototype.removeListeners=function(){a(k.TREE_NODES).off("click")},l.prototype.loadForm=function(b,c,f){this.clearForm();var g=h.loadFragment("tool_dataprivacy",b,this.systemContextId,c);g.done(function(b,c){a(k.FORM_CONTAINER).html(b),e.runTemplateJS(c),this.addpurpose.registerEventListeners(),this.addcategory.registerEventListeners(),a(k.FORM_CONTAINER).on("submit","form",f)}.bind(this)).fail(d.exception)},l.prototype.clearForm=function(){Y.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),a(k.FORM_CONTAINER).off("submit","form")},l.prototype.submitForm=function(b){b.preventDefault(),a(k.FORM_CONTAINER).find("form").submit()},l.prototype.submitContextLevelFormAjax=function(a){this.submitFormAjax(a,"tool_dataprivacy_set_contextlevel_form")},l.prototype.submitContextFormAjax=function(a){this.submitFormAjax(a,"tool_dataprivacy_set_context_form")},l.prototype.submitFormAjax=function(b,e){b.preventDefault();var f=a(k.FORM_CONTAINER).find("form").serialize();return this.strings.then(function(a){c.call([{methodname:e,args:{jsonformdata:JSON.stringify(f)},done:function(){d.alert(a[0],a[1])},fail:d.exception}])})["catch"](d.exception)},l.prototype.loadExtra=function(a,b,f){c.call([{methodname:"tool_dataprivacy_tree_extra_branches",args:{contextid:b,element:f},done:function(b){return 0==b.branches.length?void this.noElements(a,f):void e.render("tool_dataprivacy/context_tree_branches",b).then(function(b){a.after(b),this.removeListeners(),this.registerEventListeners(),this.expand(a),a.data("loaded",1)}.bind(this)).fail(d.exception)}.bind(this),fail:d.exception}])},l.prototype.noElements=function(a,b){a.data("expandcontextid",""),a.data("expandelement",""),this.strings.then(function(c){var d=2;"module"==b?d=3:"course"==b&&(d=4),a.text(c[d])}).fail(d.exception)},l.prototype.collapse=function(a){a.data("expanded",0),a.siblings("nav").addClass("hidden"),a.find("> i").removeClass("fa-minus"),a.find("> i").addClass("fa-plus")},l.prototype.expand=function(a){a.data("expanded",1),a.siblings("nav").removeClass("hidden"),a.find("> i").removeClass("fa-plus"),a.find("> i").removeClass("fa-circle-o-notch fa-spin"),a.find("> i").addClass("fa-minus")},{init:function(a,b,c){return new l(a,b,c)}}});
@@ -0,0 +1 @@
define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]',COMPLETE_BUTTON:'[data-action="complete"]'},i=function(a){d.call(this,a)};return i.TYPE="tool_dataprivacy-data_request",i.TYPE_ENQUIRY="tool_dataprivacy-complete_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.COMPLETE_BUTTON,function(b,c){var d=a.Event(f.complete);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),e.register(i.TYPE_ENQUIRY,i,"tool_dataprivacy/mark_request_complete_modal"),g=!0),i});
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){function h(b,d,h,j,k,l,m,n){null!==h&&l.forEach(function(a){a.id===h&&(a.selected=!0)}),null!==j&&m.forEach(function(a){a.id===j&&(a.selected=!0)});var o={contextlevel:d,categoryoptions:l,purposeoptions:m};null!==n&&n.length&&(null===k?o.newactivitydefaults=!0:n.forEach(function(a){k===a.name&&(a.selected=!0)}),o.modemodule=!0,o.activityoptions=n),e.create({title:b,body:g.render("tool_dataprivacy/category_purpose_form",o),type:e.types.SAVE_CANCEL,large:!0}).then(function(b){return b.getRoot().on(f.save,function(){var b=a("#activity"),c="undefined"!=typeof b?b.val():null,d=a("#override"),e="undefined"!=typeof d&&d.is(":checked");i(a("#contextlevel").val(),a("#category").val(),a("#purpose").val(),c,e)}),b.getRoot().on(f.hidden,function(){b.destroy()}),b.show(),b})["catch"](c.exception)}function i(a,c,d,e,f){var g={methodname:"tool_dataprivacy_set_context_defaults",args:{contextlevel:a,category:c,purpose:d,override:f,activity:e}};b.call([g])[0].done(function(a){a.result&&window.location.reload()})}var j={EDIT_LEVEL_DEFAULTS:'[data-action="edit-level-defaults"]',NEW_ACTIVITY_DEFAULTS:'[data-action="new-activity-defaults"]',EDIT_ACTIVITY_DEFAULTS:'[data-action="edit-activity-defaults"]',DELETE_ACTIVITY_DEFAULTS:'[data-action="delete-activity-defaults"]'},k=-1,l=function(){this.registerEvents()};return l.prototype.registerEvents=function(){a(j.EDIT_LEVEL_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=f.data("category"),j=f.data("purpose"),k=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}}],l=b.call(k),m=d.get_string("editdefaults","tool_dataprivacy",a("#defaults-header").text());a.when(l[0],l[1],m).then(function(a,b,c){var d=a.options,e=b.options;return h(c,g,i,j,null,d,e,null),!0})["catch"](c.exception)}),a(j.NEW_ACTIVITY_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}},{methodname:"tool_dataprivacy_get_activity_options",args:{nodefaults:!0}}],j=b.call(i),k=d.get_string("addnewdefaults","tool_dataprivacy");a.when(j[0],j[1],j[2],k).then(function(a,b,c,d){var e=a.options,f=b.options,i=c.options;return h(d,g,null,null,null,e,f,i),!0})["catch"](c.exception)}),a(j.EDIT_ACTIVITY_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=f.data("category"),j=f.data("purpose"),k=f.data("activityname"),l=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}},{methodname:"tool_dataprivacy_get_activity_options",args:{}}],m=b.call(l),n=d.get_string("editmoduledefaults","tool_dataprivacy");a.when(m[0],m[1],m[2],n).then(function(a,b,c,d){var e=a.options,f=b.options,l=c.options;return h(d,g,i,j,k,e,f,l),!0})["catch"](c.exception)}),a(j.DELETE_ACTIVITY_DEFAULTS).click(function(b){b.preventDefault();var h=a(this),j=h.data("contextlevel"),l=h.data("activityname"),m=h.data("activitydisplayname"),n=k,o=k;e.create({title:d.get_string("deletedefaults","tool_dataprivacy",m),body:g.render("tool_dataprivacy/delete_activity_defaults",{activityname:m}),type:e.types.SAVE_CANCEL,large:!0}).then(function(a){return a.setSaveButtonText(d.get_string("delete")),a.getRoot().on(f.save,function(){i(j,n,o,l,!1)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show(),!0})["catch"](c.exception)})},{init:function(){return new l}}});
@@ -0,0 +1 @@
define(["jquery"],function(a){var b={PURPOSE_SELECT:"#id_purposeid",RETENTION_FIELD_BOOST:"#id_error_retention_current",RETENTION_FIELD_CLEAN:"#fitem_id_retention_current [data-fieldtype=static]"},c=function(a){this.purposeRetentionPeriods=a,this.registerEventListeners()},d=function(){a(b.PURPOSE_SELECT).off("change")};return c.prototype.purposeRetentionPeriods=[],c.prototype.registerEventListeners=function(){a(b.PURPOSE_SELECT).on("change",function(c){var d=a(c.currentTarget).val(),e=this.purposeRetentionPeriods[d],f=a(b.RETENTION_FIELD_CLEAN);if(f.length>0)f.text(e);else{var g=a(b.RETENTION_FIELD_BOOST),h=g.siblings();h.length>0&&h.text(e)}}.bind(this))},{init:function(a){return d(),new c(a)}}});
+1
View File
@@ -0,0 +1 @@
define([],function(){return{approve:"tool_dataprivacy-data_request:approve",bulkApprove:"tool_dataprivacy-data_request:bulk_approve",deny:"tool_dataprivacy-data_request:deny",bulkDeny:"tool_dataprivacy-data_request:bulk_deny",complete:"tool_dataprivacy-data_request:complete"}});
@@ -0,0 +1 @@
define(["jquery","core/url","core/str"],function(a,b,c){var d=a('<img alt="" src="'+b.imageUrl("t/expanded")+'"/>'),e=a('<img alt="" src="'+b.imageUrl("t/collapsed")+'"/>');return{expandCollapse:function(a,b){a.hasClass("hide")?(a.removeClass("hide"),a.addClass("visible"),a.attr("aria-expanded",!0),b.find(":header i.fa").removeClass("fa-plus-square"),b.find(":header i.fa").addClass("fa-minus-square"),b.find(":header img.icon").attr("src",d.attr("src"))):(a.removeClass("visible"),a.addClass("hide"),a.attr("aria-expanded",!1),b.find(":header i.fa").removeClass("fa-minus-square"),b.find(":header i.fa").addClass("fa-plus-square"),b.find(":header img.icon").attr("src",e.attr("src")))},expandCollapseAll:function(b){var f="visible"==b?"hide":"visible",g="visible"==b,h="visible"==b?"fa-plus-square":"fa-minus-square",i="visible"==b?"fa-minus-square":"fa-plus-square",j="visible"==b?d.attr("src"):e.attr("src");a("."+f).each(function(){a(this).removeClass(f),a(this).addClass(b),a(this).attr("aria-expanded",g)}),a(".tool_dataprivacy-expand-all").data("visibilityState",f),c.get_string(f,"tool_dataprivacy").then(function(b){a(".tool_dataprivacy-expand-all").html(b)})["catch"](Notification.exception),a(":header i.fa").each(function(){a(this).removeClass(h),a(this).addClass(i)}),a(":header img.icon").each(function(){a(this).attr("src",j)})}}});
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/templates"],function(a,b,c){return{processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.id,label:b._label})}),d},transport:function(d,e,f,g){var h;h=b.call([{methodname:"tool_dataprivacy_get_users",args:{query:e}}]),h[0].then(function(b){var d=[],e=0;return a.each(b,function(a,b){d.push(c.render("tool_dataprivacy/form-user-selector-suggestion",b))}),a.when.apply(a.when,d).then(function(){var c=arguments;a.each(b,function(a,b){b._label=c[e],e++}),f(b)})}).fail(g)}}});
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){function h(a){var e={methodname:"tool_dataprivacy_contact_dpo",args:{message:a}},f="success";b.call([e])[0].then(function(a){return a.result?d.get_string("requestsubmitted","tool_dataprivacy"):(f="error",a.warnings.join("<br>"))}).done(function(a){c.addNotification({message:a,type:f})}).fail(c.exception)}var i={CANCEL_REQUEST:'[data-action="cancel"]',CONTACT_DPO:'[data-action="contactdpo"]'},j=function(){this.registerEvents()};return j.prototype.registerEvents=function(){a(i.CANCEL_REQUEST).click(function(g){g.preventDefault();var h=a(this).data("requestid"),i=[{key:"cancelrequest",component:"tool_dataprivacy"},{key:"cancelrequestconfirmation",component:"tool_dataprivacy"}];d.get_strings(i).then(function(a){var d=a[0],g=a[1];return e.create({title:d,body:g,type:e.types.SAVE_CANCEL}).then(function(a){return a.setSaveButtonText(d),a.getRoot().on(f.save,function(){var a={requestid:h},d={methodname:"tool_dataprivacy_cancel_data_request",args:a};b.call([d])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(i.CONTACT_DPO).click(function(b){b.preventDefault();var i=a(this).data("replytoemail"),j=[{key:"contactdataprotectionofficer",component:"tool_dataprivacy"},{key:"send",component:"tool_dataprivacy"}],k="";d.get_strings(j).then(function(a){var b=a[0];k=a[1];var c={replytoemail:i};return e.create({title:b,body:g.render("tool_dataprivacy/contact_dpo",c),type:e.types.SAVE_CANCEL,large:!0})}).done(function(b){b.setSaveButtonText(k),b.getRoot().on(f.save,function(b){var c=a("#message").val().trim();0===c.length?(b.preventDefault(),a('[data-region="messageinput"]').addClass("has-danger notifyproblem"),a("#id_error_message").removeAttr("hidden")):h(c)}),b.getRoot().on(f.hidden,function(){b.destroy()}),b.show()}).fail(c.exception)})},{init:function(){return new j}}});
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletepurpose"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletepurpose",component:"tool_dataprivacy"},{key:"deletepurposetext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_purpose",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-purposeid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
@@ -0,0 +1 @@
define(["jquery","core/form-autocomplete","core/str","core/notification"],function(a,b,c,d){var e={REQUEST_FILTERS:"#request-filters"},f=function(){var f=[{key:"filter",component:"moodle"},{key:"nofiltersapplied",component:"tool_dataprivacy"}];c.get_strings(f).then(function(a){var c=a[0],d=a[1];return b.enhance(e.REQUEST_FILTERS,!1,"",c,!1,!0,d,!0)}).fail(d.exception);var g=a(e.REQUEST_FILTERS).val();a(e.REQUEST_FILTERS).on("change",function(){var b=a(this).val();g.join(",")!==b.join(",")&&(0===b.length&&a("#filters-cleared").val(1),a(this.form).submit())})};return{init:function(){f()}}});
@@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a){return{wsfunction:"tool_dataprivacy_approve_data_request",wsparams:{requestid:a}}}function k(a){return{wsfunction:"tool_dataprivacy_bulk_approve_data_requests",wsparams:{requestids:a}}}function l(a){return{wsfunction:"tool_dataprivacy_deny_data_request",wsparams:{requestid:a}}}function m(a){return{wsfunction:"tool_dataprivacy_bulk_deny_data_requests",wsparams:{requestids:a}}}function n(a){return{wsfunction:"tool_dataprivacy_mark_complete",wsparams:{requestid:a}}}function o(a,b){var g=[];switch(a){case i.approve:g=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}];break;case i.bulkApprove:g=[{key:"bulkapproverequests",component:"tool_dataprivacy"},{key:"confirmbulkapproval",component:"tool_dataprivacy"}];break;case i.deny:g=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}];break;case i.bulkDeny:g=[{key:"bulkdenyrequests",component:"tool_dataprivacy"},{key:"confirmbulkdenial",component:"tool_dataprivacy"}];break;case i.complete:g=[{key:"markcomplete",component:"tool_dataprivacy"},{key:"confirmcompletion",component:"tool_dataprivacy"}]}var h="";d.get_strings(g).then(function(a){h=a[0];var b=a[1];return e.create({title:h,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){a.setSaveButtonText(h),a.getRoot().on(f.save,function(){p(b.wsfunction,b.wsparams)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}function p(a,d){var e={methodname:a,args:d};b.call([e])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}var q={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]',MARK_COMPLETE:'[data-action="complete"]',CHANGE_BULK_ACTION:'[id="bulk-action"]',CONFIRM_BULK_ACTION:'[id="confirm-bulk-action"]',SELECT_ALL:'[data-action="selectall"]'},r={APPROVE:1,DENY:2},s={SELECT_REQUEST:".selectrequests"},t=function(){this.registerEvents()};return t.prototype.registerEvents=function(){a(q.VIEW_REQUEST).click(function(d){d.preventDefault();var k=a(this).data("requestid"),m={requestid:k},n={methodname:"tool_dataprivacy_get_data_request",args:m},q=b.call([n]);a.when(q[0]).then(function(a){return a.result?a.result:(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){var b=e.types.DEFAULT;a.approvedeny?b=h.TYPE:a.canmarkcomplete&&(b=h.TYPE_ENQUIRY);var c=g.render("tool_dataprivacy/request_details",a);return e.create({title:a.typename,body:c,type:b,large:!0})}).then(function(a){a.getRoot().on(i.approve,function(){o(i.approve,j(k))}),a.getRoot().on(i.deny,function(){o(i.deny,l(k))}),a.getRoot().on(i.complete,function(){var a={requestid:k};p("tool_dataprivacy_mark_complete",a)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}),a(q.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");o(i.approve,j(c))}),a(q.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");o(i.deny,l(c))}),a(q.MARK_COMPLETE).click(function(b){b.preventDefault();var c=a(this).data("requestid");o(i.complete,n(c))}),a(q.CONFIRM_BULK_ACTION).click(function(){var b=[],e="",f={},g=[{key:"selectbulkaction",component:"tool_dataprivacy"},{key:"selectdatarequests",component:"tool_dataprivacy"},{key:"ok"}],h=parseInt(a("#bulk-action").val());if(h!=r.APPROVE&&h!=r.DENY)return void d.get_strings(g).done(function(a){c.alert("",a[0],a[2])}).fail(c.exception);if(a(".selectrequests:checked").each(function(){b.push(a(this).val())}),b.length<1)return void d.get_strings(g).done(function(a){c.alert("",a[1],a[2])}).fail(c.exception);switch(h){case r.APPROVE:e=i.bulkApprove,f=k(b);break;case r.DENY:e=i.bulkDeny,f=m(b)}o(e,f)}),a(q.SELECT_ALL).change(function(b){b.preventDefault();var c=a(this).is(":checked");a(s.SELECT_REQUEST).prop("checked",c)})},t});
@@ -0,0 +1,172 @@
// 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/>.
/**
* Module to add categories.
*
* @module tool_dataprivacy/add_category
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/modal_factory', 'core/modal_events', 'core/fragment'],
function($, Str, Ajax, Notification, ModalFactory, ModalEvents, Fragment) {
var SELECTORS = {
CATEGORY_LINK: '[data-add-element="category"]',
};
var AddCategory = function(contextId) {
this.contextId = contextId;
var stringKeys = [
{
key: 'addcategory',
component: 'tool_dataprivacy'
},
{
key: 'save',
component: 'admin'
}
];
this.strings = Str.get_strings(stringKeys);
this.registerEventListeners();
};
/**
* @var {int} contextId
* @private
*/
AddCategory.prototype.contextId = 0;
/**
* @var {Promise}
* @private
*/
AddCategory.prototype.strings = 0;
AddCategory.prototype.registerEventListeners = function() {
var trigger = $(SELECTORS.CATEGORY_LINK);
trigger.on('click', function() {
return this.strings.then(function(strings) {
ModalFactory.create({
type: ModalFactory.types.SAVE_CANCEL,
title: strings[0],
body: '',
}, trigger).done(function(modal) {
this.setupFormModal(modal, strings[1]);
}.bind(this));
}.bind(this))
.fail(Notification.exception);
}.bind(this));
};
/**
* @method getBody
* @param {Object} formdata
* @private
* @return {Promise}
*/
AddCategory.prototype.getBody = function(formdata) {
var params = null;
if (typeof formdata !== "undefined") {
params = {jsonformdata: JSON.stringify(formdata)};
}
// Get the content of the modal.
return Fragment.loadFragment('tool_dataprivacy', 'addcategory_form', this.contextId, params);
};
AddCategory.prototype.setupFormModal = function(modal, saveText) {
modal.setLarge();
modal.setSaveButtonText(saveText);
// We want to reset the form every time it is opened.
modal.getRoot().on(ModalEvents.hidden, this.destroy.bind(this));
modal.setBody(this.getBody());
// We catch the modal save event, and use it to submit the form inside the modal.
// Triggering a form submission will give JS validation scripts a chance to check for errors.
modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
// We also catch the form submit event and use it to submit the form with ajax.
modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
this.modal = modal;
modal.show();
};
/**
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
*
* @method submitForm
* @param {Event} e Form submission event.
* @private
*/
AddCategory.prototype.submitForm = function(e) {
e.preventDefault();
this.modal.getRoot().find('form').submit();
};
AddCategory.prototype.submitFormAjax = function(e) {
// We don't want to do a real form submission.
e.preventDefault();
// Convert all the form elements values to a serialised string.
var formData = this.modal.getRoot().find('form').serialize();
Ajax.call([{
methodname: 'tool_dataprivacy_create_category_form',
args: {jsonformdata: JSON.stringify(formData)},
done: function(data) {
if (data.validationerrors) {
this.modal.setBody(this.getBody(formData));
} else {
this.close();
}
}.bind(this),
fail: Notification.exception
}]);
};
AddCategory.prototype.close = function() {
this.destroy();
document.location.reload();
};
AddCategory.prototype.destroy = function() {
Y.use('moodle-core-formchangechecker', function() {
M.core_formchangechecker.reset_form_dirty_state();
});
this.modal.destroy();
};
AddCategory.prototype.removeListeners = function() {
$(SELECTORS.CATEGORY_LINK).off('click');
};
return /** @alias module:tool_dataprivacy/add_category */ {
getInstance: function(contextId) {
return new AddCategory(contextId);
}
};
}
);
@@ -0,0 +1,173 @@
// 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/>.
/**
* Module to add purposes.
*
* @module tool_dataprivacy/add_purpose
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/modal_factory', 'core/modal_events', 'core/fragment'],
function($, Str, Ajax, Notification, ModalFactory, ModalEvents, Fragment) {
var SELECTORS = {
PURPOSE_LINK: '[data-add-element="purpose"]',
};
var AddPurpose = function(contextId) {
this.contextId = contextId;
var stringKeys = [
{
key: 'addpurpose',
component: 'tool_dataprivacy'
},
{
key: 'save',
component: 'admin'
}
];
this.strings = Str.get_strings(stringKeys);
this.registerEventListeners();
};
/**
* @var {int} contextId
* @private
*/
AddPurpose.prototype.contextId = 0;
/**
* @var {Promise}
* @private
*/
AddPurpose.prototype.strings = 0;
AddPurpose.prototype.registerEventListeners = function() {
var trigger = $(SELECTORS.PURPOSE_LINK);
trigger.on('click', function() {
return this.strings.then(function(strings) {
ModalFactory.create({
type: ModalFactory.types.SAVE_CANCEL,
title: strings[0],
body: '',
}, trigger).done(function(modal) {
this.setupFormModal(modal, strings[1]);
}.bind(this));
}.bind(this))
.fail(Notification.exception);
}.bind(this));
};
/**
* @method getBody
* @param {Object} formdata
* @private
* @return {Promise}
*/
AddPurpose.prototype.getBody = function(formdata) {
var params = null;
if (typeof formdata !== "undefined") {
params = {jsonformdata: JSON.stringify(formdata)};
}
// Get the content of the modal.
return Fragment.loadFragment('tool_dataprivacy', 'addpurpose_form', this.contextId, params);
};
AddPurpose.prototype.setupFormModal = function(modal, saveText) {
modal.setLarge();
modal.setSaveButtonText(saveText);
// We want to reset the form every time it is opened.
modal.getRoot().on(ModalEvents.hidden, this.destroy.bind(this));
modal.setBody(this.getBody());
// We catch the modal save event, and use it to submit the form inside the modal.
// Triggering a form submission will give JS validation scripts a chance to check for errors.
modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
// We also catch the form submit event and use it to submit the form with ajax.
modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
this.modal = modal;
modal.show();
};
/**
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
*
* @method submitForm
* @param {Event} e Form submission event.
* @private
*/
AddPurpose.prototype.submitForm = function(e) {
e.preventDefault();
this.modal.getRoot().find('form').submit();
};
AddPurpose.prototype.submitFormAjax = function(e) {
// We don't want to do a real form submission.
e.preventDefault();
// Convert all the form elements values to a serialised string.
var formData = this.modal.getRoot().find('form').serialize();
Ajax.call([{
methodname: 'tool_dataprivacy_create_purpose_form',
args: {jsonformdata: JSON.stringify(formData)},
done: function(data) {
if (data.validationerrors) {
this.modal.setBody(this.getBody(formData));
} else {
this.close();
}
}.bind(this),
fail: Notification.exception
}]);
};
AddPurpose.prototype.close = function() {
this.destroy();
document.location.reload();
};
AddPurpose.prototype.destroy = function() {
Y.use('moodle-core-formchangechecker', function() {
M.core_formchangechecker.reset_form_dirty_state();
});
this.modal.destroy();
};
AddPurpose.prototype.removeListeners = function() {
$(SELECTORS.PURPOSE_LINK).off('click');
};
return /** @alias module:tool_dataprivacy/add_purpose */ {
getInstance: function(contextId) {
return new AddPurpose(contextId);
}
};
}
);
@@ -0,0 +1,132 @@
// 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/>.
/**
* AMD module for categories actions.
*
* @module tool_dataprivacy/categoriesactions
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/notification',
'core/str',
'core/modal_factory',
'core/modal_events'],
function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
/**
* List of action selectors.
*
* @type {{DELETE: string}}
*/
var ACTIONS = {
DELETE: '[data-action="deletecategory"]',
};
/**
* CategoriesActions class.
*/
var CategoriesActions = function() {
this.registerEvents();
};
/**
* Register event listeners.
*/
CategoriesActions.prototype.registerEvents = function() {
$(ACTIONS.DELETE).click(function(e) {
e.preventDefault();
var id = $(this).data('id');
var categoryname = $(this).data('name');
var stringkeys = [
{
key: 'deletecategory',
component: 'tool_dataprivacy'
},
{
key: 'deletecategorytext',
component: 'tool_dataprivacy',
param: categoryname
},
{
key: 'delete'
}
];
Str.get_strings(stringkeys).then(function(langStrings) {
var title = langStrings[0];
var confirmMessage = langStrings[1];
var buttonText = langStrings[2];
return ModalFactory.create({
title: title,
body: confirmMessage,
type: ModalFactory.types.SAVE_CANCEL
}).then(function(modal) {
modal.setSaveButtonText(buttonText);
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
var request = {
methodname: 'tool_dataprivacy_delete_category',
args: {'id': id}
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
$('tr[data-categoryid="' + id + '"]').remove();
} else {
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).fail(Notification.exception);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
return modal;
});
}).done(function(modal) {
modal.show();
}).fail(Notification.exception);
});
};
return /** @alias module:tool_dataprivacy/categoriesactions */ {
// Public variables and functions.
/**
* Initialise the module.
*
* @method init
* @return {CategoriesActions}
*/
'init': function() {
return new CategoriesActions();
}
};
});
@@ -0,0 +1,156 @@
// 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/>.
/**
* Request actions.
*
* @module tool_dataprivacy/data_deletion
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/notification',
'core/str',
'core/modal_factory',
'core/modal_events'],
function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
/**
* List of action selectors.
*
* @type {{MARK_FOR_DELETION: string}}
* @type {{SELECT_ALL: string}}
*/
var ACTIONS = {
MARK_FOR_DELETION: '[data-action="markfordeletion"]',
SELECT_ALL: '[data-action="selectall"]',
};
/**
* List of selectors.
*
* @type {{SELECTCONTEXT: string}}
*/
var SELECTORS = {
SELECTCONTEXT: '.selectcontext',
};
/**
* DataDeletionActions class.
*/
var DataDeletionActions = function() {
this.registerEvents();
};
/**
* Register event listeners.
*/
DataDeletionActions.prototype.registerEvents = function() {
$(ACTIONS.MARK_FOR_DELETION).click(function(e) {
e.preventDefault();
var selectedIds = [];
$(SELECTORS.SELECTCONTEXT).each(function() {
var checkbox = $(this);
if (checkbox.is(':checked')) {
selectedIds.push(checkbox.val());
}
});
showConfirmation(selectedIds);
});
$(ACTIONS.SELECT_ALL).change(function(e) {
e.preventDefault();
var selectallnone = $(this);
if (selectallnone.is(':checked')) {
$(SELECTORS.SELECTCONTEXT).attr('checked', 'checked');
} else {
$(SELECTORS.SELECTCONTEXT).removeAttr('checked');
}
});
};
/**
* Show the confirmation dialogue.
*
* @param {Array} ids The array of expired context record IDs.
*/
function showConfirmation(ids) {
var keys = [
{
key: 'confirm',
component: 'moodle'
},
{
key: 'confirmcontextdeletion',
component: 'tool_dataprivacy'
}
];
var wsfunction = 'tool_dataprivacy_confirm_contexts_for_deletion';
var modalTitle = '';
Str.get_strings(keys).then(function(langStrings) {
modalTitle = langStrings[0];
var confirmMessage = langStrings[1];
return ModalFactory.create({
title: modalTitle,
body: confirmMessage,
type: ModalFactory.types.SAVE_CANCEL
});
}).then(function(modal) {
modal.setSaveButtonText(modalTitle);
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
// Confirm the request.
var params = {
'ids': ids
};
var request = {
methodname: wsfunction,
args: params
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
window.location.reload();
} else {
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).fail(Notification.exception);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
return modal;
}).done(function(modal) {
modal.show();
}).fail(Notification.exception);
}
return DataDeletionActions;
});
@@ -0,0 +1,318 @@
// 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/>.
/**
* Request actions.
*
* @module tool_dataprivacy/data_registry
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/templates', 'core/modal_factory',
'core/modal_events', 'core/fragment', 'tool_dataprivacy/add_purpose', 'tool_dataprivacy/add_category'],
function($, Str, Ajax, Notification, Templates, ModalFactory, ModalEvents, Fragment, AddPurpose, AddCategory) {
var SELECTORS = {
TREE_NODES: '[data-context-tree-node=1]',
FORM_CONTAINER: '#context-form-container',
};
var DataRegistry = function(systemContextId, initContextLevel, initContextId) {
this.systemContextId = systemContextId;
this.currentContextLevel = initContextLevel;
this.currentContextId = initContextId;
this.init();
};
/**
* @var {int} systemContextId
* @private
*/
DataRegistry.prototype.systemContextId = 0;
/**
* @var {int} currentContextLevel
* @private
*/
DataRegistry.prototype.currentContextLevel = 0;
/**
* @var {int} currentContextId
* @private
*/
DataRegistry.prototype.currentContextId = 0;
/**
* @var {AddPurpose} addpurpose
* @private
*/
DataRegistry.prototype.addpurpose = null;
/**
* @var {AddCategory} addcategory
* @private
*/
DataRegistry.prototype.addcategory = null;
DataRegistry.prototype.init = function() {
// Add purpose and category modals always at system context.
this.addpurpose = AddPurpose.getInstance(this.systemContextId);
this.addcategory = AddCategory.getInstance(this.systemContextId);
var stringKeys = [
{
key: 'changessaved',
component: 'moodle'
}, {
key: 'contextpurposecategorysaved',
component: 'tool_dataprivacy'
}, {
key: 'noblockstoload',
component: 'tool_dataprivacy'
}, {
key: 'noactivitiestoload',
component: 'tool_dataprivacy'
}, {
key: 'nocoursestoload',
component: 'tool_dataprivacy'
}
];
this.strings = Str.get_strings(stringKeys);
this.registerEventListeners();
// Load the default context level form.
if (this.currentContextId) {
this.loadForm('context_form', [this.currentContextId], this.submitContextFormAjax.bind(this));
} else {
this.loadForm('contextlevel_form', [this.currentContextLevel], this.submitContextLevelFormAjax.bind(this));
}
};
DataRegistry.prototype.registerEventListeners = function() {
$(SELECTORS.TREE_NODES).on('click', function(ev) {
ev.preventDefault();
var trigger = $(ev.currentTarget);
// Active node.
$(SELECTORS.TREE_NODES).removeClass('active');
trigger.addClass('active');
var contextLevel = trigger.data('contextlevel');
var contextId = trigger.data('contextid');
if (contextLevel) {
// Context level level.
window.history.pushState({}, null, '?contextlevel=' + contextLevel);
// Remove previous add purpose and category listeners to avoid memory leaks.
this.addpurpose.removeListeners();
this.addcategory.removeListeners();
// Load the context level form.
this.currentContextLevel = contextLevel;
this.loadForm('contextlevel_form', [this.currentContextLevel], this.submitContextLevelFormAjax.bind(this));
} else if (contextId) {
// Context instance level.
window.history.pushState({}, null, '?contextid=' + contextId);
// Remove previous add purpose and category listeners to avoid memory leaks.
this.addpurpose.removeListeners();
this.addcategory.removeListeners();
// Load the context level form.
this.currentContextId = contextId;
this.loadForm('context_form', [this.currentContextId], this.submitContextFormAjax.bind(this));
} else {
// Expandable nodes.
var expandContextId = trigger.data('expandcontextid');
var expandElement = trigger.data('expandelement');
var expanded = trigger.data('expanded');
// Extra checking that there is an expandElement because we remove it after loading 0 branches.
if (expandElement) {
if (!expanded) {
if (trigger.data('loaded') || !expandContextId || !expandElement) {
this.expand(trigger);
} else {
trigger.find('> i').removeClass('fa-plus');
trigger.find('> i').addClass('fa-circle-o-notch fa-spin');
this.loadExtra(trigger, expandContextId, expandElement);
}
} else {
this.collapse(trigger);
}
}
}
}.bind(this));
};
DataRegistry.prototype.removeListeners = function() {
$(SELECTORS.TREE_NODES).off('click');
};
DataRegistry.prototype.loadForm = function(fragmentName, fragmentArgs, formSubmitCallback) {
this.clearForm();
var fragment = Fragment.loadFragment('tool_dataprivacy', fragmentName, this.systemContextId, fragmentArgs);
fragment.done(function(html, js) {
$(SELECTORS.FORM_CONTAINER).html(html);
Templates.runTemplateJS(js);
this.addpurpose.registerEventListeners();
this.addcategory.registerEventListeners();
// We also catch the form submit event and use it to submit the form with ajax.
$(SELECTORS.FORM_CONTAINER).on('submit', 'form', formSubmitCallback);
}.bind(this)).fail(Notification.exception);
};
DataRegistry.prototype.clearForm = function() {
// For the previously loaded form.
Y.use('moodle-core-formchangechecker', function() {
M.core_formchangechecker.reset_form_dirty_state();
});
// Remove previous listeners.
$(SELECTORS.FORM_CONTAINER).off('submit', 'form');
};
/**
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
*
* @method submitForm
* @param {Event} e Form submission event.
* @private
*/
DataRegistry.prototype.submitForm = function(e) {
e.preventDefault();
$(SELECTORS.FORM_CONTAINER).find('form').submit();
};
DataRegistry.prototype.submitContextLevelFormAjax = function(e) {
this.submitFormAjax(e, 'tool_dataprivacy_set_contextlevel_form');
};
DataRegistry.prototype.submitContextFormAjax = function(e) {
this.submitFormAjax(e, 'tool_dataprivacy_set_context_form');
};
DataRegistry.prototype.submitFormAjax = function(e, saveMethodName) {
// We don't want to do a real form submission.
e.preventDefault();
// Convert all the form elements values to a serialised string.
var formData = $(SELECTORS.FORM_CONTAINER).find('form').serialize();
return this.strings.then(function(strings) {
Ajax.call([{
methodname: saveMethodName,
args: {jsonformdata: JSON.stringify(formData)},
done: function() {
Notification.alert(strings[0], strings[1]);
},
fail: Notification.exception
}]);
return;
}).catch(Notification.exception);
};
DataRegistry.prototype.loadExtra = function(parentNode, expandContextId, expandElement) {
Ajax.call([{
methodname: 'tool_dataprivacy_tree_extra_branches',
args: {
contextid: expandContextId,
element: expandElement,
},
done: function(data) {
if (data.branches.length == 0) {
this.noElements(parentNode, expandElement);
return;
}
Templates.render('tool_dataprivacy/context_tree_branches', data)
.then(function(html) {
parentNode.after(html);
this.removeListeners();
this.registerEventListeners();
this.expand(parentNode);
parentNode.data('loaded', 1);
return;
}.bind(this))
.fail(Notification.exception);
}.bind(this),
fail: Notification.exception
}]);
};
DataRegistry.prototype.noElements = function(node, expandElement) {
node.data('expandcontextid', '');
node.data('expandelement', '');
this.strings.then(function(strings) {
// 2 = blocks, 3 = activities, 4 = courses (although courses is not likely really).
var key = 2;
if (expandElement == 'module') {
key = 3;
} else if (expandElement == 'course') {
key = 4;
}
node.text(strings[key]);
return;
}).fail(Notification.exception);
};
DataRegistry.prototype.collapse = function(node) {
node.data('expanded', 0);
node.siblings('nav').addClass('hidden');
node.find('> i').removeClass('fa-minus');
node.find('> i').addClass('fa-plus');
};
DataRegistry.prototype.expand = function(node) {
node.data('expanded', 1);
node.siblings('nav').removeClass('hidden');
node.find('> i').removeClass('fa-plus');
// Also remove the spinning one if data was just loaded.
node.find('> i').removeClass('fa-circle-o-notch fa-spin');
node.find('> i').addClass('fa-minus');
};
return /** @alias module:tool_dataprivacy/data_registry */ {
/**
* Initialise the page.
*
* @param {Number} systemContextId
* @param {Number} initContextLevel
* @param {Number} initContextId
* @return {DataRegistry}
*/
init: function(systemContextId, initContextLevel, initContextId) {
return new DataRegistry(systemContextId, initContextLevel, initContextId);
}
};
}
);
@@ -0,0 +1,98 @@
// 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/>.
/**
* Request actions.
*
* @module tool_dataprivacy/data_request_modal
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry',
'tool_dataprivacy/events'],
function($, Notification, CustomEvents, Modal, ModalRegistry, DataPrivacyEvents) {
var registered = false;
var SELECTORS = {
APPROVE_BUTTON: '[data-action="approve"]',
DENY_BUTTON: '[data-action="deny"]',
COMPLETE_BUTTON: '[data-action="complete"]'
};
/**
* Constructor for the Modal.
*
* @param {object} root The root jQuery element for the modal
*/
var ModalDataRequest = function(root) {
Modal.call(this, root);
};
ModalDataRequest.TYPE = 'tool_dataprivacy-data_request';
ModalDataRequest.TYPE_ENQUIRY = 'tool_dataprivacy-complete_request';
ModalDataRequest.prototype = Object.create(Modal.prototype);
ModalDataRequest.prototype.constructor = ModalDataRequest;
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
ModalDataRequest.prototype.registerEventListeners = function() {
// Apply parent event listeners.
Modal.prototype.registerEventListeners.call(this);
this.getModal().on(CustomEvents.events.activate, SELECTORS.APPROVE_BUTTON, function(e, data) {
var approveEvent = $.Event(DataPrivacyEvents.approve);
this.getRoot().trigger(approveEvent, this);
if (!approveEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
}.bind(this));
this.getModal().on(CustomEvents.events.activate, SELECTORS.DENY_BUTTON, function(e, data) {
var denyEvent = $.Event(DataPrivacyEvents.deny);
this.getRoot().trigger(denyEvent, this);
if (!denyEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
}.bind(this));
this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, function(e, data) {
var completeEvent = $.Event(DataPrivacyEvents.complete);
this.getRoot().trigger(completeEvent, this);
if (!completeEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
}.bind(this));
};
// Automatically register with the modal registry the first time this module is imported so that you can create modals
// of this type using the modal factory.
if (!registered) {
ModalRegistry.register(ModalDataRequest.TYPE, ModalDataRequest, 'tool_dataprivacy/data_request_modal');
ModalRegistry.register(ModalDataRequest.TYPE_ENQUIRY, ModalDataRequest, 'tool_dataprivacy/mark_request_complete_modal');
registered = true;
}
return ModalDataRequest;
});
@@ -0,0 +1,315 @@
// 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/>.
/**
* AMD module for data registry defaults actions.
*
* @module tool_dataprivacy/defaultsactions
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/notification',
'core/str',
'core/modal_factory',
'core/modal_events',
'core/templates'],
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
/**
* List of action selectors.
*
* @type {{EDIT_LEVEL_DEFAULTS: string}}
* @type {{NEW_ACTIVITY_DEFAULTS: string}}
* @type {{EDIT_ACTIVITY_DEFAULTS: string}}
* @type {{DELETE_ACTIVITY_DEFAULTS: string}}
*/
var ACTIONS = {
EDIT_LEVEL_DEFAULTS: '[data-action="edit-level-defaults"]',
NEW_ACTIVITY_DEFAULTS: '[data-action="new-activity-defaults"]',
EDIT_ACTIVITY_DEFAULTS: '[data-action="edit-activity-defaults"]',
DELETE_ACTIVITY_DEFAULTS: '[data-action="delete-activity-defaults"]'
};
/** @type {{INHERIT: Number}} **/
var INHERIT = -1;
/**
* DefaultsActions class.
*/
var DefaultsActions = function() {
this.registerEvents();
};
/**
* Register event listeners.
*/
DefaultsActions.prototype.registerEvents = function() {
$(ACTIONS.EDIT_LEVEL_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
var category = button.data('category');
var purpose = button.data('purpose');
// Get options.
var requests = [
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}}
];
var promises = Ajax.call(requests);
var titlePromise = Str.get_string('editdefaults', 'tool_dataprivacy', $('#defaults-header').text());
$.when(promises[0], promises[1], titlePromise).then(function(categoryResponse, purposeResponse, title) {
var categories = categoryResponse.options;
var purposes = purposeResponse.options;
showDefaultsFormModal(title, contextLevel, category, purpose, null, categories, purposes, null);
return true;
}).catch(Notification.exception);
});
$(ACTIONS.NEW_ACTIVITY_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
// Get options.
var requests = [
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}},
{methodname: 'tool_dataprivacy_get_activity_options', args: {'nodefaults': true}}
];
var promises = Ajax.call(requests);
var titlePromise = Str.get_string('addnewdefaults', 'tool_dataprivacy');
$.when(promises[0], promises[1], promises[2], titlePromise).then(
function(categoryResponse, purposeResponse, activityResponse, title) {
var categories = categoryResponse.options;
var purposes = purposeResponse.options;
var activities = activityResponse.options;
showDefaultsFormModal(title, contextLevel, null, null, null, categories, purposes, activities);
return true;
}).catch(Notification.exception);
}
);
$(ACTIONS.EDIT_ACTIVITY_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
var category = button.data('category');
var purpose = button.data('purpose');
var activity = button.data('activityname');
// Get options.
var requests = [
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}},
{methodname: 'tool_dataprivacy_get_activity_options', args: {}}
];
var promises = Ajax.call(requests);
var titlePromise = Str.get_string('editmoduledefaults', 'tool_dataprivacy');
$.when(promises[0], promises[1], promises[2], titlePromise).then(
function(categoryResponse, purposeResponse, activityResponse, title) {
var categories = categoryResponse.options;
var purposes = purposeResponse.options;
var activities = activityResponse.options;
showDefaultsFormModal(title, contextLevel, category, purpose, activity, categories, purposes, activities);
return true;
}).catch(Notification.exception);
}
);
$(ACTIONS.DELETE_ACTIVITY_DEFAULTS).click(function(e) {
e.preventDefault();
var button = $(this);
var contextLevel = button.data('contextlevel');
var activity = button.data('activityname');
var activityDisplayName = button.data('activitydisplayname');
// Set category and purpose to inherit (-1).
var category = INHERIT;
var purpose = INHERIT;
ModalFactory.create({
title: Str.get_string('deletedefaults', 'tool_dataprivacy', activityDisplayName),
body: Templates.render('tool_dataprivacy/delete_activity_defaults', {"activityname": activityDisplayName}),
type: ModalFactory.types.SAVE_CANCEL,
large: true
}).then(function(modal) {
modal.setSaveButtonText(Str.get_string('delete'));
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
setContextDefaults(contextLevel, category, purpose, activity, false);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
modal.show();
return true;
}).catch(Notification.exception);
});
};
/**
* Prepares and renders the modal for setting the defaults for the given context level/plugin.
*
* @param {String} title The modal's title.
* @param {Number} contextLevel The context level to set defaults for.
* @param {Number} category The current category ID.
* @param {Number} purpose The current purpose ID.
* @param {String} activity The plugin name of the activity. Optional.
* @param {Array} categoryOptions The list of category options.
* @param {Array} purposeOptions The list of purpose options.
* @param {Array} activityOptions The list of activity options. Optional.
*/
function showDefaultsFormModal(title, contextLevel, category, purpose, activity,
categoryOptions, purposeOptions, activityOptions) {
if (category !== null) {
categoryOptions.forEach(function(currentValue) {
if (currentValue.id === category) {
currentValue.selected = true;
}
});
}
if (purpose !== null) {
purposeOptions.forEach(function(currentValue) {
if (currentValue.id === purpose) {
currentValue.selected = true;
}
});
}
var templateContext = {
"contextlevel": contextLevel,
"categoryoptions": categoryOptions,
"purposeoptions": purposeOptions
};
// Check the activityOptions parameter that was passed.
if (activityOptions !== null && activityOptions.length) {
// Check the activity parameter that was passed.
if (activity === null) {
// We're setting a new defaults for a module.
templateContext.newactivitydefaults = true;
} else {
// Edit mode. Set selection.
activityOptions.forEach(function(currentValue) {
if (activity === currentValue.name) {
currentValue.selected = true;
}
});
}
templateContext.modemodule = true;
templateContext.activityoptions = activityOptions;
}
ModalFactory.create({
title: title,
body: Templates.render('tool_dataprivacy/category_purpose_form', templateContext),
type: ModalFactory.types.SAVE_CANCEL,
large: true
}).then(function(modal) {
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
var activity = $('#activity');
var activityVal = typeof activity !== 'undefined' ? activity.val() : null;
var override = $('#override');
var overrideVal = typeof override !== 'undefined' ? override.is(':checked') : false;
setContextDefaults($('#contextlevel').val(), $('#category').val(), $('#purpose').val(), activityVal, overrideVal);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
modal.show();
return modal;
}).catch(Notification.exception);
}
/**
* Calls a the tool_dataprivacy_set_context_defaults WS function.
*
* @param {Number} contextLevel The context level.
* @param {Number} category The category ID.
* @param {Number} purpose The purpose ID.
* @param {String} activity The plugin name of the activity module.
* @param {Boolean} override Whether to override custom instances.
*/
function setContextDefaults(contextLevel, category, purpose, activity, override) {
var request = {
methodname: 'tool_dataprivacy_set_context_defaults',
args: {
'contextlevel': contextLevel,
'category': category,
'purpose': purpose,
'override': override,
'activity': activity
}
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
window.location.reload();
}
});
}
return /** @alias module:tool_dataprivacy/defaultsactions */ {
// Public variables and functions.
/**
* Initialise the module.
*
* @method init
* @return {DefaultsActions}
*/
'init': function() {
return new DefaultsActions();
}
};
});
@@ -0,0 +1,92 @@
// 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/>.
/**
* Module to update the displayed retention period.
*
* @module tool_dataprivacy/effective_retention_period
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'],
function($) {
var SELECTORS = {
PURPOSE_SELECT: '#id_purposeid',
RETENTION_FIELD_BOOST: '#id_error_retention_current',
RETENTION_FIELD_CLEAN: '#fitem_id_retention_current [data-fieldtype=static]',
};
/**
* Constructor for the retention period display.
*
* @param {Array} purposeRetentionPeriods Associative array of purposeids with effective retention period at this context
*/
var EffectiveRetentionPeriod = function(purposeRetentionPeriods) {
this.purposeRetentionPeriods = purposeRetentionPeriods;
this.registerEventListeners();
};
/**
* Removes the current 'change' listeners.
*
* Useful when a new form is loaded.
*/
var removeListeners = function() {
$(SELECTORS.PURPOSE_SELECT).off('change');
};
/**
* @var {Array} purposeRetentionPeriods
* @private
*/
EffectiveRetentionPeriod.prototype.purposeRetentionPeriods = [];
/**
* Add purpose change listeners.
*
* @method registerEventListeners
*/
EffectiveRetentionPeriod.prototype.registerEventListeners = function() {
$(SELECTORS.PURPOSE_SELECT).on('change', function(ev) {
var selected = $(ev.currentTarget).val();
var selectedPurpose = this.purposeRetentionPeriods[selected];
var cleanSelector = $(SELECTORS.RETENTION_FIELD_CLEAN);
if (cleanSelector.length > 0) {
cleanSelector.text(selectedPurpose);
} else {
var boostSelector = $(SELECTORS.RETENTION_FIELD_BOOST);
var retentionField = boostSelector.siblings();
if (retentionField.length > 0) {
retentionField.text(selectedPurpose);
}
}
}.bind(this));
};
return /** @alias module:tool_dataprivacy/effective_retention_period */ {
init: function(purposeRetentionPeriods) {
// Remove previously attached listeners.
removeListeners();
return new EffectiveRetentionPeriod(purposeRetentionPeriods);
}
};
}
);
+33
View File
@@ -0,0 +1,33 @@
// 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/>.
/**
* Contain the events the data privacy tool can fire.
*
* @module tool_dataprivacy/events
* @class events
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
approve: 'tool_dataprivacy-data_request:approve',
bulkApprove: 'tool_dataprivacy-data_request:bulk_approve',
deny: 'tool_dataprivacy-data_request:deny',
bulkDeny: 'tool_dataprivacy-data_request:bulk_deny',
complete: 'tool_dataprivacy-data_request:complete'
};
});
@@ -0,0 +1,88 @@
// 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/>.
/**
* Potential user selector module.
*
* @module tool_dataprivacy/expand_contract
* @class page-expand-contract
* @package tool_dataprivacy
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/url', 'core/str'], function($, url, str) {
var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
return /** @alias module:tool_dataprivacy/expand-collapse */ {
/**
* Expand or collapse a selected node.
*
* @param {object} targetnode The node that we want to expand / collapse
* @param {object} thisnode The node that was clicked.
*/
expandCollapse: function(targetnode, thisnode) {
if (targetnode.hasClass('hide')) {
targetnode.removeClass('hide');
targetnode.addClass('visible');
targetnode.attr('aria-expanded', true);
thisnode.find(':header i.fa').removeClass('fa-plus-square');
thisnode.find(':header i.fa').addClass('fa-minus-square');
thisnode.find(':header img.icon').attr('src', expandedImage.attr('src'));
} else {
targetnode.removeClass('visible');
targetnode.addClass('hide');
targetnode.attr('aria-expanded', false);
thisnode.find(':header i.fa').removeClass('fa-minus-square');
thisnode.find(':header i.fa').addClass('fa-plus-square');
thisnode.find(':header img.icon').attr('src', collapsedImage.attr('src'));
}
},
/**
* Expand or collapse all nodes on this page.
*
* @param {string} nextstate The next state to change to.
*/
expandCollapseAll: function(nextstate) {
var currentstate = (nextstate == 'visible') ? 'hide' : 'visible';
var ariaexpandedstate = (nextstate == 'visible') ? true : false;
var iconclassnow = (nextstate == 'visible') ? 'fa-plus-square' : 'fa-minus-square';
var iconclassnext = (nextstate == 'visible') ? 'fa-minus-square' : 'fa-plus-square';
var imagenow = (nextstate == 'visible') ? expandedImage.attr('src') : collapsedImage.attr('src');
$('.' + currentstate).each(function() {
$(this).removeClass(currentstate);
$(this).addClass(nextstate);
$(this).attr('aria-expanded', ariaexpandedstate);
});
$('.tool_dataprivacy-expand-all').data('visibilityState', currentstate);
str.get_string(currentstate, 'tool_dataprivacy').then(function(langString) {
$('.tool_dataprivacy-expand-all').html(langString);
return;
}).catch(Notification.exception);
$(':header i.fa').each(function() {
$(this).removeClass(iconclassnow);
$(this).addClass(iconclassnext);
});
$(':header img.icon').each(function() {
$(this).attr('src', imagenow);
});
}
};
});
@@ -0,0 +1,76 @@
// 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/>.
/**
* Potential user selector module.
*
* @module tool_dataprivacy/form-user-selector
* @class form-user-selector
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
return /** @alias module:tool_dataprivacy/form-user-selector */ {
processResults: function(selector, results) {
var users = [];
$.each(results, function(index, user) {
users.push({
value: user.id,
label: user._label
});
});
return users;
},
transport: function(selector, query, success, failure) {
var promise;
promise = Ajax.call([{
methodname: 'tool_dataprivacy_get_users',
args: {
query: query
}
}]);
promise[0].then(function(results) {
var promises = [],
i = 0;
// Render the label.
$.each(results, function(index, user) {
promises.push(Templates.render('tool_dataprivacy/form-user-selector-suggestion', user));
});
// Apply the label to the results.
return $.when.apply($.when, promises).then(function() {
var args = arguments;
$.each(results, function(index, user) {
user._label = args[i];
i++;
});
success(results);
return;
});
}).fail(failure);
}
};
});
@@ -0,0 +1,221 @@
// 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/>.
/**
* AMD module to enable users to manage their own data requests.
*
* @module tool_dataprivacy/myrequestactions
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/notification',
'core/str',
'core/modal_factory',
'core/modal_events',
'core/templates'],
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
/**
* List of action selectors.
*
* @type {{CANCEL_REQUEST: string}}
* @type {{CONTACT_DPO: string}}
*/
var ACTIONS = {
CANCEL_REQUEST: '[data-action="cancel"]',
CONTACT_DPO: '[data-action="contactdpo"]',
};
/**
* MyRequestActions class.
*/
var MyRequestActions = function() {
this.registerEvents();
};
/**
* Register event listeners.
*/
MyRequestActions.prototype.registerEvents = function() {
$(ACTIONS.CANCEL_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
var stringkeys = [
{
key: 'cancelrequest',
component: 'tool_dataprivacy'
},
{
key: 'cancelrequestconfirmation',
component: 'tool_dataprivacy'
}
];
Str.get_strings(stringkeys).then(function(langStrings) {
var title = langStrings[0];
var confirmMessage = langStrings[1];
return ModalFactory.create({
title: title,
body: confirmMessage,
type: ModalFactory.types.SAVE_CANCEL
}).then(function(modal) {
modal.setSaveButtonText(title);
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
// Cancel the request.
var params = {
'requestid': requestId
};
var request = {
methodname: 'tool_dataprivacy_cancel_data_request',
args: params
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
window.location.reload();
} else {
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).fail(Notification.exception);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
return modal;
});
}).done(function(modal) {
// Show the modal!
modal.show();
}).fail(Notification.exception);
});
$(ACTIONS.CONTACT_DPO).click(function(e) {
e.preventDefault();
var replyToEmail = $(this).data('replytoemail');
var keys = [
{
key: 'contactdataprotectionofficer',
component: 'tool_dataprivacy'
},
{
key: 'send',
component: 'tool_dataprivacy'
},
];
var sendButtonText = '';
Str.get_strings(keys).then(function(langStrings) {
var modalTitle = langStrings[0];
sendButtonText = langStrings[1];
var context = {
'replytoemail': replyToEmail
};
return ModalFactory.create({
title: modalTitle,
body: Templates.render('tool_dataprivacy/contact_dpo', context),
type: ModalFactory.types.SAVE_CANCEL,
large: true
});
}).done(function(modal) {
modal.setSaveButtonText(sendButtonText);
// Handle send event.
modal.getRoot().on(ModalEvents.save, function(e) {
var message = $('#message').val().trim();
if (message.length === 0) {
e.preventDefault();
// Show validation error when the message is empty.
$('[data-region="messageinput"]').addClass('has-danger notifyproblem');
$('#id_error_message').removeAttr('hidden');
} else {
// Send the message.
sendMessageToDPO(message);
}
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
// Show the modal!
modal.show();
}).fail(Notification.exception);
});
};
/**
* Send message to the Data Protection Officer.
*
* @param {String} message The message to send.
*/
function sendMessageToDPO(message) {
var request = {
methodname: 'tool_dataprivacy_contact_dpo',
args: {
message: message
}
};
var requestType = 'success';
Ajax.call([request])[0].then(function(data) {
if (data.result) {
return Str.get_string('requestsubmitted', 'tool_dataprivacy');
}
requestType = 'error';
return data.warnings.join('<br>');
}).done(function(message) {
Notification.addNotification({
message: message,
type: requestType
});
}).fail(Notification.exception);
}
return /** @alias module:tool_dataprivacy/myrequestactions */ {
// Public variables and functions.
/**
* Initialise the unified user filter.
*
* @method init
* @return {MyRequestActions}
*/
'init': function() {
return new MyRequestActions();
}
};
});
@@ -0,0 +1,132 @@
// 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/>.
/**
* AMD module for purposes actions.
*
* @module tool_dataprivacy/purposesactions
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/notification',
'core/str',
'core/modal_factory',
'core/modal_events'],
function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
/**
* List of action selectors.
*
* @type {{DELETE: string}}
*/
var ACTIONS = {
DELETE: '[data-action="deletepurpose"]',
};
/**
* PurposesActions class.
*/
var PurposesActions = function() {
this.registerEvents();
};
/**
* Register event listeners.
*/
PurposesActions.prototype.registerEvents = function() {
$(ACTIONS.DELETE).click(function(e) {
e.preventDefault();
var id = $(this).data('id');
var purposename = $(this).data('name');
var stringkeys = [
{
key: 'deletepurpose',
component: 'tool_dataprivacy'
},
{
key: 'deletepurposetext',
component: 'tool_dataprivacy',
param: purposename
},
{
key: 'delete'
}
];
Str.get_strings(stringkeys).then(function(langStrings) {
var title = langStrings[0];
var confirmMessage = langStrings[1];
var buttonText = langStrings[2];
return ModalFactory.create({
title: title,
body: confirmMessage,
type: ModalFactory.types.SAVE_CANCEL
}).then(function(modal) {
modal.setSaveButtonText(buttonText);
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
var request = {
methodname: 'tool_dataprivacy_delete_purpose',
args: {'id': id}
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
$('tr[data-purposeid="' + id + '"]').remove();
} else {
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).fail(Notification.exception);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
return modal;
});
}).done(function(modal) {
modal.show();
}).fail(Notification.exception);
});
};
return /** @alias module:tool_dataprivacy/purposesactions */ {
// Public variables and functions.
/**
* Initialise the module.
*
* @method init
* @return {PurposesActions}
*/
'init': function() {
return new PurposesActions();
}
};
});
@@ -0,0 +1,84 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* JS module for the data requests filter.
*
* @module tool_dataprivacy/request_filter
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'], function($, Autocomplete, Str, Notification) {
/**
* Selectors.
*
* @access private
* @type {{REQUEST_FILTERS: string}}
*/
var SELECTORS = {
REQUEST_FILTERS: '#request-filters'
};
/**
* Init function.
*
* @method init
* @private
*/
var init = function() {
var stringkeys = [
{
key: 'filter',
component: 'moodle'
},
{
key: 'nofiltersapplied',
component: 'tool_dataprivacy'
}
];
Str.get_strings(stringkeys).then(function(langstrings) {
var placeholder = langstrings[0];
var noSelectionString = langstrings[1];
return Autocomplete.enhance(SELECTORS.REQUEST_FILTERS, false, '', placeholder, false, true, noSelectionString, true);
}).fail(Notification.exception);
var last = $(SELECTORS.REQUEST_FILTERS).val();
$(SELECTORS.REQUEST_FILTERS).on('change', function() {
var current = $(this).val();
// Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
if (last.join(',') !== current.join(',')) {
// If we're submitting without filters, set the hidden input 'filters-cleared' to 1.
if (current.length === 0) {
$('#filters-cleared').val(1);
}
$(this.form).submit();
}
});
};
return /** @alias module:core/form-autocomplete */ {
/**
* Initialise the unified user filter.
*
* @method init
*/
init: function() {
init();
}
};
});
@@ -0,0 +1,443 @@
// 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/>.
/**
* Request actions.
*
* @module tool_dataprivacy/requestactions
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/ajax',
'core/notification',
'core/str',
'core/modal_factory',
'core/modal_events',
'core/templates',
'tool_dataprivacy/data_request_modal',
'tool_dataprivacy/events'],
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, ModalDataRequest, DataPrivacyEvents) {
/**
* List of action selectors.
*
* @type {{APPROVE_REQUEST: string}}
* @type {{DENY_REQUEST: string}}
* @type {{VIEW_REQUEST: string}}
* @type {{MARK_COMPLETE: string}}
* @type {{CHANGE_BULK_ACTION: string}}
* @type {{CONFIRM_BULK_ACTION: string}}
* @type {{SELECT_ALL: string}}
*/
var ACTIONS = {
APPROVE_REQUEST: '[data-action="approve"]',
DENY_REQUEST: '[data-action="deny"]',
VIEW_REQUEST: '[data-action="view"]',
MARK_COMPLETE: '[data-action="complete"]',
CHANGE_BULK_ACTION: '[id="bulk-action"]',
CONFIRM_BULK_ACTION: '[id="confirm-bulk-action"]',
SELECT_ALL: '[data-action="selectall"]'
};
/**
* List of available bulk actions.
*
* @type {{APPROVE: number}}
* @type {{DENY: number}}
*/
var BULK_ACTIONS = {
APPROVE: 1,
DENY: 2
};
/**
* List of selectors.
*
* @type {{SELECT_REQUEST: string}}
*/
var SELECTORS = {
SELECT_REQUEST: '.selectrequests'
};
/**
* RequestActions class.
*/
var RequestActions = function() {
this.registerEvents();
};
/**
* Register event listeners.
*/
RequestActions.prototype.registerEvents = function() {
$(ACTIONS.VIEW_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
// Cancel the request.
var params = {
'requestid': requestId
};
var request = {
methodname: 'tool_dataprivacy_get_data_request',
args: params
};
var promises = Ajax.call([request]);
$.when(promises[0]).then(function(data) {
if (data.result) {
return data.result;
}
// Fail.
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
return false;
}).then(function(data) {
// Determine the type of modal to show.
var modalType = ModalFactory.types.DEFAULT;
if (data.approvedeny) {
modalType = ModalDataRequest.TYPE;
} else if (data.canmarkcomplete) {
modalType = ModalDataRequest.TYPE_ENQUIRY;
}
var body = Templates.render('tool_dataprivacy/request_details', data);
return ModalFactory.create({
title: data.typename,
body: body,
type: modalType,
large: true
});
}).then(function(modal) {
// Handle approve event.
modal.getRoot().on(DataPrivacyEvents.approve, function() {
showConfirmation(DataPrivacyEvents.approve, approveEventWsData(requestId));
});
// Handle deny event.
modal.getRoot().on(DataPrivacyEvents.deny, function() {
showConfirmation(DataPrivacyEvents.deny, denyEventWsData(requestId));
});
// Handle send event.
modal.getRoot().on(DataPrivacyEvents.complete, function() {
var params = {
'requestid': requestId
};
handleSave('tool_dataprivacy_mark_complete', params);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
// Show the modal!
modal.show();
return;
}).catch(Notification.exception);
});
$(ACTIONS.APPROVE_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.approve, approveEventWsData(requestId));
});
$(ACTIONS.DENY_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.deny, denyEventWsData(requestId));
});
$(ACTIONS.MARK_COMPLETE).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.complete, completeEventWsData(requestId));
});
$(ACTIONS.CONFIRM_BULK_ACTION).click(function() {
var requestIds = [];
var actionEvent = '';
var wsdata = {};
var bulkActionKeys = [
{
key: 'selectbulkaction',
component: 'tool_dataprivacy'
},
{
key: 'selectdatarequests',
component: 'tool_dataprivacy'
},
{
key: 'ok'
}
];
var bulkaction = parseInt($('#bulk-action').val());
if (bulkaction != BULK_ACTIONS.APPROVE && bulkaction != BULK_ACTIONS.DENY) {
Str.get_strings(bulkActionKeys).done(function(langStrings) {
Notification.alert('', langStrings[0], langStrings[2]);
}).fail(Notification.exception);
return;
}
$(".selectrequests:checked").each(function() {
requestIds.push($(this).val());
});
if (requestIds.length < 1) {
Str.get_strings(bulkActionKeys).done(function(langStrings) {
Notification.alert('', langStrings[1], langStrings[2]);
}).fail(Notification.exception);
return;
}
switch (bulkaction) {
case BULK_ACTIONS.APPROVE:
actionEvent = DataPrivacyEvents.bulkApprove;
wsdata = bulkApproveEventWsData(requestIds);
break;
case BULK_ACTIONS.DENY:
actionEvent = DataPrivacyEvents.bulkDeny;
wsdata = bulkDenyEventWsData(requestIds);
}
showConfirmation(actionEvent, wsdata);
});
$(ACTIONS.SELECT_ALL).change(function(e) {
e.preventDefault();
var selectAll = $(this).is(':checked');
$(SELECTORS.SELECT_REQUEST).prop('checked', selectAll);
});
};
/**
* Return the webservice data for the approve request action.
*
* @param {Number} requestId The ID of the request.
* @return {Object}
*/
function approveEventWsData(requestId) {
return {
'wsfunction': 'tool_dataprivacy_approve_data_request',
'wsparams': {'requestid': requestId}
};
}
/**
* Return the webservice data for the bulk approve request action.
*
* @param {Array} requestIds The array of request ID's.
* @return {Object}
*/
function bulkApproveEventWsData(requestIds) {
return {
'wsfunction': 'tool_dataprivacy_bulk_approve_data_requests',
'wsparams': {'requestids': requestIds}
};
}
/**
* Return the webservice data for the deny request action.
*
* @param {Number} requestId The ID of the request.
* @return {Object}
*/
function denyEventWsData(requestId) {
return {
'wsfunction': 'tool_dataprivacy_deny_data_request',
'wsparams': {'requestid': requestId}
};
}
/**
* Return the webservice data for the bulk deny request action.
*
* @param {Array} requestIds The array of request ID's.
* @return {Object}
*/
function bulkDenyEventWsData(requestIds) {
return {
'wsfunction': 'tool_dataprivacy_bulk_deny_data_requests',
'wsparams': {'requestids': requestIds}
};
}
/**
* Return the webservice data for the complete request action.
*
* @param {Number} requestId The ID of the request.
* @return {Object}
*/
function completeEventWsData(requestId) {
return {
'wsfunction': 'tool_dataprivacy_mark_complete',
'wsparams': {'requestid': requestId}
};
}
/**
* Show the confirmation dialogue.
*
* @param {String} action The action name.
* @param {Object} wsdata Object containing ws data.
*/
function showConfirmation(action, wsdata) {
var keys = [];
switch (action) {
case DataPrivacyEvents.approve:
keys = [
{
key: 'approverequest',
component: 'tool_dataprivacy'
},
{
key: 'confirmapproval',
component: 'tool_dataprivacy'
}
];
break;
case DataPrivacyEvents.bulkApprove:
keys = [
{
key: 'bulkapproverequests',
component: 'tool_dataprivacy'
},
{
key: 'confirmbulkapproval',
component: 'tool_dataprivacy'
}
];
break;
case DataPrivacyEvents.deny:
keys = [
{
key: 'denyrequest',
component: 'tool_dataprivacy'
},
{
key: 'confirmdenial',
component: 'tool_dataprivacy'
}
];
break;
case DataPrivacyEvents.bulkDeny:
keys = [
{
key: 'bulkdenyrequests',
component: 'tool_dataprivacy'
},
{
key: 'confirmbulkdenial',
component: 'tool_dataprivacy'
}
];
break;
case DataPrivacyEvents.complete:
keys = [
{
key: 'markcomplete',
component: 'tool_dataprivacy'
},
{
key: 'confirmcompletion',
component: 'tool_dataprivacy'
}
];
break;
}
var modalTitle = '';
Str.get_strings(keys).then(function(langStrings) {
modalTitle = langStrings[0];
var confirmMessage = langStrings[1];
return ModalFactory.create({
title: modalTitle,
body: confirmMessage,
type: ModalFactory.types.SAVE_CANCEL
});
}).then(function(modal) {
modal.setSaveButtonText(modalTitle);
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
handleSave(wsdata.wsfunction, wsdata.wsparams);
});
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
modal.show();
return;
}).catch(Notification.exception);
}
/**
* Calls a web service function and reloads the page on success and shows a notification.
* Displays an error notification, otherwise.
*
* @param {String} wsfunction The web service function to call.
* @param {Object} params The parameters for the web service functoon.
*/
function handleSave(wsfunction, params) {
// Confirm the request.
var request = {
methodname: wsfunction,
args: params
};
Ajax.call([request])[0].done(function(data) {
if (data.result) {
// On success, reload the page so that the data request table will be updated.
// TODO: Probably in the future, better to reload the table or the target data request via AJAX.
window.location.reload();
} else {
// Add the notification.
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
}
}).fail(Notification.exception);
}
return RequestActions;
});
+42
View File
@@ -0,0 +1,42 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This page lets users manage categories.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
$url = new moodle_url("/admin/tool/dataprivacy/categories.php");
$title = get_string('editcategories', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
echo $output->heading($title);
$categories = \tool_dataprivacy\api::get_categories();
$renderable = new \tool_dataprivacy\output\categories($categories);
echo $output->render($renderable);
echo $output->footer();
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,91 @@
<?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 for loading/storing data categories from the DB.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Class for loading/storing data categories from the DB.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category extends \core\persistent {
/**
* Database table.
*/
const TABLE = 'tool_dataprivacy_category';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'name' => array(
'type' => PARAM_TEXT,
'description' => 'The category name.',
),
'description' => array(
'type' => PARAM_RAW,
'description' => 'The category description.',
'null' => NULL_ALLOWED,
'default' => '',
),
'descriptionformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_HTML
),
);
}
/**
* Is this category used?.
*
* @return null
*/
public function is_used() {
if (\tool_dataprivacy\contextlevel::is_category_used($this->get('id')) ||
\tool_dataprivacy\context_instance::is_category_used($this->get('id'))) {
return true;
}
$pluginconfig = get_config('tool_dataprivacy');
$levels = \context_helper::get_all_levels();
foreach ($levels as $level => $classname) {
list($unused, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
if (!empty($pluginconfig->{$categoryvar}) && $pluginconfig->{$categoryvar} == $this->get('id')) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,116 @@
<?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 for loading/storing context instances data from the DB.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
/**
* Class for loading/storing context instances data from the DB.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class context_instance extends \core\persistent {
/**
* Database table.
*/
const TABLE = 'tool_dataprivacy_ctxinstance';
/**
* Not set value.
*/
const NOTSET = 0;
/**
* Inherit value.
*/
const INHERIT = -1;
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'contextid' => array(
'type' => PARAM_INT,
'description' => 'The context id.',
),
'purposeid' => array(
'type' => PARAM_INT,
'description' => 'The purpose id.',
'null' => NULL_ALLOWED,
),
'categoryid' => array(
'type' => PARAM_INT,
'description' => 'The category id.',
'null' => NULL_ALLOWED,
),
);
}
/**
* Returns an instance by contextid.
*
* @param mixed $contextid
* @param mixed $exception
* @return null
*/
public static function get_record_by_contextid($contextid, $exception = true) {
global $DB;
if (!$record = $DB->get_record(self::TABLE, array('contextid' => $contextid))) {
if (!$exception) {
return false;
} else {
throw new \dml_missing_record_exception(self::TABLE);
}
}
return new static(0, $record);
}
/**
* Is the provided purpose used by any context instance?
*
* @param int $purposeid
* @return bool
*/
public static function is_purpose_used($purposeid) {
global $DB;
return $DB->record_exists(self::TABLE, array('purposeid' => $purposeid));
}
/**
* Is the provided category used by any context instance?
*
* @param int $categoryid
* @return bool
*/
public static function is_category_used($categoryid) {
global $DB;
return $DB->record_exists(self::TABLE, array('categoryid' => $categoryid));
}
}
@@ -0,0 +1,140 @@
<?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 for loading/storing context level data from the DB.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
/**
* Class for loading/storing context level data from the DB.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlevel extends \core\persistent {
/**
* Database table.
*/
const TABLE = 'tool_dataprivacy_ctxlevel';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'contextlevel' => array(
'type' => PARAM_INT,
'description' => 'The context level.',
),
'purposeid' => array(
'type' => PARAM_INT,
'description' => 'The purpose id.',
),
'categoryid' => array(
'type' => PARAM_INT,
'description' => 'The category id.',
),
);
}
/**
* Returns an instance by contextlevel.
*
* @param mixed $contextlevel
* @param mixed $exception
* @return null
*/
public static function get_record_by_contextlevel($contextlevel, $exception = true) {
global $DB;
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
if ($data = $cache->get($contextlevel)) {
return new static(0, $data);
}
if (!$record = $DB->get_record(self::TABLE, array('contextlevel' => $contextlevel))) {
if (!$exception) {
return false;
} else {
throw new \dml_missing_record_exception(self::TABLE);
}
}
return new static(0, $record);
}
/**
* Is the provided purpose used by any contextlevel?
*
* @param int $purposeid
* @return bool
*/
public static function is_purpose_used($purposeid) {
global $DB;
return $DB->record_exists(self::TABLE, array('purposeid' => $purposeid));
}
/**
* Is the provided category used by any contextlevel?
*
* @param int $categoryid
* @return bool
*/
public static function is_category_used($categoryid) {
global $DB;
return $DB->record_exists(self::TABLE, array('categoryid' => $categoryid));
}
/**
* Adds the new record to the cache.
*
* @return null
*/
protected function after_create() {
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
$cache->set($this->get('contextlevel'), $this->to_record());
}
/**
* Updates the cache record.
*
* @param bool $result
* @return null
*/
protected function after_update($result) {
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
$cache->set($this->get('contextlevel'), $this->to_record());
}
/**
* Removes unnecessary stuff from db.
*
* @return null
*/
protected function before_delete() {
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
$cache->delete($this->get('contextlevel'));
}
}
@@ -0,0 +1,64 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the contextlist persistent.
*
* @package tool_dataprivacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
use core\persistent;
/**
* The contextlist persistent.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist extends persistent {
/** The table name this persistent object maps to. */
const TABLE = 'tool_dataprivacy_contextlist';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return [
'component' => [
'type' => PARAM_TEXT
]
];
}
/**
* Create a new contextlist persistent from an instance of \core_privacy\local\request\contextlist.
*
* @param \core_privacy\local\request\contextlist $contextlist the core privacy contextlist.
* @return contextlist a contextlist persistent.
*/
public static function from_contextlist(\core_privacy\local\request\contextlist $contextlist) {
$contextlistpersistent = new contextlist();
return $contextlistpersistent->set('component', $contextlist->get_component());
}
}
@@ -0,0 +1,74 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the contextlist_context persistent.
*
* @package tool_dataprivacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
use core\persistent;
/**
* The contextlist_context persistent.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist_context extends persistent {
/** The table name this persistent object maps to. */
const TABLE = 'tool_dataprivacy_ctxlst_ctx';
/** This context is pending approval. */
const STATUS_PENDING = 0;
/** This context has been approved. */
const STATUS_APPROVED = 1;
/** This context has been rejected. */
const STATUS_REJECTED = 2;
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return [
'contextid' => [
'type' => PARAM_INT
],
'contextlistid' => [
'type' => PARAM_INT
],
'status' => [
'choices' => [
self::STATUS_PENDING,
self::STATUS_APPROVED,
self::STATUS_REJECTED,
],
'default' => self::STATUS_PENDING,
'type' => PARAM_INT
]
];
}
}
@@ -0,0 +1,357 @@
<?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 registry business logic methods. Mostly internal stuff.
*
* All methods should be considered part of the internal tool_dataprivacy API
* unless something different is specified.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use coding_exception;
use core\persistent;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/coursecatlib.php');
/**
* Data registry business logic methods. Mostly internal stuff.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_registry {
/**
* Returns purpose and category var names from a context class name
*
* @param string $classname The context level's class.
* @param string $pluginname The name of the plugin associated with the context level.
* @return string[]
*/
public static function var_names_from_context($classname, $pluginname = '') {
$pluginname = trim($pluginname);
if (!empty($pluginname)) {
$categoryvar = $classname . '_' . $pluginname . '_category';
$purposevar = $classname . '_' . $pluginname . '_purpose';
} else {
$categoryvar = $classname . '_category';
$purposevar = $classname . '_purpose';
}
return [
$purposevar,
$categoryvar
];
}
/**
* Returns the default purpose id and category id for the provided context level.
*
* The caller code is responsible of checking that $contextlevel is an integer.
*
* @param int $contextlevel The context level.
* @param string $pluginname The name of the plugin associated with the context level.
* @return int[]|false[]
*/
public static function get_defaults($contextlevel, $pluginname = '') {
$classname = \context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
$purposeid = get_config('tool_dataprivacy', $purposevar);
$categoryid = get_config('tool_dataprivacy', $categoryvar);
if (!empty($pluginname)) {
list($purposevar, $categoryvar) = self::var_names_from_context($classname);
// If the plugin-level doesn't have a default purpose set, try the context level.
if ($purposeid == false) {
$purposeid = get_config('tool_dataprivacy', $purposevar);
}
// If the plugin-level doesn't have a default category set, try the context level.
if ($categoryid == false) {
$categoryid = get_config('tool_dataprivacy', $categoryvar);
}
}
if (empty($purposeid)) {
$purposeid = context_instance::NOTSET;
}
if (empty($categoryid)) {
$categoryid = context_instance::NOTSET;
}
return [$purposeid, $categoryid];
}
/**
* Are data registry defaults set?
*
* At least the system defaults need to be set.
*
* @return bool
*/
public static function defaults_set() {
list($purposeid, $categoryid) = self::get_defaults(CONTEXT_SYSTEM);
if (empty($purposeid) || empty($categoryid)) {
return false;
}
return true;
}
/**
* Returns all site categories that are visible to the current user.
*
* @return \coursecat[]
*/
public static function get_site_categories() {
global $DB;
if (method_exists('\coursecat', 'get_all')) {
$categories = \coursecat::get_all(['returnhidden' => true]);
} else {
// Fallback (to be removed once this gets integrated into master).
$ids = $DB->get_fieldset_select('course_categories', 'id', '');
$categories = \coursecat::get_many($ids);
}
foreach ($categories as $key => $category) {
if (!$category->is_uservisible()) {
unset($categories[$key]);
}
}
return $categories;
}
/**
* Returns the roles assigned to the provided level.
*
* Important to note that it returns course-level assigned roles
* if the provided context level is below course.
*
* @param \context $context
* @return array
*/
public static function get_subject_scope(\context $context) {
if ($contextcourse = $context->get_course_context(false)) {
// Below course level we look at module or block level roles + course-assigned roles.
$courseroles = get_roles_with_assignment_on_context($contextcourse);
$roles = $courseroles + get_roles_with_assignment_on_context($context);
} else {
// We list category + system for others (we don't work with user instances so no need to work about them).
$roles = get_roles_used_in_context($context);
}
return array_map(function($role) {
if ($role->name) {
return $role->name;
} else {
return $role->shortname;
}
}, $roles);
}
/**
* Returns the effective value given a context instance
*
* @param \context $context
* @param string $element 'category' or 'purpose'
* @param int|false $forcedvalue Use this value as if this was this context instance value.
* @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element
*/
public static function get_effective_context_value(\context $context, $element, $forcedvalue = false) {
global $DB;
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
}
$fieldname = $element . 'id';
if (!empty($forcedvalue) && ($forcedvalue == context_instance::INHERIT)) {
// Do not include the current context when calculating the value.
// This has the effect that an inheritted value is calculated.
$parentcontextids = $context->get_parent_context_ids(false);
} else if (!empty($forcedvalue) && ($forcedvalue != context_instance::NOTSET)) {
return self::get_element_instance($element, $forcedvalue);
} else {
// Fetch all parent contexts, including self.
$parentcontextids = $context->get_parent_context_ids(true);
}
list($insql, $inparams) = $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED);
$inparams['contextmodule'] = CONTEXT_MODULE;
if ('purpose' === $element) {
$elementjoin = 'LEFT JOIN {tool_dataprivacy_purpose} ele ON ctxins.purposeid = ele.id';
$elementfields = purpose::get_sql_fields('ele', 'ele');
} else {
$elementjoin = 'LEFT JOIN {tool_dataprivacy_category} ele ON ctxins.categoryid = ele.id';
$elementfields = category::get_sql_fields('ele', 'ele');
}
$contextfields = \context_helper::get_preload_record_columns_sql('ctx');
$fields = implode(', ', ['ctx.id', 'm.name AS modname', $contextfields, $elementfields]);
$sql = "SELECT $fields
FROM {context} ctx
LEFT JOIN {tool_dataprivacy_ctxinstance} ctxins ON ctx.id = ctxins.contextid
LEFT JOIN {course_modules} cm ON ctx.contextlevel = :contextmodule AND ctx.instanceid = cm.id
LEFT JOIN {modules} m ON m.id = cm.module
{$elementjoin}
WHERE ctx.id {$insql}
ORDER BY ctx.path DESC";
$contextinstances = $DB->get_records_sql($sql, $inparams);
// Check whether this context is a user context, or a child of a user context.
// All children of a User context share the same context and cannot be set individually.
foreach ($contextinstances as $record) {
\context_helper::preload_from_record($record);
$parent = \context::instance_by_id($record->id, false);
if ($parent->contextlevel == CONTEXT_USER) {
// Use the context level value for the user.
return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
}
}
foreach ($contextinstances as $record) {
$parent = \context::instance_by_id($record->id, false);
$checkcontextlevel = false;
if (empty($record->eleid)) {
$checkcontextlevel = true;
}
if (!empty($forcedvalue) && context_instance::NOTSET == $forcedvalue) {
$checkcontextlevel = true;
}
if ($checkcontextlevel) {
// Check for a value at the contextlevel
$forplugin = empty($record->modname) ? '' : $record->modname;
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
$parent->contextlevel, false, false, $forplugin);
$instancevalue = $$fieldname;
if (context_instance::NOTSET != $instancevalue && context_instance::INHERIT != $instancevalue) {
// There is an actual value. Return it.
return self::get_element_instance($element, $instancevalue);
}
} else {
$elementclass = "\\tool_dataprivacy\\{$element}";
$instance = new $elementclass(null, $elementclass::extract_record($record, 'ele'));
$instance->validate();
return $instance;
}
}
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
/**
* Returns the effective value for a context level.
*
* Note that this is different from the effective default context level
* (see get_effective_default_contextlevel_purpose_and_category) as this is returning
* the value set in the data registry, not in the defaults page.
*
* @param int $contextlevel
* @param string $element 'category' or 'purpose'
* @return \tool_dataprivacy\purpose|false
*/
public static function get_effective_contextlevel_value($contextlevel, $element) {
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
}
$fieldname = $element . 'id';
if ($contextlevel != CONTEXT_SYSTEM && $contextlevel != CONTEXT_USER) {
throw new \coding_exception('Only context_system and context_user values can be retrieved, no other context levels ' .
'have a purpose or a category.');
}
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
// Note: The $$fieldname points to either $purposeid, or $categoryid.
if (context_instance::NOTSET != $$fieldname && context_instance::INHERIT != $$fieldname) {
// There is a specific value set.
return self::get_element_instance($element, $$fieldname);
}
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
/**
* Returns the effective default purpose and category for a context level.
*
* @param int $contextlevel
* @param int|bool $forcedpurposevalue Use this value as if this was this context level purpose.
* @param int|bool $forcedcategoryvalue Use this value as if this was this context level category.
* @param string $component The name of the component to check.
* @return int[]
*/
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
$forcedcategoryvalue = false, $component = '') {
// Get the defaults for this context level.
list($purposeid, $categoryid) = self::get_defaults($contextlevel, $component);
// Honour forced values.
if ($forcedpurposevalue) {
$purposeid = $forcedpurposevalue;
}
if ($forcedcategoryvalue) {
$categoryid = $forcedcategoryvalue;
}
if ($contextlevel == CONTEXT_USER) {
// Only user context levels inherit from a parent context level.
list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
$purposeid = (int)$parentpurposeid;
}
if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
$categoryid = $parentcategoryid;
}
}
return [$purposeid, $categoryid];
}
/**
* Returns an instance of the provided element.
*
* @throws \coding_exception
* @param string $element The element name 'purpose' or 'category'
* @param int $id The element id
* @return \core\persistent
*/
private static function get_element_instance($element, $id) {
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('No other elements than purpose and category are allowed');
}
$classname = '\tool_dataprivacy\\' . $element;
return new $classname($id);
}
}
@@ -0,0 +1,229 @@
<?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 for loading/storing data requests from the DB.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
use core\persistent;
/**
* Class for loading/storing competencies from the DB.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_request extends persistent {
/** The table name this persistent object maps to. */
const TABLE = 'tool_dataprivacy_request';
/** Data request created manually. */
const DATAREQUEST_CREATION_MANUAL = 0;
/** Data request created automatically. */
const DATAREQUEST_CREATION_AUTO = 1;
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return [
'type' => [
'choices' => [
api::DATAREQUEST_TYPE_EXPORT,
api::DATAREQUEST_TYPE_DELETE,
api::DATAREQUEST_TYPE_OTHERS,
],
'type' => PARAM_INT
],
'comments' => [
'type' => PARAM_TEXT,
'default' => ''
],
'commentsformat' => [
'choices' => [
FORMAT_HTML,
FORMAT_MOODLE,
FORMAT_PLAIN,
FORMAT_MARKDOWN
],
'type' => PARAM_INT,
'default' => FORMAT_PLAIN
],
'userid' => [
'default' => 0,
'type' => PARAM_INT
],
'requestedby' => [
'default' => 0,
'type' => PARAM_INT
],
'status' => [
'default' => api::DATAREQUEST_STATUS_PENDING,
'choices' => [
api::DATAREQUEST_STATUS_PENDING,
api::DATAREQUEST_STATUS_PREPROCESSING,
api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
api::DATAREQUEST_STATUS_APPROVED,
api::DATAREQUEST_STATUS_PROCESSING,
api::DATAREQUEST_STATUS_COMPLETE,
api::DATAREQUEST_STATUS_CANCELLED,
api::DATAREQUEST_STATUS_REJECTED,
api::DATAREQUEST_STATUS_DOWNLOAD_READY,
api::DATAREQUEST_STATUS_EXPIRED,
api::DATAREQUEST_STATUS_DELETED,
],
'type' => PARAM_INT
],
'dpo' => [
'default' => 0,
'type' => PARAM_INT,
'null' => NULL_ALLOWED
],
'dpocomment' => [
'default' => '',
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED
],
'dpocommentformat' => [
'choices' => [
FORMAT_HTML,
FORMAT_MOODLE,
FORMAT_PLAIN,
FORMAT_MARKDOWN
],
'type' => PARAM_INT,
'default' => FORMAT_PLAIN
],
'creationmethod' => [
'default' => self::DATAREQUEST_CREATION_MANUAL,
'choices' => [
self::DATAREQUEST_CREATION_MANUAL,
self::DATAREQUEST_CREATION_AUTO
],
'type' => PARAM_INT
],
];
}
/**
* Determines whether a completed data export request has expired.
* The response will be valid regardless of the expiry scheduled task having run.
*
* @param data_request $request the data request object whose expiry will be checked.
* @return bool true if the request has expired.
*/
public static function is_expired(data_request $request) {
$result = false;
// Only export requests expire.
if ($request->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
switch ($request->get('status')) {
// Expired requests are obviously expired.
case api::DATAREQUEST_STATUS_EXPIRED:
$result = true;
break;
// Complete requests are expired if the expiry time has elapsed.
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
$result = true;
}
break;
}
}
return $result;
}
/**
* Fetch completed data requests which are due to expire.
*
* @param int $userid Optional user ID to filter by.
*
* @return array Details of completed requests which are due to expire.
*/
public static function get_expired_requests($userid = 0) {
global $DB;
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
$expirytime = strtotime("-{$expiryseconds} second");
$table = self::TABLE;
$sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
$params = array(
'export_type' => api::DATAREQUEST_TYPE_EXPORT,
'completestatus' => api::DATAREQUEST_STATUS_DOWNLOAD_READY,
'expirytime' => $expirytime,
);
$sort = 'id';
$fields = 'id, userid';
// Filter by user ID if specified.
if ($userid > 0) {
$sqlwhere .= ' AND (userid = :userid OR requestedby = :requestedby)';
$params['userid'] = $userid;
$params['requestedby'] = $userid;
}
return $DB->get_records_select_menu($table, $sqlwhere, $params, $sort, $fields, 0, 2000);
}
/**
* Expire a given set of data requests.
* Update request status and delete the files.
*
* @param array $expiredrequests [requestid => userid]
*
* @return void
*/
public static function expire($expiredrequests) {
global $DB;
$ids = array_keys($expiredrequests);
if (count($ids) > 0) {
list($insql, $inparams) = $DB->get_in_or_equal($ids);
$initialparams = array(api::DATAREQUEST_STATUS_EXPIRED, time());
$params = array_merge($initialparams, $inparams);
$update = "UPDATE {" . self::TABLE . "}
SET status = ?, timemodified = ?
WHERE id $insql";
if ($DB->execute($update, $params)) {
$fs = get_file_storage();
foreach ($expiredrequests as $id => $userid) {
$usercontext = \context_user::instance($userid);
$fs->delete_area_files($usercontext->id, 'tool_dataprivacy', 'export', $id);
}
}
}
}
}
@@ -0,0 +1,63 @@
<?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/>.
/**
* Event observers supported by this module.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\event;
use \tool_dataprivacy\api;
use \tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
/**
* Event observers supported by this module.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_deleted_observer {
/**
* Create user data deletion request when the user is deleted.
*
* @param \core\event\user_deleted $event
*/
public static function create_delete_data_request(\core\event\user_deleted $event) {
// Automatic creation of deletion requests must be enabled.
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
$requesttypes = [api::DATAREQUEST_TYPE_DELETE];
$requeststatuses = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DELETED];
$hasongoingdeleterequests = api::has_ongoing_request($event->objectid, $requesttypes[0]);
$hascompleteddeleterequest = (api::get_data_requests_count($event->objectid, $requeststatuses,
$requesttypes) > 0) ? true : false;
if (!$hasongoingdeleterequests && !$hascompleteddeleterequest) {
api::create_data_request($event->objectid, $requesttypes[0],
get_string('datarequestcreatedupondelete', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO);
}
}
}
}
@@ -0,0 +1,209 @@
<?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 that represents an expired context.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use dml_exception;
defined('MOODLE_INTERNAL') || die();
/**
* Class that represents an expired context.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_context extends \core\persistent {
/**
* Database table.
*/
const TABLE = 'tool_dataprivacy_ctxexpired';
/**
* Expired contexts with no delete action scheduled.
*/
const STATUS_EXPIRED = 0;
/**
* Expired contexts approved for deletion.
*/
const STATUS_APPROVED = 1;
/**
* Already processed expired contexts.
*/
const STATUS_CLEANED = 2;
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'contextid' => array(
'type' => PARAM_INT,
'description' => 'The context id.',
),
'status' => array(
'choices' => [
self::STATUS_EXPIRED,
self::STATUS_APPROVED,
self::STATUS_CLEANED,
],
'type' => PARAM_INT,
'description' => 'The deletion status of the context.',
),
);
}
/**
* Returns expired_contexts instances that match the provided level and status.
*
* @param int $contextlevel The context level filter criterion.
* @param bool $status The expired context record's status.
* @param string $sort The sort column. Must match the column name in {tool_dataprivacy_ctxexpired} table
* @param int $offset The query offset.
* @param int $limit The query limit.
* @return expired_context[]
* @throws dml_exception
*/
public static function get_records_by_contextlevel($contextlevel = null, $status = false, $sort = 'timecreated',
$offset = 0, $limit = 0) {
global $DB;
$sql = "SELECT expiredctx.*
FROM {" . self::TABLE . "} expiredctx
JOIN {context} ctx
ON ctx.id = expiredctx.contextid";
$params = [];
$conditions = [];
if (!empty($contextlevel)) {
$conditions[] = "ctx.contextlevel = :contextlevel";
$params['contextlevel'] = intval($contextlevel);
}
if ($status !== false) {
$conditions[] = "expiredctx.status = :status";
$params['status'] = intval($status);
}
if (!empty($conditions)) {
$sql .= ' WHERE ' . implode(' AND ', $conditions);
}
$sql .= " ORDER BY expiredctx.{$sort}";
$records = $DB->get_records_sql($sql, $params, $offset, $limit);
// We return class instances.
$instances = array();
foreach ($records as $key => $record) {
$instances[$key] = new static(0, $record);
}
return $instances;
}
/**
* Returns the number of expired_contexts instances that match the provided level and status.
*
* @param int $contextlevel
* @param bool $status
* @return int
* @throws dml_exception
*/
public static function get_record_count_by_contextlevel($contextlevel = null, $status = false) {
global $DB;
$sql = "SELECT COUNT(1)
FROM {" . self::TABLE . "} expiredctx
JOIN {context} ctx
ON ctx.id = expiredctx.contextid";
$conditions = [];
$params = [];
if (!empty($contextlevel)) {
$conditions[] = "ctx.contextlevel = :contextlevel";
$params['contextlevel'] = intval($contextlevel);
}
if ($status !== false) {
$sql .= " AND expiredctx.status = :status";
$params['status'] = intval($status);
}
if (!empty($conditions)) {
$sql .= ' WHERE ' . implode(' AND ', $conditions);
}
return $DB->count_records_sql($sql, $params);
}
/**
* Create a new expired_context based on the context, and expiry_info object.
*
* @param \context $context
* @param expiry_info $info
* @return expired_context
*/
public static function create_from_expiry_info(\context $context, expiry_info $info) {
$record = (object) [
'contextid' => $context->id,
'status' => self::STATUS_EXPIRED,
];
$expiredcontext = new static(0, $record);
$expiredcontext->save();
return $expiredcontext;
}
/**
* Update the expired_context from an expiry_info object which relates to this context.
*
* @param expiry_info $info
* @return $this
*/
public function update_from_expiry_info(expiry_info $info) {
return $this;
}
/**
* Check whether this expired_context record is in a state ready for deletion to actually take place.
*
* @return bool
*/
public function can_process_deletion() {
return ($this->get('status') == self::STATUS_APPROVED);
}
/**
* Check whether this expired_context record has already been cleaned.
*
* @return bool
*/
public function is_complete() {
return ($this->get('status') == self::STATUS_CLEANED);
}
}
@@ -0,0 +1,713 @@
<?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/>.
/**
* Expired contexts manager.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use core_privacy\manager;
use tool_dataprivacy\expired_context;
defined('MOODLE_INTERNAL') || die();
/**
* Expired contexts manager.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_contexts_manager {
/**
* Number of deleted contexts for each scheduled task run.
*/
const DELETE_LIMIT = 200;
/** @var progress_trace The log progress tracer */
protected $progresstracer = null;
/** @var manager The privacy manager */
protected $manager = null;
/**
* Flag expired contexts as expired.
*
* @return int[] The number of contexts flagged as expired for courses, and users.
*/
public function flag_expired_contexts() {
if (!$this->check_requirements()) {
return [0, 0];
}
// Clear old and stale records first.
static::clear_old_records();
$data = static::get_nested_expiry_info_for_courses();
$coursecount = 0;
foreach ($data as $expiryrecord) {
if ($this->update_from_expiry_info($expiryrecord)) {
$coursecount++;
}
}
$data = static::get_nested_expiry_info_for_user();
$usercount = 0;
foreach ($data as $expiryrecord) {
if ($this->update_from_expiry_info($expiryrecord)) {
$usercount++;
}
}
return [$coursecount, $usercount];
}
/**
* Clear old and stale records.
*/
protected static function clear_old_records() {
global $DB;
$sql = "SELECT dpctx.*
FROM {tool_dataprivacy_ctxexpired} dpctx
LEFT JOIN {context} ctx ON ctx.id = dpctx.contextid
WHERE ctx.id IS NULL";
$orphaned = $DB->get_recordset_sql($sql);
foreach ($orphaned as $orphan) {
$expiredcontext = new expired_context(0, $orphan);
$expiredcontext->delete();
}
// Delete any child of a user context.
$parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
$params = [
'contextuser' => CONTEXT_USER,
];
$sql = "SELECT dpctx.*
FROM {tool_dataprivacy_ctxexpired} dpctx
WHERE dpctx.contextid IN (
SELECT ctx.id
FROM {context} ctxuser
JOIN {context} ctx ON ctx.path LIKE {$parentpath}
WHERE ctxuser.contextlevel = :contextuser
)";
$userchildren = $DB->get_recordset_sql($sql, $params);
foreach ($userchildren as $child) {
$expiredcontext = new expired_context(0, $child);
$expiredcontext->delete();
}
}
/**
* Get the full nested set of expiry data relating to all contexts.
*
* @param string $contextpath A contexpath to restrict results to
* @return \stdClass[]
*/
protected static function get_nested_expiry_info($contextpath = '') {
$coursepaths = self::get_nested_expiry_info_for_courses($contextpath);
$userpaths = self::get_nested_expiry_info_for_user($contextpath);
return array_merge($coursepaths, $userpaths);
}
/**
* Get the full nested set of expiry data relating to course-related contexts.
*
* @param string $contextpath A contexpath to restrict results to
* @return \stdClass[]
*/
protected static function get_nested_expiry_info_for_courses($contextpath = '') {
global $DB;
$contextfields = \context_helper::get_preload_record_columns_sql('ctx');
$expiredfields = expired_context::get_sql_fields('expiredctx', 'expiredctx');
$purposefields = 'dpctx.purposeid';
$coursefields = 'ctxcourse.expirydate AS expirydate';
$fields = implode(', ', ['ctx.id', $contextfields, $expiredfields, $coursefields, $purposefields]);
// We want all contexts at course-dependant levels.
$parentpath = $DB->sql_concat('ctxcourse.path', "'/%'");
// This SQL query returns all course-dependant contexts (including the course context)
// which course end date already passed.
// This is ordered by the context path in reverse order, which will give the child nodes before any parent node.
$params = [
'contextlevel' => CONTEXT_COURSE,
];
$where = '';
if (!empty($contextpath)) {
$where = "WHERE (ctx.path = :pathmatchexact OR ctx.path LIKE :pathmatchchildren)";
$params['pathmatchexact'] = $contextpath;
$params['pathmatchchildren'] = "{$contextpath}/%";
}
$sql = "SELECT $fields
FROM {context} ctx
JOIN (
SELECT c.enddate AS expirydate, subctx.path
FROM {context} subctx
JOIN {course} c
ON subctx.contextlevel = :contextlevel
AND subctx.instanceid = c.id
AND c.format != 'site'
) ctxcourse
ON ctx.path LIKE {$parentpath} OR ctx.path = ctxcourse.path
LEFT JOIN {tool_dataprivacy_ctxinstance} dpctx
ON dpctx.contextid = ctx.id
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx
ON ctx.id = expiredctx.contextid
{$where}
ORDER BY ctx.path DESC";
return self::get_nested_expiry_info_from_sql($sql, $params);
}
/**
* Get the full nested set of expiry data.
*
* @param string $contextpath A contexpath to restrict results to
* @return \stdClass[]
*/
protected static function get_nested_expiry_info_for_user($contextpath = '') {
global $DB;
$contextfields = \context_helper::get_preload_record_columns_sql('ctx');
$expiredfields = expired_context::get_sql_fields('expiredctx', 'expiredctx');
$purposefields = 'dpctx.purposeid';
$userfields = 'u.lastaccess AS expirydate';
$fields = implode(', ', ['ctx.id', $contextfields, $expiredfields, $userfields, $purposefields]);
// We want all contexts at user-dependant levels.
$parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
// This SQL query returns all user-dependant contexts (including the user context)
// This is ordered by the context path in reverse order, which will give the child nodes before any parent node.
$params = [
'contextlevel' => CONTEXT_USER,
];
$where = '';
if (!empty($contextpath)) {
$where = "AND ctx.path = :pathmatchexact";
$params['pathmatchexact'] = $contextpath;
}
$sql = "SELECT $fields, u.deleted AS userdeleted
FROM {context} ctx
JOIN {user} u ON ctx.instanceid = u.id
LEFT JOIN {tool_dataprivacy_ctxinstance} dpctx
ON dpctx.contextid = ctx.id
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx
ON ctx.id = expiredctx.contextid
WHERE ctx.contextlevel = :contextlevel {$where}
ORDER BY ctx.path DESC";
return self::get_nested_expiry_info_from_sql($sql, $params);
}
/**
* Get the full nested set of expiry data given appropriate SQL.
* Only contexts which have expired will be included.
*
* @param string $sql The SQL used to select the nested information.
* @param array $params The params required by the SQL.
* @return \stdClass[]
*/
protected static function get_nested_expiry_info_from_sql($sql, array $params) {
global $DB;
$fulllist = $DB->get_recordset_sql($sql, $params);
$datalist = [];
$expiredcontents = [];
$pathstoskip = [];
foreach ($fulllist as $record) {
\context_helper::preload_from_record($record);
$context = \context::instance_by_id($record->id, false);
if (!self::is_eligible_for_deletion($pathstoskip, $context)) {
// We should skip this context, and therefore all of it's children.
$datalist = array_filter($datalist, function($data, $path) use ($context) {
// Remove any child of this context.
// Technically this should never be fulfilled because the query is ordered in path DESC, but is kept
// in to be certain.
return (false === strpos($path, "{$context->path}/"));
}, ARRAY_FILTER_USE_BOTH);
if ($record->expiredctxid) {
// There was previously an expired context record.
// Delete it to be on the safe side.
$expiredcontext = new expired_context(null, expired_context::extract_record($record, 'expiredctx'));
$expiredcontext->delete();
}
continue;
}
$purposevalue = $record->purposeid !== null ? $record->purposeid : context_instance::NOTSET;
$purpose = api::get_effective_context_purpose($context, $purposevalue);
if ($context instanceof \context_user && !empty($record->userdeleted)) {
$expiryinfo = static::get_expiry_info($purpose, $record->userdeleted);
} else {
$expiryinfo = static::get_expiry_info($purpose, $record->expirydate);
}
foreach ($datalist as $path => $data) {
// Merge with already-processed children.
if (strpos($path, $context->path) !== 0) {
continue;
}
$expiryinfo->merge_with_child($data->info);
}
$datalist[$context->path] = (object) [
'context' => $context,
'record' => $record,
'purpose' => $purpose,
'info' => $expiryinfo,
];
}
$fulllist->close();
return $datalist;
}
/**
* Check whether the supplied context would be elible for deletion.
*
* @param array $pathstoskip A set of paths which should be skipped
* @param \context $context
* @return bool
*/
protected static function is_eligible_for_deletion(array &$pathstoskip, \context $context) {
$shouldskip = false;
// Check whether any of the child contexts are ineligble.
$shouldskip = !empty(array_filter($pathstoskip, function($path) use ($context) {
// If any child context has already been skipped then it will appear in this list.
// Since paths include parents, test if the context under test appears as the haystack in the skipped
// context's needle.
return false !== (strpos($context->path, $path));
}));
if (!$shouldskip && $context instanceof \context_user) {
// The context instanceid is the user's ID.
if (isguestuser($context->instanceid) || is_siteadmin($context->instanceid)) {
// This is an admin, or the guest and cannot be deleted.
$shouldskip = true;
}
if (!$shouldskip) {
$courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
$requireenddate = self::require_all_end_dates_for_user_deletion();
foreach ($courses as $course) {
if (empty($course->enddate)) {
// This course has no end date.
if ($requireenddate) {
// Course end dates are required, and this course has no end date.
$shouldskip = true;
break;
}
// Course end dates are not required. The subsequent checks are pointless at this time so just
// skip them.
continue;
}
if ($course->enddate >= time()) {
// This course is still in the future.
$shouldskip = true;
break;
}
// This course has an end date which is in the past.
if (!self::is_course_expired($course)) {
// This course has not expired yet.
$shouldskip = true;
break;
}
}
}
}
if ($shouldskip) {
// Add this to the list of contexts to skip for parentage checks.
$pathstoskip[] = $context->path;
}
return !$shouldskip;
}
/**
* Deletes the expired contexts.
*
* @return int[] The number of deleted contexts.
*/
public function process_approved_deletions() {
if (!$this->check_requirements()) {
return [0, 0];
}
$expiredcontexts = expired_context::get_records(['status' => expired_context::STATUS_APPROVED]);
$totalprocessed = 0;
$usercount = 0;
$coursecount = 0;
foreach ($expiredcontexts as $expiredctx) {
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
if (empty($context)) {
// Unable to process this request further.
// We have no context to delete.
$expiredctx->delete();
continue;
}
if ($this->delete_expired_context($expiredctx)) {
if ($context instanceof \context_user) {
$usercount++;
} else {
$coursecount++;
}
$totalprocessed++;
if ($totalprocessed >= $this->get_delete_limit()) {
break;
}
}
}
return [$coursecount, $usercount];
}
/**
* Deletes user data from the provided context.
*
* @param expired_context $expiredctx
* @return \context|false
*/
protected function delete_expired_context(expired_context $expiredctx) {
$context = \context::instance_by_id($expiredctx->get('contextid'));
$this->get_progress()->output("Deleting context {$context->id} - " . $context->get_context_name(true, true));
// Update the expired_context and verify that it is still ready for deletion.
$expiredctx = $this->update_expired_context($expiredctx);
if (empty($expiredctx)) {
$this->get_progress()->output("Context has changed since approval and is no longer pending approval. Skipping", 1);
return false;
}
if (!$expiredctx->can_process_deletion()) {
// This only happens if the record was updated after being first fetched.
$this->get_progress()->output("Context has changed since approval and must be re-approved. Skipping", 1);
$expiredctx->set('status', expired_context::STATUS_EXPIRED);
$expiredctx->save();
return false;
}
$privacymanager = $this->get_privacy_manager();
if ($context instanceof \context_user) {
$this->delete_expired_user_context($expiredctx);
} else {
// This context is fully expired - that is that the default retention period has been reached.
$privacymanager->delete_data_for_all_users_in_context($context);
}
// Mark the record as cleaned.
$expiredctx->set('status', expired_context::STATUS_CLEANED);
$expiredctx->save();
return $context;
}
/**
* Deletes user data from the provided user context.
*
* @param expired_context $expiredctx
*/
protected function delete_expired_user_context(expired_context $expiredctx) {
global $DB;
$contextid = $expiredctx->get('contextid');
$context = \context::instance_by_id($contextid);
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
$privacymanager = $this->get_privacy_manager();
// Delete all child contexts of the user context.
$parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
$params = [
'contextlevel' => CONTEXT_USER,
'contextid' => $expiredctx->get('contextid'),
];
$fields = \context_helper::get_preload_record_columns_sql('ctx');
$sql = "SELECT ctx.id, $fields
FROM {context} ctxuser
JOIN {context} ctx ON ctx.path LIKE {$parentpath}
WHERE ctxuser.contextlevel = :contextlevel AND ctxuser.id = :contextid
ORDER BY ctx.path DESC";
$children = $DB->get_recordset_sql($sql, $params);
foreach ($children as $child) {
\context_helper::preload_from_record($child);
$context = \context::instance_by_id($child->id);
$privacymanager->delete_data_for_all_users_in_context($context);
}
$children->close();
// Delete all unprotected data that the user holds.
$approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
$contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
foreach ($contextlistcollection as $contextlist) {
$contextids = [];
$approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
$user,
$contextlist->get_component(),
$contextlist->get_contextids()
));
}
$privacymanager->delete_data_for_user($approvedlistcollection, $this->get_progress());
// Delete the user context.
$context = \context::instance_by_id($expiredctx->get('contextid'));
$privacymanager->delete_data_for_all_users_in_context($context);
// This user is now fully expired - finish by deleting the user.
delete_user($user);
}
/**
* Whether end dates are required on all courses in order for a user to be expired from them.
*
* @return bool
*/
protected static function require_all_end_dates_for_user_deletion() {
$requireenddate = get_config('tool_dataprivacy', 'requireallenddatesforuserdeletion');
return !empty($requireenddate);
}
/**
* Check that the requirements to start deleting contexts are satisified.
*
* @return bool
*/
protected function check_requirements() {
if (!data_registry::defaults_set()) {
return false;
}
return true;
}
/**
* Check whether a date is beyond the specified period.
*
* @param string $period The Expiry Period
* @param int $comparisondate The date for comparison
* @return bool
*/
protected static function has_expired($period, $comparisondate) {
$dt = new \DateTime();
$dt->setTimestamp($comparisondate);
$dt->add(new \DateInterval($period));
return (time() >= $dt->getTimestamp());
}
/**
* Get the expiry info object for the specified purpose and comparison date.
*
* @param purpose $purpose The purpose of this context
* @param int $comparisondate The date for comparison
* @return expiry_info
*/
protected static function get_expiry_info(purpose $purpose, $comparisondate = 0) {
if (empty($comparisondate)) {
// The date is empty, therefore this context cannot be considered for automatic expiry.
$defaultexpired = false;
} else {
$defaultexpired = static::has_expired($purpose->get('retentionperiod'), $comparisondate);
}
return new expiry_info($defaultexpired);
}
/**
* Update or delete the expired_context from the expiry_info object.
* This function depends upon the data structure returned from get_nested_expiry_info.
*
* If the context is expired in any way, then an expired_context will be returned, otherwise null will be returned.
*
* @param \stdClass $expiryrecord
* @return expired_context|null
*/
protected function update_from_expiry_info(\stdClass $expiryrecord) {
if ($expiryrecord->info->is_any_expired()) {
// The context is expired in some fashion.
// Create or update as required.
if ($expiryrecord->record->expiredctxid) {
$expiredcontext = new expired_context(null, expired_context::extract_record($expiryrecord->record, 'expiredctx'));
$expiredcontext->update_from_expiry_info($expiryrecord->info);
if ($expiredcontext->is_complete()) {
return null;
}
} else {
$expiredcontext = expired_context::create_from_expiry_info($expiryrecord->context, $expiryrecord->info);
}
return $expiredcontext;
} else {
// The context is not expired.
if ($expiryrecord->record->expiredctxid) {
// There was previously an expired context record, but it is no longer relevant.
// Delete it to be on the safe side.
$expiredcontext = new expired_context(null, expired_context::extract_record($expiryrecord->record, 'expiredctx'));
$expiredcontext->delete();
}
return null;
}
}
/**
* Update the expired context record.
*
* Note: You should use the return value as the provided value will be used to fetch data only.
*
* @param expired_context $expiredctx The record to update
* @return expired_context|null
*/
protected function update_expired_context(expired_context $expiredctx) {
// Fetch the context from the expired_context record.
$context = \context::instance_by_id($expiredctx->get('contextid'));
// Fetch the current nested expiry data.
$expiryrecords = self::get_nested_expiry_info($context->path);
// Find the current record.
if (empty($expiryrecords[$context->path])) {
$expiredctx->delete();
return null;
}
// Refresh the record.
// Note: Use the returned expiredctx.
$expiredctx = $this->update_from_expiry_info($expiryrecords[$context->path]);
if (empty($expiredctx)) {
return null;
}
if (!$context instanceof \context_user) {
// Where the target context is not a user, we check all children of the context.
// The expiryrecords array only contains children, fetched from the get_nested_expiry_info call above.
// No need to check that these _are_ children.
foreach ($expiryrecords as $expiryrecord) {
if ($expiryrecord->context->id === $context->id) {
// This is record for the context being tested that we checked earlier.
continue;
}
if (empty($expiryrecord->record->expiredctxid)) {
// There is no expired context record for this context.
// If there is no record, then this context cannot have been approved for removal.
return null;
}
// Fetch the expired_context object for this record.
// This needs to be updated from the expiry_info data too as there may be child changes to consider.
$expiredcontext = new expired_context(null, expired_context::extract_record($expiryrecord->record, 'expiredctx'));
$expiredcontext->update_from_expiry_info($expiryrecord->info);
if (!$expiredcontext->is_complete()) {
return null;
}
}
}
return $expiredctx;
}
/**
* Check whether the course has expired.
*
* @param \stdClass $course
* @return bool
*/
protected static function is_course_expired(\stdClass $course) {
$context = \context_course::instance($course->id);
$expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
return !empty($expiryrecords[$context->path]) && $expiryrecords[$context->path]->info->is_fully_expired();
}
/**
* Create a new instance of the privacy manager.
*
* @return manager
*/
protected function get_privacy_manager() {
if (null === $this->manager) {
$this->manager = new manager();
$this->manager->set_observer(new \tool_dataprivacy\manager_observer());
}
return $this->manager;
}
/**
* Fetch the limit for the maximum number of contexts to delete in one session.
*
* @return int
*/
protected function get_delete_limit() {
return self::DELETE_LIMIT;
}
/**
* Get the progress tracer.
*
* @return \progress_trace
*/
protected function get_progress() {
if (null === $this->progresstracer) {
$this->set_progress(new \text_progress_trace());
}
return $this->progresstracer;
}
/**
* Set a specific tracer for the task.
*
* @param \progress_trace $trace
* @return $this
*/
public function set_progress(\progress_trace $trace) {
$this->progresstracer = $trace;
return $this;
}
}
@@ -0,0 +1,93 @@
<?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/>.
/**
* Expiry Data.
*
* @package tool_dataprivacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use core_privacy\manager;
defined('MOODLE_INTERNAL') || die();
/**
* Expiry Data.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expiry_info {
/** @var bool Whether this context is fully expired */
protected $isexpired = false;
/**
* Constructor for the expiry_info class.
*
* @param bool $isexpired Whether the retention period for this context has expired yet.
*/
public function __construct($isexpired) {
$this->isexpired = $isexpired;
}
/**
* Whether this context has 'fully' expired.
* That is to say that the default retention period has been reached, and that there are no unexpired roles.
*
* @return bool
*/
public function is_fully_expired() {
return $this->isexpired;
}
/**
* Whether any part of this context has expired.
*
* @return bool
*/
public function is_any_expired() {
if ($this->is_fully_expired()) {
return true;
}
return false;
}
/**
* Merge this expiry_info object with another belonging to a child context in order to set the 'safest' heritage.
*
* It is not possible to delete any part of a context that is not deleted by a parent.
* So if a course's retention policy has been reached, then only parts where the children have also expired can be
* deleted.
*
* @param expiry_info $child The child record to merge with.
* @return $this
*/
public function merge_with_child(expiry_info $child) {
if ($child->is_fully_expired()) {
return $this;
}
// If the child is not fully expired, then none of the parents can be either.
$this->isexpired = false;
return $this;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
<?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 for exporting data category.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\external;
defined('MOODLE_INTERNAL') || die();
use core\external\persistent_exporter;
use tool_dataprivacy\category;
use tool_dataprivacy\context_instance;
/**
* Class for exporting field data.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category_exporter extends persistent_exporter {
/**
* Defines the persistent class.
*
* @return string
*/
protected static function define_class() {
return \tool_dataprivacy\category::class;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
/**
* Utility function that fetches a category name from the given ID.
*
* @param int $categoryid The category ID. Could be INHERIT (false, -1), NOT_SET (0), or the actual ID.
* @return string The purpose name.
*/
public static function get_name($categoryid) {
global $PAGE;
if ($categoryid === false || $categoryid == context_instance::INHERIT) {
return get_string('inherit', 'tool_dataprivacy');
} else if ($categoryid == context_instance::NOTSET) {
return get_string('notset', 'tool_dataprivacy');
} else {
$purpose = new category($categoryid);
$output = $PAGE->get_renderer('tool_dataprivacy');
$exporter = new self($purpose, ['context' => \context_system::instance()]);
$data = $exporter->export($output);
return $data->name;
}
}
}
@@ -0,0 +1,45 @@
<?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 for exporting context instance.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\external;
defined('MOODLE_INTERNAL') || die();
use core\external\persistent_exporter;
/**
* Class for exporting context instance.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class context_instance_exporter extends persistent_exporter {
/**
* Defines the persistent class.
*
* @return string
*/
protected static function define_class() {
return \tool_dataprivacy\context_instance::class;
}
}
@@ -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/>.
/**
* Class for exporting user evidence with all competencies.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\external;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use core\external\persistent_exporter;
use core_user;
use core_user\external\user_summary_exporter;
use dml_exception;
use moodle_exception;
use renderer_base;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
use tool_dataprivacy\local\helper;
/**
* Class for exporting user evidence with all competencies.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_request_exporter extends persistent_exporter {
/**
* Class definition.
*
* @return string
*/
protected static function define_class() {
return data_request::class;
}
/**
* Related objects definition.
*
* @return array
*/
protected static function define_related() {
return [
'context' => 'context',
];
}
/**
* Other properties definition.
*
* @return array
*/
protected static function define_other_properties() {
return [
'foruser' => [
'type' => user_summary_exporter::read_properties_definition(),
],
'requestedbyuser' => [
'type' => user_summary_exporter::read_properties_definition(),
'optional' => true
],
'dpouser' => [
'type' => user_summary_exporter::read_properties_definition(),
'optional' => true
],
'messagehtml' => [
'type' => PARAM_RAW,
'optional' => true
],
'typename' => [
'type' => PARAM_TEXT,
],
'typenameshort' => [
'type' => PARAM_TEXT,
],
'statuslabel' => [
'type' => PARAM_TEXT,
],
'statuslabelclass' => [
'type' => PARAM_TEXT,
],
'canreview' => [
'type' => PARAM_BOOL,
'optional' => true,
'default' => false
],
'approvedeny' => [
'type' => PARAM_BOOL,
'optional' => true,
'default' => false
],
'canmarkcomplete' => [
'type' => PARAM_BOOL,
'optional' => true,
'default' => false
],
];
}
/**
* Assign values to the defined other properties.
*
* @param renderer_base $output The output renderer object.
* @return array
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
protected function get_other_values(renderer_base $output) {
$values = [];
$foruserid = $this->persistent->get('userid');
$user = core_user::get_user($foruserid, '*', MUST_EXIST);
$userexporter = new user_summary_exporter($user);
$values['foruser'] = $userexporter->export($output);
$requestedbyid = $this->persistent->get('requestedby');
if ($requestedbyid != $foruserid) {
$user = core_user::get_user($requestedbyid, '*', MUST_EXIST);
$userexporter = new user_summary_exporter($user);
$values['requestedbyuser'] = $userexporter->export($output);
} else {
$values['requestedbyuser'] = $values['foruser'];
}
if (!empty($this->persistent->get('dpo'))) {
$dpoid = $this->persistent->get('dpo');
$user = core_user::get_user($dpoid, '*', MUST_EXIST);
$userexporter = new user_summary_exporter($user);
$values['dpouser'] = $userexporter->export($output);
}
$values['messagehtml'] = text_to_html($this->persistent->get('comments'));
$requesttype = $this->persistent->get('type');
$values['typename'] = helper::get_request_type_string($requesttype);
$values['typenameshort'] = helper::get_shortened_request_type_string($requesttype);
$values['canreview'] = false;
$values['approvedeny'] = false;
$values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
switch ($this->persistent->get('status')) {
case api::DATAREQUEST_STATUS_PENDING:
$values['statuslabelclass'] = 'label-info';
// Request can be manually completed for general enquiry requests.
$values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
break;
case api::DATAREQUEST_STATUS_PREPROCESSING:
$values['statuslabelclass'] = 'label-default';
break;
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
$values['statuslabelclass'] = 'label-info';
// DPO can review the request once it's ready.
$values['canreview'] = true;
// Whether the DPO can approve or deny the request.
$values['approvedeny'] = in_array($requesttype, [api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_TYPE_DELETE]);
break;
case api::DATAREQUEST_STATUS_APPROVED:
$values['statuslabelclass'] = 'label-info';
break;
case api::DATAREQUEST_STATUS_PROCESSING:
$values['statuslabelclass'] = 'label-info';
break;
case api::DATAREQUEST_STATUS_COMPLETE:
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
case api::DATAREQUEST_STATUS_DELETED:
$values['statuslabelclass'] = 'label-success';
break;
case api::DATAREQUEST_STATUS_CANCELLED:
$values['statuslabelclass'] = 'label-warning';
break;
case api::DATAREQUEST_STATUS_REJECTED:
$values['statuslabelclass'] = 'label-important';
break;
case api::DATAREQUEST_STATUS_EXPIRED:
$values['statuslabelclass'] = 'label-default';
break;
}
return $values;
}
}
@@ -0,0 +1,63 @@
<?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 for exporting an object containing a name and a description.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class that exports an object containing a name and a description.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class name_description_exporter extends exporter {
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'name' => [
'type' => PARAM_TEXT,
],
'description' => [
'type' => PARAM_TEXT,
],
];
}
}
@@ -0,0 +1,168 @@
<?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 for exporting data purpose.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\external;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use core\external\persistent_exporter;
use DateInterval;
use Exception;
use renderer_base;
use tool_dataprivacy\context_instance;
use tool_dataprivacy\purpose;
/**
* Class for exporting field data.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class purpose_exporter extends persistent_exporter {
/**
* Defines the persistent class.
*
* @return string
*/
protected static function define_class() {
return purpose::class;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return array(
'context' => 'context',
);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'formattedretentionperiod' => [
'type' => PARAM_TEXT
],
'formattedlawfulbases' => [
'type' => name_description_exporter::read_properties_definition(),
'multiple' => true
],
'formattedsensitivedatareasons' => [
'type' => name_description_exporter::read_properties_definition(),
'multiple' => true,
'optional' => true
],
];
}
/**
* Return other properties.
*
* @param renderer_base $output
* @return array
* @throws coding_exception
* @throws Exception
*/
protected function get_other_values(renderer_base $output) {
$values = [];
$formattedbases = [];
$lawfulbases = explode(',', $this->persistent->get('lawfulbases'));
if (!empty($lawfulbases)) {
foreach ($lawfulbases as $basis) {
if (empty(trim($basis))) {
continue;
}
$formattedbases[] = (object)[
'name' => get_string($basis . '_name', 'tool_dataprivacy'),
'description' => get_string($basis . '_description', 'tool_dataprivacy')
];
}
}
$values['formattedlawfulbases'] = $formattedbases;
$formattedsensitivereasons = [];
$sensitivereasons = explode(',', $this->persistent->get('sensitivedatareasons'));
if (!empty($sensitivereasons)) {
foreach ($sensitivereasons as $reason) {
if (empty(trim($reason))) {
continue;
}
$formattedsensitivereasons[] = (object)[
'name' => get_string($reason . '_name', 'tool_dataprivacy'),
'description' => get_string($reason . '_description', 'tool_dataprivacy')
];
}
}
$values['formattedsensitivedatareasons'] = $formattedsensitivereasons;
$retentionperiod = $this->persistent->get('retentionperiod');
if ($retentionperiod) {
$interval = new DateInterval($retentionperiod);
// It is one or another.
if ($interval->y) {
$formattedtime = get_string('numyears', 'moodle', $interval->format('%y'));
} else if ($interval->m) {
$formattedtime = get_string('nummonths', 'moodle', $interval->format('%m'));
} else if ($interval->d) {
$formattedtime = get_string('numdays', 'moodle', $interval->format('%d'));
} else {
$formattedtime = get_string('retentionperiodzero', 'tool_dataprivacy');
}
} else {
$formattedtime = get_string('retentionperiodnotdefined', 'tool_dataprivacy');
}
$values['formattedretentionperiod'] = $formattedtime;
return $values;
}
/**
* Utility function that fetches a purpose name from the given ID.
*
* @param int $purposeid The purpose ID. Could be INHERIT (false, -1), NOT_SET (0), or the actual ID.
* @return string The purpose name.
*/
public static function get_name($purposeid) {
global $PAGE;
if ($purposeid === false || $purposeid == context_instance::INHERIT) {
return get_string('inherit', 'tool_dataprivacy');
} else if ($purposeid == context_instance::NOTSET) {
return get_string('notset', 'tool_dataprivacy');
} else {
$purpose = new purpose($purposeid);
$output = $PAGE->get_renderer('tool_dataprivacy');
$exporter = new self($purpose, ['context' => \context_system::instance()]);
$data = $exporter->export($output);
return $data->name;
}
}
}
@@ -0,0 +1,67 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the form add/update a data category.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\form;
defined('MOODLE_INTERNAL') || die();
use core\form\persistent;
/**
* Data category form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category extends persistent {
/**
* @var The persistent class.
*/
protected static $persistentclass = 'tool_dataprivacy\\category';
/**
* Define the form - called by parent constructor
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('text', 'name', get_string('name'), 'maxlength="100"');
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', get_string('required'), 'required', null, 'server');
$mform->addRule('name', get_string('maximumchars', '', 100), 'maxlength', 100, 'server');
$mform->addElement('editor', 'description', get_string('description'), null, ['autosave' => false]);
$mform->setType('description', PARAM_CLEANHTML);
if (!empty($this->_customdata['showbuttons'])) {
if (!$this->get_persistent()->get('id')) {
$savetext = get_string('add');
} else {
$savetext = get_string('savechanges');
}
$this->add_action_buttons(true, $savetext);
}
}
}
@@ -0,0 +1,226 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the form add/update context instance data.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\form;
defined('MOODLE_INTERNAL') || die();
use tool_dataprivacy\api;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\purpose;
/**
* Context instance data form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class context_instance extends \core\form\persistent {
/**
* @var The persistent class.
*/
protected static $persistentclass = 'tool_dataprivacy\\context_instance';
/**
* Define the form - called by parent constructor
*/
public function definition() {
$this->_form->setDisableShortforms();
$this->_form->addElement('header', 'contextname', $this->_customdata['contextname']);
$subjectscope = implode(', ', $this->_customdata['subjectscope']);
if (empty($subjectscope)) {
$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);
$this->_form->addElement('hidden', 'contextid');
$this->_form->setType('contextid', PARAM_INT);
parent::add_action_buttons(false, get_string('savechanges'));
}
/**
* Adds purpose and category selectors.
*
* @param int $contextlevel Apply this context level defaults. False for no defaults.
* @return null
*/
protected function add_purpose_category($contextlevel = false) {
$mform = $this->_form;
$addcategorytext = $this->get_add_element_content(get_string('addcategory', 'tool_dataprivacy'));
$categoryselect = $mform->createElement('select', 'categoryid', null, $this->_customdata['categories']);
$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);
$addpurposetext = $this->get_add_element_content(get_string('addpurpose', 'tool_dataprivacy'));
$purposeselect = $mform->createElement('select', 'purposeid', null, $this->_customdata['purposes']);
$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');
}
}
/**
* Returns the 'add' label.
*
* It depends on the theme in use.
*
* @param string $label
* @return \renderable|string
*/
private function get_add_element_content($label) {
global $PAGE, $OUTPUT;
$bs4 = false;
$theme = $PAGE->theme;
if ($theme->name === 'boost') {
$bs4 = true;
} else {
foreach ($theme->parents as $basetheme) {
if ($basetheme === 'boost') {
$bs4 = true;
}
}
}
if (!$bs4) {
return $label;
}
return $OUTPUT->pix_icon('e/insert', $label);
}
/**
* Returns the customdata array for the provided context instance.
*
* @param \context $context
* @return array
*/
public static function get_context_instance_customdata(\context $context) {
$persistent = \tool_dataprivacy\context_instance::get_record_by_contextid($context->id, false);
if (!$persistent) {
$persistent = new \tool_dataprivacy\context_instance();
$persistent->set('contextid', $context->id);
}
$purposes = [];
foreach (api::get_purposes() as $purpose) {
$purposes[$purpose->get('id')] = $purpose;
}
$purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options($purposes);
$categoryoptions = \tool_dataprivacy\output\data_registry_page::category_options(api::get_categories());
$customdata = [
'context' => $context,
'subjectscope' => data_registry::get_subject_scope($context),
'contextname' => $context->get_context_name(),
'persistent' => $persistent,
'purposes' => $purposeoptions,
'categories' => $categoryoptions,
];
$effectivepurpose = api::get_effective_context_purpose($context);
if ($effectivepurpose) {
$customdata['currentretentionperiod'] = self::get_retention_display_text($effectivepurpose, $context->contextlevel,
$context);
$customdata['purposeretentionperiods'] = [];
foreach (array_keys($purposeoptions) as $optionvalue) {
if (isset($purposes[$optionvalue])) {
$purpose = $purposes[$optionvalue];
} else {
// Get the effective purpose if $optionvalue would be the selected value.
$purpose = api::get_effective_context_purpose($context, $optionvalue);
}
$retentionperiod = self::get_retention_display_text(
$purpose,
$context->contextlevel,
$context
);
$customdata['purposeretentionperiods'][$optionvalue] = $retentionperiod;
}
}
return $customdata;
}
/**
* Returns the purpose display text.
*
* @param purpose $effectivepurpose
* @param int $retentioncontextlevel
* @param \context $context The context, just for displaying (filters) purposes.
* @return string
*/
protected static function get_retention_display_text(purpose $effectivepurpose, $retentioncontextlevel, \context $context) {
global $PAGE;
$renderer = $PAGE->get_renderer('tool_dataprivacy');
$exporter = new \tool_dataprivacy\external\purpose_exporter($effectivepurpose, ['context' => $context]);
$exportedpurpose = $exporter->export($renderer);
switch ($retentioncontextlevel) {
case CONTEXT_COURSE:
case CONTEXT_MODULE:
case CONTEXT_BLOCK:
$str = get_string('effectiveretentionperiodcourse', 'tool_dataprivacy',
$exportedpurpose->formattedretentionperiod);
break;
case CONTEXT_USER:
$str = get_string('effectiveretentionperioduser', 'tool_dataprivacy',
$exportedpurpose->formattedretentionperiod);
break;
default:
$str = $exportedpurpose->formattedretentionperiod;
}
return $str;
}
}
@@ -0,0 +1,124 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the form add/update context level data.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\form;
defined('MOODLE_INTERNAL') || die();
use core\form\persistent;
use tool_dataprivacy\api;
use tool_dataprivacy\data_registry;
/**
* Context level data form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlevel extends context_instance {
/**
* @var The persistent class.
*/
protected static $persistentclass = 'tool_dataprivacy\\contextlevel';
/**
* Define the form - called by parent constructor
*/
public function definition() {
$this->_form->setDisableShortforms();
$this->_form->addElement('header', 'contextlevelname', $this->_customdata['contextlevelname']);
$this->add_purpose_category();
$this->_form->addElement('hidden', 'contextlevel');
$this->_form->setType('contextlevel', PARAM_INT);
parent::add_action_buttons(false, get_string('savechanges'));
}
/**
* Returns the customdata array for the provided context level.
*
* @param int $contextlevel
* @return array
*/
public static function get_contextlevel_customdata($contextlevel) {
$persistent = \tool_dataprivacy\contextlevel::get_record_by_contextlevel($contextlevel, false);
if (!$persistent) {
$persistent = new \tool_dataprivacy\contextlevel();
$persistent->set('contextlevel', $contextlevel);
}
$includeinherit = true;
if ($contextlevel == CONTEXT_SYSTEM) {
// Nothing to inherit from Site level.
$includeinherit = false;
}
$includenotset = true;
if ($contextlevel == CONTEXT_SYSTEM || $contextlevel == CONTEXT_USER) {
// No 'not set' value for system and user because we do not have defaults for them.
$includenotset = false;
}
$purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options(
api::get_purposes(), $includenotset, $includeinherit);
$categoryoptions = \tool_dataprivacy\output\data_registry_page::category_options(
api::get_categories(), $includenotset, $includeinherit);
$customdata = [
'contextlevel' => $contextlevel,
'contextlevelname' => get_string('contextlevelname' . $contextlevel, 'tool_dataprivacy'),
'persistent' => $persistent,
'purposes' => $purposeoptions,
'categories' => $categoryoptions,
];
$effectivepurpose = api::get_effective_contextlevel_purpose($contextlevel);
if ($effectivepurpose) {
$customdata['currentretentionperiod'] = self::get_retention_display_text($effectivepurpose, $contextlevel,
\context_system::instance());
$customdata['purposeretentionperiods'] = [];
foreach ($purposeoptions as $optionvalue => $unused) {
// Get the effective purpose if $optionvalue would be the selected value.
list($purposeid, $unused) = data_registry::get_effective_default_contextlevel_purpose_and_category($contextlevel,
$optionvalue);
$purpose = new \tool_dataprivacy\purpose($purposeid);
$retentionperiod = self::get_retention_display_text(
$purpose,
$contextlevel,
\context_system::instance()
);
$customdata['purposeretentionperiods'][$optionvalue] = $retentionperiod;
}
}
return $customdata;
}
}
@@ -0,0 +1,149 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the form add/update a data purpose.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\form;
defined('MOODLE_INTERNAL') || die();
use core\form\persistent;
/**
* Data purpose form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class purpose extends persistent {
/**
* @var string The persistent class.
*/
protected static $persistentclass = 'tool_dataprivacy\\purpose';
/**
* Define the form - called by parent constructor
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('text', 'name', get_string('name'), 'maxlength="100"');
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', get_string('required'), 'required', null, 'server');
$mform->addRule('name', get_string('maximumchars', '', 100), 'maxlength', 100, 'server');
$mform->addElement('editor', 'description', get_string('description'), null, ['autosave' => false]);
$mform->setType('description', PARAM_CLEANHTML);
// Field for selecting lawful bases (from GDPR Article 6.1).
$lawfulbases = [];
foreach (\tool_dataprivacy\purpose::GDPR_ART_6_1_ITEMS as $article) {
$key = 'gdpr_art_6_1_' . $article;
$lawfulbases[$key] = get_string($key . '_name', 'tool_dataprivacy');
}
$options = array(
'multiple' => true,
);
$mform->addElement('autocomplete', 'lawfulbases', get_string('lawfulbases', 'tool_dataprivacy'), $lawfulbases, $options);
$mform->addRule('lawfulbases', get_string('required'), 'required', null, 'server');
$mform->addHelpButton('lawfulbases', 'lawfulbases', 'tool_dataprivacy');
// Optional field for selecting reasons for collecting sensitive personal data (from GDPR Article 9.2).
$sensitivereasons = [];
foreach (\tool_dataprivacy\purpose::GDPR_ART_9_2_ITEMS as $article) {
$key = 'gdpr_art_9_2_' . $article;
$sensitivereasons[$key] = get_string($key . '_name', 'tool_dataprivacy');
}
$mform->addElement('autocomplete', 'sensitivedatareasons', get_string('sensitivedatareasons', 'tool_dataprivacy'),
$sensitivereasons, $options);
$mform->addHelpButton('sensitivedatareasons', 'sensitivedatareasons', 'tool_dataprivacy');
$number = $mform->createElement('text', 'retentionperiodnumber', null, ['size' => 8]);
$unitoptions = [
'Y' => get_string('years'),
'M' => strtolower(get_string('months')),
'D' => strtolower(get_string('days'))
];
$unit = $mform->createElement('select', 'retentionperiodunit', '', $unitoptions);
$mform->addGroup(['number' => $number, 'unit' => $unit], 'retentionperiod',
get_string('retentionperiod', 'tool_dataprivacy'), null, false);
$mform->setType('retentionperiodnumber', PARAM_INT);
$this->_form->addElement('advcheckbox', 'protected', get_string('protected', 'tool_dataprivacy'),
get_string('protectedlabel', 'tool_dataprivacy'));
if (!empty($this->_customdata['showbuttons'])) {
if (!$this->get_persistent()->get('id')) {
$savetext = get_string('add');
} else {
$savetext = get_string('savechanges');
}
$this->add_action_buttons(true, $savetext);
}
}
/**
* Converts fields.
*
* @param \stdClass $data
* @return \stdClass
*/
protected static function convert_fields(\stdClass $data) {
$data = parent::convert_fields($data);
if (is_array($data->lawfulbases)) {
$data->lawfulbases = implode(',', $data->lawfulbases);
}
if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) {
$data->sensitivedatareasons = implode(',', $data->sensitivedatareasons);
}
// A single value.
$data->retentionperiod = 'P' . $data->retentionperiodnumber . $data->retentionperiodunit;
unset($data->retentionperiodnumber);
unset($data->retentionperiodunit);
return $data;
}
/**
* Get the default data.
*
* @return \stdClass
*/
protected function get_default_data() {
$data = parent::get_default_data();
$data->lawfulbases = explode(',', $data->lawfulbases);
if (!empty($data->sensitivedatareasons)) {
$data->sensitivedatareasons = explode(',', $data->sensitivedatareasons);
}
// Convert the single properties into number and unit.
$strlen = strlen($data->retentionperiod);
$data->retentionperiodnumber = substr($data->retentionperiod, 1, $strlen - 2);
$data->retentionperiodunit = substr($data->retentionperiod, $strlen - 1);
unset($data->retentionperiod);
return $data;
}
}
@@ -0,0 +1,105 @@
<?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/>.
/**
* Filter selector field.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\form;
defined('MOODLE_INTERNAL') || die();
use MoodleQuickForm_autocomplete;
global $CFG;
require_once($CFG->libdir . '/form/autocomplete.php');
/**
* Form field type for choosing a user on the data request creation form.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class request_user_autocomplete extends MoodleQuickForm_autocomplete {
/**
* Constructor.
*
* @param string $elementname Element name
* @param mixed $elementlabel Label(s) for an element
* @param array $options Options to control the element's display
* Valid options are:
* - multiple bool Whether or not the field accepts more than one values.
*/
public function __construct($elementname = null, $elementlabel = null, $options = array()) {
$validattributes = array(
'ajax' => 'tool_dataprivacy/form-user-selector',
);
if (!empty($options['multiple'])) {
$validattributes['multiple'] = 'multiple';
}
parent::__construct($elementname, $elementlabel, array(), $validattributes);
}
/**
* Set the value of this element.
*
* @param string|array $value The value to set.
* @return boolean
*/
public function setValue($value) {
global $DB;
$values = (array) $value;
$ids = [];
foreach ($values as $onevalue) {
if (!empty($onevalue) && (!$this->optionExists($onevalue)) &&
($onevalue !== '_qf__force_multiselect_submission')) {
array_push($ids, $onevalue);
}
}
if (empty($ids)) {
return $this->setSelected([]);
}
$toselect = [];
list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
$allusernames = get_all_user_name_fields(true);
// Exclude admins and guest user.
$excludedusers = array_keys(get_admins()) + [guest_user()->id];
$sort = 'firstname ASC';
$fields = 'id, email, ' . $allusernames;
// Fetch the selected users, exclude the admins and guest users.
$users = get_users(true, '', true, $excludedusers, $sort, '', '', 0, 30, $fields, "id $insql", $inparams);
foreach ($users as $user) {
$userdata = (object)[
'name' => fullname($user),
'email' => $user->email
];
$this->addOption(get_string('nameemail', 'tool_dataprivacy', $userdata), $user->id);
array_push($toselect, $user->id);
}
return $this->setSelected($toselect);
}
}
@@ -0,0 +1,251 @@
<?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/>.
/**
* Collection of helper functions for the data privacy tool.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\local;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use moodle_exception;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
/**
* Class containing helper functions for the data privacy tool.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/** The default number of results to be shown per page. */
const DEFAULT_PAGE_SIZE = 20;
/** Filter constant associated with the request type filter. */
const FILTER_TYPE = 1;
/** Filter constant associated with the request status filter. */
const FILTER_STATUS = 2;
/** Filter constant associated with the request creation filter. */
const FILTER_CREATION = 3;
/** The request filters preference key. */
const PREF_REQUEST_FILTERS = 'tool_dataprivacy_request-filters';
/** The number of data request records per page preference key. */
const PREF_REQUEST_PERPAGE = 'tool_dataprivacy_request-perpage';
/**
* Retrieves the human-readable text value of a data request type.
*
* @param int $requesttype The request type.
* @return string
* @throws coding_exception
* @throws moodle_exception
*/
public static function get_request_type_string($requesttype) {
$types = self::get_request_types();
if (!isset($types[$requesttype])) {
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
}
return $types[$requesttype];
}
/**
* Retrieves the human-readable shortened text value of a data request type.
*
* @param int $requesttype The request type.
* @return string
* @throws coding_exception
* @throws moodle_exception
*/
public static function get_shortened_request_type_string($requesttype) {
$types = self::get_request_types_short();
if (!isset($types[$requesttype])) {
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
}
return $types[$requesttype];
}
/**
* Returns the key value-pairs of request type code and their string value.
*
* @return array
*/
public static function get_request_types() {
return [
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothers', 'tool_dataprivacy'),
];
}
/**
* Returns the key value-pairs of request type code and their shortened string value.
*
* @return array
*/
public static function get_request_types_short() {
return [
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexportshort', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedeleteshort', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothersshort', 'tool_dataprivacy'),
];
}
/**
* Retrieves the human-readable value of a data request status.
*
* @param int $status The request status.
* @return string
* @throws moodle_exception
*/
public static function get_request_status_string($status) {
$statuses = self::get_request_statuses();
if (!isset($statuses[$status])) {
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
}
return $statuses[$status];
}
/**
* Returns the key value-pairs of request status code and string value.
*
* @return array
*/
public static function get_request_statuses() {
return [
api::DATAREQUEST_STATUS_PENDING => get_string('statuspending', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_PREPROCESSING => get_string('statuspreprocessing', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => get_string('statusawaitingapproval', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_APPROVED => get_string('statusapproved', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_PROCESSING => get_string('statusprocessing', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_COMPLETE => get_string('statuscomplete', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_DOWNLOAD_READY => get_string('statusready', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_EXPIRED => get_string('statusexpired', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_CANCELLED => get_string('statuscancelled', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_REJECTED => get_string('statusrejected', 'tool_dataprivacy'),
api::DATAREQUEST_STATUS_DELETED => get_string('statusdeleted', 'tool_dataprivacy'),
];
}
/**
* Retrieves the human-readable value of a data request creation method.
*
* @param int $creation The request creation method.
* @return string
* @throws moodle_exception
*/
public static function get_request_creation_method_string($creation) {
$creationmethods = self::get_request_creation_methods();
if (!isset($creationmethods[$creation])) {
throw new moodle_exception('errorinvalidrequestcreationmethod', 'tool_dataprivacy');
}
return $creationmethods[$creation];
}
/**
* Returns the key value-pairs of request creation method code and string value.
*
* @return array
*/
public static function get_request_creation_methods() {
return [
data_request::DATAREQUEST_CREATION_MANUAL => get_string('creationmanual', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO => get_string('creationauto', 'tool_dataprivacy'),
];
}
/**
* Get the users that a user can make data request for.
*
* E.g. User having a parent role and has the 'tool/dataprivacy:makedatarequestsforchildren' capability.
* @param int $userid The user's ID.
* @return array
*/
public static function get_children_of_user($userid) {
global $DB;
// Get users that the user has role assignments to.
$allusernames = get_all_user_name_fields(true, 'u');
$sql = "SELECT u.id, $allusernames
FROM {role_assignments} ra, {context} c, {user} u
WHERE ra.userid = :userid
AND ra.contextid = c.id
AND c.instanceid = u.id
AND c.contextlevel = :contextlevel";
$params = [
'userid' => $userid,
'contextlevel' => CONTEXT_USER
];
// The final list of users that we will return.
$finalresults = [];
// Our prospective list of users.
if ($candidates = $DB->get_records_sql($sql, $params)) {
foreach ($candidates as $key => $child) {
$childcontext = \context_user::instance($child->id);
if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext, $userid)) {
$finalresults[$key] = $child;
}
}
}
return $finalresults;
}
/**
* Get options for the data requests filter.
*
* @return array
* @throws coding_exception
*/
public static function get_request_filter_options() {
$filters = [
self::FILTER_TYPE => (object)[
'name' => get_string('requesttype', 'tool_dataprivacy'),
'options' => self::get_request_types_short()
],
self::FILTER_STATUS => (object)[
'name' => get_string('requeststatus', 'tool_dataprivacy'),
'options' => self::get_request_statuses()
],
self::FILTER_CREATION => (object)[
'name' => get_string('requestcreation', 'tool_dataprivacy'),
'options' => self::get_request_creation_methods()
],
];
$options = [];
foreach ($filters as $category => $filtercategory) {
foreach ($filtercategory->options as $key => $name) {
$option = (object)[
'category' => $filtercategory->name,
'name' => $name
];
$options["{$category}:{$key}"] = get_string('filteroption', 'tool_dataprivacy', $option);
}
}
return $options;
}
}
@@ -0,0 +1,76 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class \tool_dataprivacy\manager_observer.
*
* @package tool_dataprivacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
/**
* A failure observer for the \core_privacy\manager.
*
* @package tool_dataprivacy
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager_observer implements \core_privacy\manager_observer {
/**
* Notifies all DPOs that an exception occurred.
*
* @param \Throwable $e
* @param string $component
* @param string $interface
* @param string $methodname
* @param array $params
*/
public function handle_component_failure($e, $component, $interface, $methodname, array $params) {
// Get the list of the site Data Protection Officers.
$dpos = api::get_site_dpos();
$messagesubject = get_string('exceptionnotificationsubject', 'tool_dataprivacy');
$a = (object)[
'fullmethodname' => \core_privacy\manager::get_provider_classname_for_component($component) . '::' . $methodname,
'component' => $component,
'message' => $e->getMessage(),
'backtrace' => $e->getTraceAsString()
];
$messagebody = get_string('exceptionnotificationbody', 'tool_dataprivacy', $a);
// Email the data request to the Data Protection Officer(s)/Admin(s).
foreach ($dpos as $dpo) {
$message = new \core\message\message();
$message->courseid = SITEID;
$message->component = 'tool_dataprivacy';
$message->name = 'notifyexceptions';
$message->userfrom = \core_user::get_noreply_user();
$message->subject = $messagesubject;
$message->fullmessageformat = FORMAT_HTML;
$message->notification = 1;
$message->userto = $dpo;
$message->fullmessagehtml = $messagebody;
$message->fullmessage = html_to_text($messagebody);
// Send message.
message_send($message);
}
}
}
@@ -0,0 +1,168 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing helper methods for processing data requests.
*
* @package tool_dataprivacy
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
/**
* Class containing helper methods for processing data requests.
*
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class metadata_registry {
/**
* Returns plugin types / plugins and the user data that it stores in a format that can be sent to a template.
*
* @return array An array with all of the plugin types / plugins and the user data they store.
*/
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();
$fullyrichtree = $this->get_full_component_list();
foreach ($fullyrichtree as $branch => $leaves) {
$plugintype = $leaves['plugin_type'];
$plugins = array_map(function($component) use ($manager, $metadata, $contributedplugins, $plugintype, $pluginman) {
// Use the plugin name for the plugins, ignore for core subsystems.
$internaldata = ($plugintype == 'core') ? ['component' => $component] :
['component' => $pluginman->plugin_name($component)];
$internaldata['raw_component'] = $component;
if ($manager->component_is_compliant($component)) {
$internaldata['compliant'] = true;
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);
}
} else {
$internaldata['compliant'] = false;
}
// Check to see if we are an external plugin.
// Plugin names can contain _ characters, limit to 2 to just remove initial plugintype.
$componentshortname = explode('_', $component, 2);
$shortname = array_pop($componentshortname);
if (isset($contributedplugins[$plugintype][$shortname])) {
$internaldata['external'] = true;
}
// Check if the interface is deprecated.
if (!$manager->is_empty_subsystem($component)) {
$classname = $manager->get_provider_classname_for_component($component);
if (class_exists($classname)) {
$componentclass = new $classname();
if ($componentclass instanceof \core_privacy\local\deprecated) {
$internaldata['deprecated'] = true;
}
}
}
return $internaldata;
}, $leaves['plugins']);
$fullyrichtree[$branch]['plugin_type_raw'] = $plugintype;
// We're done using the plugin type. Convert it to a readable string.
$fullyrichtree[$branch]['plugin_type'] = $pluginman->plugintype_name($plugintype);
$fullyrichtree[$branch]['plugins'] = $plugins;
}
return $fullyrichtree;
}
/**
* Formats the metadata for use with a template.
*
* @param array $collection The collection associated with the component that we want to expand and format.
* @param string $component The component that we are dealing in
* @param array $internaldata The array to add the formatted metadata to.
* @return array The internal data array with the formatted metadata.
*/
protected function format_metadata($collection, $component, $internaldata) {
foreach ($collection as $collectioninfo) {
$privacyfields = $collectioninfo->get_privacy_fields();
$fields = '';
if (!empty($privacyfields)) {
$fields = array_map(function($key, $field) use ($component) {
return [
'field_name' => $key,
'field_summary' => get_string($field, $component)
];
}, array_keys($privacyfields), $privacyfields);
}
// Can the metadata types be located somewhere else besides core?
$items = explode('\\', get_class($collectioninfo));
$type = array_pop($items);
$typedata = [
'name' => $collectioninfo->get_name(),
'type' => $type,
'fields' => $fields,
'summary' => get_string($collectioninfo->get_summary(), $component)
];
if (strpos($type, 'subsystem_link') === 0 || strpos($type, 'plugintype_link') === 0) {
$typedata['link'] = true;
}
$internaldata['metadata'][] = $typedata;
}
return $internaldata;
}
/**
* Return the full list of components.
*
* @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;
}
/**
* Returns a list of contributed plugins installed on the system.
*
* @return array A list of contributed plugins installed.
*/
protected function get_contrib_list() {
return array_map(function($plugins) {
return array_filter($plugins, function($plugindata) {
return !$plugindata->is_standard();
});
}, \core_plugin_manager::instance()->get_plugins());
}
}
@@ -0,0 +1,88 @@
<?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/>.
/**
* Categories renderable.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_dataprivacy\external\category_exporter;
/**
* Class containing the categories page renderable.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class categories extends crud_element implements renderable, templatable {
/** @var array $categories All system categories. */
protected $categories = [];
/**
* Construct this renderable.
*
* @param \tool_dataprivacy\category[] $categories
*/
public function __construct($categories) {
$this->categories = $categories;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $PAGE;
$context = \context_system::instance();
$PAGE->requires->js_call_amd('tool_dataprivacy/categoriesactions', 'init');
$PAGE->requires->js_call_amd('tool_dataprivacy/add_category', 'getInstance', [$context->id]);
$data = new stdClass();
// Navigation links.
$data->navigation = [];
$navigationlinks = $this->get_navigation();
foreach ($navigationlinks as $navlink) {
$data->navigation[] = $navlink->export_for_template($output);
}
$data->categories = [];
foreach ($this->categories as $category) {
$exporter = new category_exporter($category, ['context' => \context_system::instance()]);
$exportedcategory = $exporter->export($output);
$actionmenu = $this->action_menu('category', $exportedcategory, $category);
$exportedcategory->actions = $actionmenu->export_for_template($output);
$data->categories[] = $exportedcategory;
}
return $data;
}
}
@@ -0,0 +1,92 @@
<?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/>.
/**
* Abstract renderer for independent renderable elements.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_dataprivacy\external\purpose_exporter;
use tool_dataprivacy\external\category_exporter;
/**
* Abstract renderer for independent renderable elements.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class crud_element {
/**
* Returns the top navigation buttons.
*
* @return \action_link[]
*/
protected final function get_navigation() {
$back = new \action_link(
new \moodle_url('/admin/tool/dataprivacy/dataregistry.php'),
get_string('back'),
null,
['class' => 'btn btn-default']
);
return [$back];
}
/**
* Adds an action menu for the provided element
*
* @param string $elementname 'purpose' or 'category'.
* @param \stdClass $exported
* @param \core\persistent $persistent
* @return \action_menu
*/
protected final function action_menu($elementname, $exported, $persistent) {
// Just in case, we are doing funny stuff below.
$elementname = clean_param($elementname, PARAM_ALPHA);
// Actions.
$actionmenu = new \action_menu();
$actionmenu->set_menu_trigger(get_string('actions'));
$actionmenu->set_owner_selector($elementname . '-' . $exported->id . '-actions');
$actionmenu->set_alignment(\action_menu::TL, \action_menu::BL);
$url = new \moodle_url('/admin/tool/dataprivacy/edit' . $elementname . '.php',
['id' => $exported->id]);
$link = new \action_menu_link_secondary($url, new \pix_icon('t/edit',
get_string('edit')), get_string('edit'));
$actionmenu->add($link);
if (!$persistent->is_used()) {
$url = new \moodle_url('#');
$attrs = ['data-id' => $exported->id, 'data-action' => 'delete' . $elementname, 'data-name' => $exported->name];
$link = new \action_menu_link_secondary($url, new \pix_icon('t/delete',
get_string('delete')), get_string('delete'), $attrs);
$actionmenu->add($link);
}
return $actionmenu;
}
}
@@ -0,0 +1,94 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing data for a user's data requests.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use moodle_exception;
use moodle_url;
use renderable;
use renderer_base;
use single_select;
use stdClass;
use templatable;
use tool_dataprivacy\data_request;
use tool_dataprivacy\local\helper;
/**
* Class containing data for a user's data requests.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_deletion_page implements renderable, templatable {
/** @var data_request[] $requests List of data requests. */
protected $filter = null;
/** @var data_request[] $requests List of data requests. */
protected $expiredcontextstable = [];
/**
* Construct this renderable.
*
* @param \tool_dataprivacy\data_request[] $filter
* @param expired_contexts_table $expiredcontextstable
*/
public function __construct($filter, expired_contexts_table $expiredcontextstable) {
$this->filter = $filter;
$this->expiredcontextstable = $expiredcontextstable;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
* @throws coding_exception
* @throws moodle_exception
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
$url = new moodle_url('/admin/tool/dataprivacy/datadeletion.php');
$options = [
CONTEXT_USER => get_string('user'),
CONTEXT_COURSE => get_string('course'),
CONTEXT_MODULE => get_string('activitiesandresources', 'tool_dataprivacy'),
CONTEXT_BLOCK => get_string('blocks'),
];
$filterselector = new single_select($url, 'filter', $options, $this->filter, null);
$data->filter = $filterselector->export_for_template($output);
ob_start();
$this->expiredcontextstable->out(helper::DEFAULT_PAGE_SIZE, true);
$expiredcontexts = ob_get_contents();
ob_end_clean();
$data->expiredcontexts = $expiredcontexts;
$data->existingcontexts = $this->expiredcontextstable->rawdata ? true : false;
return $data;
}
}
@@ -0,0 +1,60 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the data registry compliance renderable.
*
* @package tool_dataprivacy
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
/**
* Class containing the data registry compliance renderable
*
* @copyright 2018 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_registry_compliance_page implements renderable, templatable {
/** @var array meta-data to be displayed about the system. */
protected $metadata;
/**
* Constructor.
*
* @param array $metadata
*/
public function __construct($metadata) {
$this->metadata = $metadata;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
return ['types' => $this->metadata];
}
}
@@ -0,0 +1,481 @@
<?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 registry renderable.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_dataprivacy\data_registry;
require_once($CFG->libdir . '/coursecatlib.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
require_once($CFG->libdir . '/blocklib.php');
/**
* Class containing the data registry renderable
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_registry_page implements renderable, templatable {
/**
* @var int
*/
private $defaultcontextlevel;
/**
* @var int
*/
private $defaultcontextid;
/**
* Constructor.
*
* @param int $defaultcontextlevel
* @param int $defaultcontextid
* @return null
*/
public function __construct($defaultcontextlevel = false, $defaultcontextid = false) {
$this->defaultcontextlevel = $defaultcontextlevel;
$this->defaultcontextid = $defaultcontextid;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $PAGE;
$params = [\context_system::instance()->id, $this->defaultcontextlevel, $this->defaultcontextid];
$PAGE->requires->js_call_amd('tool_dataprivacy/data_registry', 'init', $params);
$data = new stdClass();
$defaultsbutton = new \action_link(
new \moodle_url('/admin/tool/dataprivacy/defaults.php'),
get_string('setdefaults', 'tool_dataprivacy'),
null,
['class' => 'btn btn-default']
);
$data->defaultsbutton = $defaultsbutton->export_for_template($output);
$actionmenu = new \action_menu();
$actionmenu->set_menu_trigger(get_string('edit'), 'btn btn-default');
$actionmenu->set_owner_selector('dataregistry-actions');
$actionmenu->set_alignment(\action_menu::TL, \action_menu::BL);
$url = new \moodle_url('/admin/tool/dataprivacy/categories.php');
$categories = new \action_menu_link_secondary($url, null, get_string('categories', 'tool_dataprivacy'));
$actionmenu->add($categories);
$url = new \moodle_url('/admin/tool/dataprivacy/purposes.php');
$purposes = new \action_menu_link_secondary($url, null, get_string('purposes', 'tool_dataprivacy'));
$actionmenu->add($purposes);
$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
];
}
$data->tree = $this->get_default_tree_structure();
return $data;
}
/**
* Returns the tree default structure.
*
* @return array
*/
private function get_default_tree_structure() {
$frontpage = \context_course::instance(SITEID);
$categorybranches = $this->get_all_category_branches();
$elements = [
'text' => get_string('contextlevelname' . CONTEXT_SYSTEM, 'tool_dataprivacy'),
'contextlevel' => CONTEXT_SYSTEM,
'branches' => [
[
'text' => get_string('user'),
'contextlevel' => CONTEXT_USER,
], [
'text' => get_string('categories'),
'branches' => $categorybranches,
'expandelement' => 'category',
], [
'text' => get_string('frontpagecourse', 'tool_dataprivacy'),
'contextid' => $frontpage->id,
'branches' => [
[
'text' => get_string('activitiesandresources', 'tool_dataprivacy'),
'expandcontextid' => $frontpage->id,
'expandelement' => 'module',
'expanded' => 0,
], [
'text' => get_string('blocks'),
'expandcontextid' => $frontpage->id,
'expandelement' => 'block',
'expanded' => 0,
],
]
]
]
];
// Returned as an array to follow a common array format.
return [self::complete($elements, $this->defaultcontextlevel, $this->defaultcontextid)];
}
/**
* Returns the hierarchy of system course categories.
*
* @return array
*/
private function get_all_category_branches() {
$categories = data_registry::get_site_categories();
$categoriesbranch = [];
while (count($categories) > 0) {
foreach ($categories as $key => $category) {
$context = \context_coursecat::instance($category->id);
$newnode = [
'text' => shorten_text(format_string($category->name, true, ['context' => $context])),
'categoryid' => $category->id,
'contextid' => $context->id,
];
if ($category->coursecount > 0) {
$newnode['branches'] = [
[
'text' => get_string('courses'),
'expandcontextid' => $context->id,
'expandelement' => 'course',
'expanded' => 0,
]
];
}
$added = false;
if ($category->parent == 0) {
// New categories root-level node.
$categoriesbranch[] = $newnode;
$added = true;
} else {
// Add the new node under the appropriate parent.
if ($this->add_to_parent_category_branch($category, $newnode, $categoriesbranch)) {
$added = true;
}
}
if ($added) {
unset($categories[$key]);
}
}
}
return $categoriesbranch;
}
/**
* Gets the courses branch for the provided category.
*
* @param \context $catcontext
* @return array
*/
public static function get_courses_branch(\context $catcontext) {
if ($catcontext->contextlevel !== CONTEXT_COURSECAT) {
throw new \coding_exception('A course category context should be provided');
}
$coursecat = \coursecat::get($catcontext->instanceid);
$courses = $coursecat->get_courses();
$branches = [];
foreach ($courses as $course) {
$coursecontext = \context_course::instance($course->id);
if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
continue;
}
$coursenode = [
'text' => shorten_text(format_string($course->shortname, true, ['context' => $coursecontext])),
'contextid' => $coursecontext->id,
'branches' => [
[
'text' => get_string('activitiesandresources', 'tool_dataprivacy'),
'expandcontextid' => $coursecontext->id,
'expandelement' => 'module',
'expanded' => 0,
], [
'text' => get_string('blocks'),
'expandcontextid' => $coursecontext->id,
'expandelement' => 'block',
'expanded' => 0,
],
]
];
$branches[] = self::complete($coursenode);
}
return $branches;
}
/**
* Gets the modules branch for the provided course.
*
* @param \context $coursecontext
* @return array
*/
public static function get_modules_branch(\context $coursecontext) {
if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
throw new \coding_exception('A course context should be provided');
}
$branches = [];
// Using the current user.
$modinfo = get_fast_modinfo($coursecontext->instanceid);
foreach ($modinfo->get_instances() as $moduletype => $instances) {
foreach ($instances as $cm) {
if (!$cm->uservisible) {
continue;
}
$a = (object)[
'instancename' => shorten_text($cm->get_formatted_name()),
'modulename' => get_string('pluginname', 'mod_' . $moduletype),
];
$text = get_string('moduleinstancename', 'tool_dataprivacy', $a);
$branches[] = self::complete([
'text' => $text,
'contextid' => $cm->context->id,
]);
}
}
return $branches;
}
/**
* Gets the blocks branch for the provided course.
*
* @param \context $coursecontext
* @return null
*/
public static function get_blocks_branch(\context $coursecontext) {
global $DB;
if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
throw new \coding_exception('A course context should be provided');
}
$branches = [];
$children = $coursecontext->get_child_contexts();
foreach ($children as $childcontext) {
if ($childcontext->contextlevel !== CONTEXT_BLOCK) {
continue;
}
$blockinstance = block_instance_by_id($childcontext->instanceid);
$displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $childcontext]));
$branches[] = self::complete([
'text' => $displayname,
'contextid' => $childcontext->id,
]);
}
return $branches;
}
/**
* Adds the provided category to the categories branch.
*
* @param stdClass $category
* @param array $newnode
* @param array $categoriesbranch
* @return bool
*/
private function add_to_parent_category_branch($category, $newnode, &$categoriesbranch) {
foreach ($categoriesbranch as $key => $branch) {
if (!empty($branch['categoryid']) && $branch['categoryid'] == $category->parent) {
// It may be empty (if it does not contain courses and this is the first child cat).
if (!isset($categoriesbranch[$key]['branches'])) {
$categoriesbranch[$key]['branches'] = [];
}
$categoriesbranch[$key]['branches'][] = $newnode;
return true;
}
if (!empty($branch['branches'])) {
$parent = $this->add_to_parent_category_branch($category, $newnode, $categoriesbranch[$key]['branches']);
if ($parent) {
return true;
}
}
}
return false;
}
/**
* Completes tree nodes with default values.
*
* @param array $node
* @param int|false $currentcontextlevel
* @param int|false $currentcontextid
* @return array
*/
private static function complete($node, $currentcontextlevel = false, $currentcontextid = false) {
if (!isset($node['active'])) {
if ($currentcontextlevel && !empty($node['contextlevel']) &&
$currentcontextlevel == $node['contextlevel'] &&
empty($currentcontextid)) {
// This is the active context level, we also checked that there
// is no default contextid set.
$node['active'] = true;
} else if ($currentcontextid && !empty($node['contextid']) &&
$currentcontextid == $node['contextid']) {
$node['active'] = true;
} else {
$node['active'] = null;
}
}
if (!isset($node['branches'])) {
$node['branches'] = [];
} else {
foreach ($node['branches'] as $key => $childnode) {
$node['branches'][$key] = self::complete($childnode, $currentcontextlevel, $currentcontextid);
}
}
if (!isset($node['expandelement'])) {
$node['expandelement'] = null;
}
if (!isset($node['expandcontextid'])) {
$node['expandcontextid'] = null;
}
if (!isset($node['contextid'])) {
$node['contextid'] = null;
}
if (!isset($node['contextlevel'])) {
$node['contextlevel'] = null;
}
if (!isset($node['expanded'])) {
if (!empty($node['branches'])) {
$node['expanded'] = 1;
} else {
$node['expanded'] = 0;
}
}
return $node;
}
/**
* From a list of purpose persistents to a list of id => name purposes.
*
* @param \tool_dataprivacy\purpose[] $purposes
* @param bool $includenotset
* @param bool $includeinherit
* @return string[]
*/
public static function purpose_options($purposes, $includenotset = true, $includeinherit = true) {
$options = self::base_options($includenotset, $includeinherit);
foreach ($purposes as $purpose) {
$options[$purpose->get('id')] = $purpose->get('name');
}
return $options;
}
/**
* From a list of category persistents to a list of id => name categories.
*
* @param \tool_dataprivacy\category[] $categories
* @param bool $includenotset
* @param bool $includeinherit
* @return string[]
*/
public static function category_options($categories, $includenotset = true, $includeinherit = true) {
$options = self::base_options($includenotset, $includeinherit);
foreach ($categories as $category) {
$options[$category->get('id')] = $category->get('name');
}
return $options;
}
/**
* Base not set and inherit options.
*
* @param bool $includenotset
* @param bool $includeinherit
* @return array
*/
private static function base_options($includenotset = true, $includeinherit = true) {
$options = [];
if ($includenotset) {
$options[\tool_dataprivacy\context_instance::NOTSET] = get_string('notset', 'tool_dataprivacy');
}
if ($includeinherit) {
$options[\tool_dataprivacy\context_instance::INHERIT] = get_string('inherit', 'tool_dataprivacy');
}
return $options;
}
}
@@ -0,0 +1,96 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing data for a user's data requests.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use dml_exception;
use moodle_exception;
use moodle_url;
use renderable;
use renderer_base;
use single_select;
use stdClass;
use templatable;
use tool_dataprivacy\api;
use tool_dataprivacy\local\helper;
/**
* Class containing data for a user's data requests.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_requests_page implements renderable, templatable {
/** @var data_requests_table $table The data requests table. */
protected $table;
/** @var int[] $filters The applied filters. */
protected $filters = [];
/**
* Construct this renderable.
*
* @param data_requests_table $table The data requests table.
* @param int[] $filters The applied filters.
*/
public function __construct($table, $filters) {
$this->table = $table;
$this->filters = $filters;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
$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);
}
$url = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
$filteroptions = helper::get_request_filter_options();
$filter = new request_filter($filteroptions, $this->filters, $url);
$data->filter = $filter->export_for_template($output);
ob_start();
$this->table->out($this->table->get_requests_per_page(), true);
$requests = ob_get_contents();
ob_end_clean();
$data->datarequests = $requests;
return $data;
}
}
@@ -0,0 +1,395 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the class used for the displaying the data requests table.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/tablelib.php');
use action_menu;
use action_menu_link_secondary;
use coding_exception;
use dml_exception;
use html_writer;
use moodle_url;
use stdClass;
use table_sql;
use tool_dataprivacy\api;
use tool_dataprivacy\external\data_request_exporter;
defined('MOODLE_INTERNAL') || die;
/**
* The class for displaying the data requests table.
*
* @copyright 2018 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class data_requests_table extends table_sql {
/** @var int The user ID. */
protected $userid = 0;
/** @var int[] The status filters. */
protected $statuses = [];
/** @var int[] The request type filters. */
protected $types = [];
/** @var bool Whether this table is being rendered for managing data requests. */
protected $manage = false;
/** @var \tool_dataprivacy\data_request[] Array of data request persistents. */
protected $datarequests = [];
/** @var int The number of data request to be displayed per page. */
protected $perpage;
/** @var int[] The available options for the number of data request to be displayed per page. */
protected $perpageoptions = [25, 50, 100, 250];
/**
* data_requests_table constructor.
*
* @param int $userid The user ID
* @param int[] $statuses
* @param int[] $types
* @param int[] $creationmethods
* @param bool $manage
* @throws coding_exception
*/
public function __construct($userid = 0, $statuses = [], $types = [], $creationmethods = [], $manage = false) {
parent::__construct('data-requests-table');
$this->userid = $userid;
$this->statuses = $statuses;
$this->types = $types;
$this->creationmethods = $creationmethods;
$this->manage = $manage;
$checkboxattrs = [
'title' => get_string('selectall'),
'data-action' => 'selectall'
];
$columnheaders = [
'select' => html_writer::checkbox('selectall', 1, false, null, $checkboxattrs),
'type' => get_string('requesttype', 'tool_dataprivacy'),
'userid' => get_string('user', 'tool_dataprivacy'),
'timecreated' => get_string('daterequested', 'tool_dataprivacy'),
'requestedby' => get_string('requestby', 'tool_dataprivacy'),
'status' => get_string('requeststatus', 'tool_dataprivacy'),
'comments' => get_string('message', 'tool_dataprivacy'),
'actions' => '',
];
$this->define_columns(array_keys($columnheaders));
$this->define_headers(array_values($columnheaders));
$this->no_sorting('select', 'actions');
}
/**
* The select column.
*
* @param stdClass $data The row data.
* @return string
*/
public function col_select($data) {
if ($data->status == \tool_dataprivacy\api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
$stringdata = [
'username' => $data->foruser->fullname,
'requesttype' => \core_text::strtolower($data->typenameshort)
];
return \html_writer::checkbox('requestids[]', $data->id, false, '',
['class' => 'selectrequests', 'title' => get_string('selectuserdatarequest',
'tool_dataprivacy', $stringdata)]);
}
}
/**
* The type column.
*
* @param stdClass $data The row data.
* @return string
*/
public function col_type($data) {
if ($this->manage) {
return $data->typenameshort;
}
return $data->typename;
}
/**
* The user column.
*
* @param stdClass $data The row data.
* @return mixed
*/
public function col_userid($data) {
$user = $data->foruser;
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
}
/**
* The context information column.
*
* @param stdClass $data The row data.
* @return string
*/
public function col_timecreated($data) {
return userdate($data->timecreated);
}
/**
* The requesting user's column.
*
* @param stdClass $data The row data.
* @return mixed
*/
public function col_requestedby($data) {
$user = $data->requestedbyuser;
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
}
/**
* The status column.
*
* @param stdClass $data The row data.
* @return mixed
*/
public function col_status($data) {
return html_writer::span($data->statuslabel, 'label ' . $data->statuslabelclass);
}
/**
* The comments column.
*
* @param stdClass $data The row data.
* @return string
*/
public function col_comments($data) {
return shorten_text($data->comments, 60);
}
/**
* The actions column.
*
* @param stdClass $data The row data.
* @return string
*/
public function col_actions($data) {
global $OUTPUT;
$requestid = $data->id;
$status = $data->status;
// Prepare actions.
$actions = [];
// View action.
$actionurl = new moodle_url('#');
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
switch ($status) {
case api::DATAREQUEST_STATUS_PENDING:
// Add action to mark a general enquiry request as complete.
if ($data->type == api::DATAREQUEST_TYPE_OTHERS) {
$actiondata['data-action'] = 'complete';
$nameemail = (object)[
'name' => $data->foruser->fullname,
'email' => $data->foruser->email
];
$actiondata['data-requestid'] = $data->id;
$actiondata['data-replytoemail'] = get_string('nameemail', 'tool_dataprivacy', $nameemail);
$actiontext = get_string('markcomplete', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
}
break;
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
// Approve.
$actiondata['data-action'] = 'approve';
$actiontext = get_string('approverequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
// Deny.
$actiondata['data-action'] = 'deny';
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
break;
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
$userid = $data->foruser->id;
$usercontext = \context_user::instance($userid, IGNORE_MISSING);
// If user has permission to view download link, show relevant action item.
if ($usercontext && api::can_download_data_request_for_user($userid, $data->requestedbyuser->id)) {
$actions[] = api::get_download_link($usercontext, $requestid);
}
break;
}
$actionsmenu = new action_menu($actions);
$actionsmenu->set_menu_trigger(get_string('actions'));
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
$actionsmenu->set_constraint('[data-region=data-requests-table] > .no-overflow');
return $OUTPUT->render($actionsmenu);
}
/**
* Query the database for results to display in the table.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
* @throws dml_exception
* @throws coding_exception
*/
public function query_db($pagesize, $useinitialsbar = true) {
global $PAGE;
// Set dummy page total until we fetch full result set.
$this->pagesize($pagesize, $pagesize + 1);
$sort = $this->get_sql_sort();
// Get data requests from the given conditions.
$datarequests = api::get_data_requests($this->userid, $this->statuses, $this->types,
$this->creationmethods, $sort, $this->get_page_start(), $this->get_page_size());
// Count data requests from the given conditions.
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types,
$this->creationmethods);
$this->pagesize($pagesize, $total);
$this->rawdata = [];
$context = \context_system::instance();
$renderer = $PAGE->get_renderer('tool_dataprivacy');
foreach ($datarequests as $persistent) {
$this->datarequests[$persistent->get('id')] = $persistent;
$exporter = new data_request_exporter($persistent, ['context' => $context]);
$this->rawdata[] = $exporter->export($renderer);
}
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
/**
* Override default implementation to display a more meaningful information to the user.
*/
public function print_nothing_to_display() {
global $OUTPUT;
echo $this->render_reset_button();
$this->print_initials_bar();
if (!empty($this->statuses) || !empty($this->types)) {
$message = get_string('nodatarequestsmatchingfilter', 'tool_dataprivacy');
} else {
$message = get_string('nodatarequests', 'tool_dataprivacy');
}
echo $OUTPUT->notification($message, 'warning');
}
/**
* Override the table's show_hide_link method to prevent the show/hide links from rendering.
*
* @param string $column the column name, index into various names.
* @param int $index numerical index of the column.
* @return string HTML fragment.
*/
protected function show_hide_link($column, $index) {
return '';
}
/**
* Override the table's wrap_html_finish method in order to render the bulk actions and
* records per page options.
*/
public function wrap_html_finish() {
global $OUTPUT;
$data = new stdClass();
$data->options = [
[
'value' => 0,
'name' => ''
],
[
'value' => \tool_dataprivacy\api::DATAREQUEST_ACTION_APPROVE,
'name' => get_string('approve', 'tool_dataprivacy')
],
[
'value' => \tool_dataprivacy\api::DATAREQUEST_ACTION_REJECT,
'name' => get_string('deny', 'tool_dataprivacy')
]
];
$perpageoptions = array_combine($this->perpageoptions, $this->perpageoptions);
$perpageselect = new \single_select(new moodle_url(''), 'perpage',
$perpageoptions, get_user_preferences('tool_dataprivacy_request-perpage'), null, 'selectgroup');
$perpageselect->label = get_string('perpage', 'moodle');
$data->perpage = $OUTPUT->render($perpageselect);
echo $OUTPUT->render_from_template('tool_dataprivacy/data_requests_bulk_actions', $data);
}
/**
* Set the number of data request records to be displayed per page.
*
* @param int $perpage The number of data request records.
*/
public function set_requests_per_page($perpage) {
$this->perpage = $perpage;
}
/**
* Get the number of data request records to be displayed per page.
*
* @return int The number of data request records.
*/
public function get_requests_per_page() {
return $this->perpage;
}
/**
* Set the available options for the number of data request to be displayed per page.
*
* @param array $perpageoptions The available options for the number of data request to be displayed per page.
*/
public function set_requests_per_page_options($perpageoptions) {
$this->$perpageoptions = $perpageoptions;
}
/**
* Get the available options for the number of data request to be displayed per page.
*
* @return array The available options for the number of data request to be displayed per page.
*/
public function get_requests_per_page_options() {
return $this->perpageoptions;
}
}
@@ -0,0 +1,178 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing data for the data registry defaults.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use action_menu_link_primary;
use coding_exception;
use moodle_exception;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\external\category_exporter;
use tool_dataprivacy\external\purpose_exporter;
/**
* Class containing data for the data registry defaults.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class defaults_page implements renderable, templatable {
/** @var int $mode The display mode. */
protected $mode = null;
/** @var int $category The default category for the given mode. */
protected $category = null;
/** @var int $purpose The default purpose for the given mode. */
protected $purpose = null;
/** @var stdClass[] $otherdefaults Other defaults for the given mode. */
protected $otherdefaults = [];
/** @var bool $canedit Whether editing is allowed. */
protected $canedit = false;
/**
* Construct this renderable.
*
* @param int $mode The display mode.
* @param int $category The default category for the given mode.
* @param int $purpose The default purpose for the given mode.
* @param stdClass[] $otherdefaults Other defaults for the given mode.
* @param bool $canedit Whether editing is allowed.
*/
public function __construct($mode, $category, $purpose, $otherdefaults = [], $canedit = false) {
$this->mode = $mode;
$this->category = $category;
$this->purpose = $purpose;
$this->otherdefaults = $otherdefaults;
$this->canedit = $canedit;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
* @throws coding_exception
* @throws moodle_exception
*/
public function export_for_template(renderer_base $output) {
$data = new stdClass();
// Set tab URLs.
$coursecaturl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_COURSECAT]);
$courseurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_COURSE]);
$moduleurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_MODULE]);
$blockurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_BLOCK]);
$data->coursecaturl = $coursecaturl;
$data->courseurl = $courseurl;
$data->moduleurl = $moduleurl;
$data->blockurl = $blockurl;
// Set display mode.
switch ($this->mode) {
case CONTEXT_COURSECAT:
$data->modecoursecat = true;
break;
case CONTEXT_COURSE:
$data->modecourse = true;
break;
case CONTEXT_MODULE:
$data->modemodule = true;
break;
case CONTEXT_BLOCK:
$data->modeblock = true;
break;
default:
$data->modecoursecat = true;
break;
}
// Set config variables.
$configname = \context_helper::get_class_for_level($this->mode);
list($purposevar, $categoryvar) = data_registry::var_names_from_context($configname);
$data->categoryvar = $categoryvar;
$data->purposevar = $purposevar;
// Set default category.
$data->categoryid = $this->category;
$data->category = category_exporter::get_name($this->category);
// Set default purpose.
$data->purposeid = $this->purpose;
$data->purpose = purpose_exporter::get_name($this->purpose);
// Set other defaults.
$otherdefaults = [];
$url = new moodle_url('#');
foreach ($this->otherdefaults as $pluginname => $values) {
$defaults = [
'name' => $values->name,
'category' => category_exporter::get_name($values->category),
'purpose' => purpose_exporter::get_name($values->purpose),
];
if ($this->canedit) {
$actions = [];
// Edit link.
$editattrs = [
'data-action' => 'edit-activity-defaults',
'data-contextlevel' => $this->mode,
'data-activityname' => $pluginname,
'data-category' => $values->category,
'data-purpose' => $values->purpose,
];
$editlink = new action_menu_link_primary($url, new \pix_icon('t/edit', get_string('edit')),
get_string('edit'), $editattrs);
$actions[] = $editlink->export_for_template($output);
// Delete link.
$deleteattrs = [
'data-action' => 'delete-activity-defaults',
'data-contextlevel' => $this->mode,
'data-activityname' => $pluginname,
'data-activitydisplayname' => $values->name,
];
$deletelink = new action_menu_link_primary($url, new \pix_icon('t/delete', get_string('delete')),
get_string('delete'), $deleteattrs);
$actions[] = $deletelink->export_for_template($output);
$defaults['actions'] = $actions;
}
$otherdefaults[] = (object)$defaults;
}
$data->otherdefaults = $otherdefaults;
$data->canedit = $this->canedit;
$data->contextlevel = $this->mode;
return $data;
}
}
@@ -0,0 +1,284 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the class used for the displaying the expired contexts table.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/tablelib.php');
use coding_exception;
use context_helper;
use dml_exception;
use Exception;
use html_writer;
use pix_icon;
use stdClass;
use table_sql;
use tool_dataprivacy\api;
use tool_dataprivacy\expired_context;
use tool_dataprivacy\external\purpose_exporter;
use tool_dataprivacy\purpose;
defined('MOODLE_INTERNAL') || die;
/**
* The class for displaying the expired contexts table.
*
* @copyright 2018 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_contexts_table extends table_sql {
/** @var int The context level acting as a filter for this table. */
protected $contextlevel = null;
/**
* @var bool $selectall Has the user selected all users on the page? True by default.
*/
protected $selectall = true;
/** @var purpose[] Array of purposes mapped to the contexts. */
protected $purposes = [];
/**
* expired_contexts_table constructor.
*
* @param int|null $contextlevel
* @throws coding_exception
*/
public function __construct($contextlevel = null) {
parent::__construct('expired-contexts-table');
$this->contextlevel = $contextlevel;
$columnheaders = [
'name' => get_string('name'),
'info' => get_string('info'),
'purpose' => get_string('purpose', 'tool_dataprivacy'),
'category' => get_string('category', 'tool_dataprivacy'),
'retentionperiod' => get_string('retentionperiod', 'tool_dataprivacy'),
'timecreated' => get_string('expiry', 'tool_dataprivacy'),
];
$checkboxattrs = [
'title' => get_string('selectall'),
'data-action' => 'selectall'
];
$columnheaders['select'] = html_writer::checkbox('selectall', 1, true, null, $checkboxattrs);
$this->define_columns(array_keys($columnheaders));
$this->define_headers(array_values($columnheaders));
$this->no_sorting('name');
$this->no_sorting('select');
$this->no_sorting('info');
$this->no_sorting('purpose');
$this->no_sorting('category');
$this->no_sorting('retentionperiod');
// Make this table sorted by first name by default.
$this->sortable(true, 'timecreated');
}
/**
* The context name column.
*
* @param stdClass $data The row data.
* @return string
* @throws coding_exception
*/
public function col_name($data) {
global $OUTPUT;
$context = context_helper::instance_by_id($data->contextid);
$parent = $context->get_parent_context();
$contextdata = (object)[
'name' => $context->get_context_name(false, true),
'parent' => $parent->get_context_name(false, true),
];
$fullcontexts = $context->get_parent_contexts(true);
$contextsinpath = [];
foreach ($fullcontexts as $contextinpath) {
$contextsinpath[] = $contextinpath->get_context_name(false, true);
}
$infoicon = new pix_icon('i/info', implode(' / ', array_reverse($contextsinpath)));
$infoiconhtml = $OUTPUT->render($infoicon);
$name = html_writer::span(get_string('nameandparent', 'tool_dataprivacy', $contextdata), 'm-r-1');
return $name . $infoiconhtml;
}
/**
* The context information column.
*
* @param stdClass $data The row data.
* @return string
* @throws coding_exception
*/
public function col_info($data) {
global $OUTPUT;
$context = context_helper::instance_by_id($data->contextid);
$children = $context->get_child_contexts();
if (empty($children)) {
return get_string('none');
} else {
$childnames = [];
foreach ($children as $child) {
$childnames[] = $child->get_context_name(false, true);
}
$infoicon = new pix_icon('i/info', implode(', ', $childnames));
$infoiconhtml = $OUTPUT->render($infoicon);
$name = html_writer::span(get_string('nchildren', 'tool_dataprivacy', count($children)), 'm-r-1');
return $name . $infoiconhtml;
}
}
/**
* The category name column.
*
* @param stdClass $data The row data.
* @return mixed
* @throws coding_exception
* @throws dml_exception
*/
public function col_category($data) {
$context = context_helper::instance_by_id($data->contextid);
$category = api::get_effective_context_category($context);
return s($category->get('name'));
}
/**
* The purpose column.
*
* @param stdClass $data The row data.
* @return string
* @throws coding_exception
*/
public function col_purpose($data) {
$purpose = $this->purposes[$data->contextid];
return s($purpose->get('name'));
}
/**
* The retention period column.
*
* @param stdClass $data The row data.
* @return string
* @throws Exception
*/
public function col_retentionperiod($data) {
global $PAGE;
$purpose = $this->purposes[$data->contextid];
$exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
$exportedpurpose = $exporter->export($PAGE->get_renderer('core'));
return $exportedpurpose->formattedretentionperiod;
}
/**
* The timecreated a.k.a. the context expiry date column.
*
* @param stdClass $data The row data.
* @return string
*/
public function col_timecreated($data) {
return userdate($data->timecreated);
}
/**
* Generate the select column.
*
* @param stdClass $data The row data.
* @return string
*/
public function col_select($data) {
$id = $data->id;
return html_writer::checkbox('expiredcontext_' . $id, $id, $this->selectall, '', ['class' => 'selectcontext']);
}
/**
* Query the database for results to display in the table.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
* @throws dml_exception
* @throws coding_exception
*/
public function query_db($pagesize, $useinitialsbar = true) {
// Only count expired contexts that are awaiting confirmation.
$total = expired_context::get_record_count_by_contextlevel($this->contextlevel, expired_context::STATUS_EXPIRED);
$this->pagesize($pagesize, $total);
$sort = $this->get_sql_sort();
if (empty($sort)) {
$sort = 'timecreated';
}
// Only load expired contexts that are awaiting confirmation.
$expiredcontexts = expired_context::get_records_by_contextlevel($this->contextlevel, expired_context::STATUS_EXPIRED,
$sort, $this->get_page_start(), $this->get_page_size());
$this->rawdata = [];
foreach ($expiredcontexts as $persistent) {
$data = $persistent->to_record();
$context = context_helper::instance_by_id($data->contextid);
$purpose = api::get_effective_context_purpose($context);
$this->purposes[$data->contextid] = $purpose;
$this->rawdata[] = $data;
}
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
/**
* Override default implementation to display a more meaningful information to the user.
*/
public function print_nothing_to_display() {
global $OUTPUT;
echo $this->render_reset_button();
$this->print_initials_bar();
echo $OUTPUT->notification(get_string('noexpiredcontexts', 'tool_dataprivacy'), 'warning');
}
/**
* Override the table's show_hide_link method to prevent the show/hide link for the select column from rendering.
*
* @param string $column the column name, index into various names.
* @param int $index numerical index of the column.
* @return string HTML fragment.
*/
protected function show_hide_link($column, $index) {
if ($index < 6) {
return parent::show_hide_link($column, $index);
}
return '';
}
}
@@ -0,0 +1,165 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing data for a user's data requests.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use action_menu;
use action_menu_link_secondary;
use coding_exception;
use context_user;
use moodle_exception;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
use tool_dataprivacy\external\data_request_exporter;
/**
* Class containing data for a user's data requests.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class my_data_requests_page implements renderable, templatable {
/** @var array $requests List of data requests. */
protected $requests = [];
/**
* Construct this renderable.
*
* @param data_request[] $requests
*/
public function __construct($requests) {
$this->requests = $requests;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
* @throws coding_exception
* @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');
$type = $request->get('type');
$usercontext = context_user::instance($userid, IGNORE_MISSING);
if (!$usercontext) {
// Use the context system.
$outputcontext = \context_system::instance();
} else {
$outputcontext = $usercontext;
}
$requestexporter = new data_request_exporter($request, ['context' => $outputcontext]);
$item = $requestexporter->export($output);
$self = $request->get('userid') == $USER->id;
if (!$self) {
// Append user name if it differs from $USER.
$a = (object)['typename' => $item->typename, 'user' => $item->foruser->fullname];
$item->typename = get_string('requesttypeuser', 'tool_dataprivacy', $a);
}
$candownload = false;
$cancancel = true;
switch ($status) {
case api::DATAREQUEST_STATUS_COMPLETE:
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
$cancancel = false;
break;
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statusready', 'tool_dataprivacy');
$cancancel = false;
$candownload = true;
if ($usercontext) {
$candownload = api::can_download_data_request_for_user(
$request->get('userid'), $request->get('requestedby'));
}
break;
case api::DATAREQUEST_STATUS_DELETED:
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statusdeleted', 'tool_dataprivacy');
$cancancel = false;
break;
case api::DATAREQUEST_STATUS_EXPIRED:
$item->statuslabelclass = 'label-default';
$item->statuslabel = get_string('statusexpired', 'tool_dataprivacy');
$item->statuslabeltitle = get_string('downloadexpireduser', 'tool_dataprivacy');
$cancancel = false;
break;
case api::DATAREQUEST_STATUS_CANCELLED:
case api::DATAREQUEST_STATUS_REJECTED:
$cancancel = false;
break;
}
// Prepare actions.
$actions = [];
if ($cancancel) {
$cancelurl = new moodle_url('#');
$canceldata = ['data-action' => 'cancel', 'data-requestid' => $requestid];
$canceltext = get_string('cancelrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($cancelurl, null, $canceltext, $canceldata);
}
if ($candownload && $usercontext) {
$actions[] = api::get_download_link($usercontext, $requestid);
}
if (!empty($actions)) {
$actionsmenu = new action_menu($actions);
$actionsmenu->set_menu_trigger(get_string('actions'));
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
$item->actions = $actionsmenu->export_for_template($output);
}
$requests[] = $item;
}
$data->requests = $requests;
return $data;
}
}
@@ -0,0 +1,88 @@
<?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/>.
/**
* Purposes renderable.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use stdClass;
use templatable;
use tool_dataprivacy\external\purpose_exporter;
/**
* Class containing the purposes page renderable.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class purposes extends crud_element implements renderable, templatable {
/** @var array $purposes All system purposes. */
protected $purposes = [];
/**
* Construct this renderable.
*
* @param \tool_dataprivacy\purpose[] $purposes
*/
public function __construct($purposes) {
$this->purposes = $purposes;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $PAGE;
$context = \context_system::instance();
$PAGE->requires->js_call_amd('tool_dataprivacy/purposesactions', 'init');
$PAGE->requires->js_call_amd('tool_dataprivacy/add_purpose', 'getInstance', [$context->id]);
$data = new stdClass();
// Navigation links.
$data->navigation = [];
$navigationlinks = $this->get_navigation();
foreach ($navigationlinks as $navlink) {
$data->navigation[] = $navlink->export_for_template($output);
}
$data->purposes = [];
foreach ($this->purposes as $purpose) {
$exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
$exportedpurpose = $exporter->export($output);
$actionmenu = $this->action_menu('purpose', $exportedpurpose, $purpose);
$exportedpurpose->actions = $actionmenu->export_for_template($output);
$data->purposes[] = $exportedpurpose;
}
return $data;
}
}
@@ -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/>.
/**
* Renderer class for tool_dataprivacy
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use html_writer;
use moodle_exception;
use plugin_renderer_base;
/**
* Renderer class for tool_dataprivacy.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Render the user's data requests page.
*
* @param my_data_requests_page $page
* @return string html for the page
* @throws moodle_exception
*/
public function render_my_data_requests_page(my_data_requests_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/my_data_requests', $data);
}
/**
* Render the contact DPO link.
*
* @param string $replytoemail The Reply-to email address
* @return string The HTML for the link.
* @throws coding_exception
*/
public function render_contact_dpo_link($replytoemail) {
$params = [
'data-action' => 'contactdpo',
'data-replytoemail' => $replytoemail,
'class' => 'contactdpo'
];
return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
}
/**
* Render the data requests page for the DPO.
*
* @param data_requests_page $page
* @return string html for the page
* @throws moodle_exception
*/
public function render_data_requests_page(data_requests_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/data_requests', $data);
}
/**
* Render the data registry.
*
* @param data_registry_page $page
* @return string html for the page
* @throws moodle_exception
*/
public function render_data_registry_page(data_registry_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/data_registry', $data);
}
/**
* Render the data compliance registry.
*
* @param data_registry_compliance_page $page
* @return string html for the page
* @throws moodle_exception
*/
public function render_data_registry_compliance_page(data_registry_compliance_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/data_registry_compliance', $data);
}
/**
* Render the purposes management page.
*
* @param purposes $page
* @return string html for the page
* @throws moodle_exception
*/
public function render_purposes(purposes $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/purposes', $data);
}
/**
* Render the categories management page.
*
* @param categories $page
* @return string html for the page
* @throws moodle_exception
*/
public function render_categories(categories $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/categories', $data);
}
/**
* Render the review page for the deletion of expired contexts.
*
* @param data_deletion_page $page
* @return string html for the page
* @throws moodle_exception
*/
public function render_data_deletion_page(data_deletion_page $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('tool_dataprivacy/data_deletion', $data);
}
}
@@ -0,0 +1,98 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class containing the filter options data for rendering the autocomplete element for the data requests page.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\output;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
defined('MOODLE_INTERNAL') || die();
/**
* Class containing the filter options data for rendering the autocomplete element for the data requests page.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class request_filter implements renderable, templatable {
/** @var array $filteroptions The filter options. */
protected $filteroptions;
/** @var array $selectedoptions The list of selected filter option values. */
protected $selectedoptions;
/** @var moodle_url|string $baseurl The url with params needed to call up this page. */
protected $baseurl;
/**
* request_filter constructor.
*
* @param array $filteroptions The filter options.
* @param array $selectedoptions The list of selected filter option values.
* @param string|moodle_url $baseurl The url with params needed to call up this page.
*/
public function __construct($filteroptions, $selectedoptions, $baseurl = null) {
$this->filteroptions = $filteroptions;
$this->selectedoptions = $selectedoptions;
if (!empty($baseurl)) {
$this->baseurl = new moodle_url($baseurl);
}
}
/**
* Function to export the renderer data in a format that is suitable for a mustache template.
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return stdClass|array
*/
public function export_for_template(renderer_base $output) {
global $PAGE;
$data = new stdClass();
if (empty($this->baseurl)) {
$this->baseurl = $PAGE->url;
}
$data->action = $this->baseurl->out(false);
foreach ($this->selectedoptions as $option) {
if (!isset($this->filteroptions[$option])) {
$this->filteroptions[$option] = $option;
}
}
$data->filteroptions = [];
foreach ($this->filteroptions as $value => $label) {
$selected = in_array($value, $this->selectedoptions);
$filteroption = (object)[
'value' => $value,
'label' => $label
];
$filteroption->selected = $selected;
$data->filteroptions[] = $filteroption;
}
return $data;
}
}
@@ -0,0 +1,85 @@
<?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/>.
/**
* Page helper.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use context_system;
use moodle_url;
defined('MOODLE_INTERNAL') || die();
/**
* Page helper.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class page_helper {
/**
* Sets up $PAGE for data privacy admin pages.
*
* @param moodle_url $url The page URL.
* @param string $title The page's title.
* @param string $attachtoparentnode The parent navigation node where this page can be accessed from.
* @param string $requiredcapability The required capability to view this page.
*/
public static function setup(moodle_url $url, $title, $attachtoparentnode = '',
$requiredcapability = 'tool/dataprivacy:managedataregistry') {
global $PAGE, $SITE;
$context = context_system::instance();
require_login();
if (isguestuser()) {
print_error('noguest');
}
// TODO Check that data privacy is enabled.
require_capability($requiredcapability, $context);
$PAGE->navigation->override_active_url($url);
$PAGE->set_url($url);
$PAGE->set_context($context);
$PAGE->set_pagelayout('admin');
$PAGE->set_title($title);
$PAGE->set_heading($SITE->fullname);
// If necessary, override the settings navigation to add this page into the breadcrumb navigation.
if ($attachtoparentnode) {
if ($siteadmin = $PAGE->settingsnav->find('root', \navigation_node::TYPE_SITE_ADMIN)) {
$PAGE->navbar->add($siteadmin->get_content(), $siteadmin->action());
}
if ($dataprivacy = $PAGE->settingsnav->find('privacy', \navigation_node::TYPE_SETTING)) {
$PAGE->navbar->add($dataprivacy->get_content(), $dataprivacy->action());
}
if ($dataregistry = $PAGE->settingsnav->find($attachtoparentnode, \navigation_node::TYPE_SETTING)) {
$PAGE->navbar->add($dataregistry->get_content(), $dataregistry->action());
}
$PAGE->navbar->add($title, $url);
}
}
}
@@ -0,0 +1,218 @@
<?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 class for requesting user data.
*
* @package tool_dataprivacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\privacy;
defined('MOODLE_INTERNAL') || die();
use coding_exception;
use context;
use context_user;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use dml_exception;
use stdClass;
use tool_dataprivacy\api;
use tool_dataprivacy\local\helper as tool_helper;
/**
* Privacy class for requesting user data.
*
* @package tool_dataprivacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// This tool stores user data.
\core_privacy\local\metadata\provider,
// This tool may provide access to and deletion of user data.
\core_privacy\local\request\plugin\provider,
// This plugin has some sitewide user preferences to export.
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @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->add_database_table(
'tool_dataprivacy_request',
[
'comments' => 'privacy:metadata:request:comments',
'userid' => 'privacy:metadata:request:userid',
'requestedby' => 'privacy:metadata:request:requestedby',
'dpocomment' => 'privacy:metadata:request:dpocomment',
'timecreated' => 'privacy:metadata:request:timecreated'
],
'privacy:metadata:request'
);
$collection->add_user_preference(tool_helper::PREF_REQUEST_FILTERS,
'privacy:metadata:preference:tool_dataprivacy_request-filters');
$collection->add_user_preference(tool_helper::PREF_REQUEST_PERPAGE,
'privacy:metadata:preference:tool_dataprivacy_request-perpage');
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($userid) {
$sql = "SELECT id
FROM {context}
WHERE instanceid = :userid
AND contextlevel = :contextlevel";
$contextlist = new contextlist();
$contextlist->set_component('tool_dataprivacy');
$contextlist->add_from_sql($sql, ['userid' => $userid, 'contextlevel' => CONTEXT_USER]);
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.
* @throws coding_exception
* @throws dml_exception
* @throws \moodle_exception
*/
public static function export_user_data(approved_contextlist $contextlist) {
if (empty($contextlist->count())) {
return;
}
$user = $contextlist->get_user();
$datarequests = api::get_data_requests($user->id);
$context = context_user::instance($user->id);
$contextdatatowrite = [];
foreach ($datarequests as $request) {
$record = $request->to_record();
$data = new stdClass();
// The user ID that made the request/the request is made for.
if ($record->requestedby != $record->userid) {
if ($user->id != $record->requestedby) {
// This request is done by this user for another user.
$data->userid = fullname($user);
} else if ($user->id != $record->userid) {
// This request was done by another user on behalf of this user.
$data->requestedby = fullname($user);
}
}
// Request type.
$data->type = tool_helper::get_shortened_request_type_string($record->type);
// Status.
$data->status = tool_helper::get_request_status_string($record->status);
// Creation method.
$data->creationmethod = tool_helper::get_request_creation_method_string($record->creationmethod);
// Comments.
$data->comments = $record->comments;
// The DPO's comment about this request.
$data->dpocomment = $record->dpocomment;
// The date and time this request was lodged.
$data->timecreated = transform::datetime($record->timecreated);
$contextdatatowrite[] = $data;
}
// ...{User context} / Privacy and policies / Data requests.
$subcontext = [
get_string('privacyandpolicies', 'admin'),
get_string('datarequests', 'tool_dataprivacy'),
];
writer::with_context($context)->export_data($subcontext, (object)$contextdatatowrite);
// Write generic module intro files.
helper::export_context_files($context, $user);
}
/**
* 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) {
}
/**
* 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) {
}
/**
* 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($userid) {
$preffilter = get_user_preferences(tool_helper::PREF_REQUEST_FILTERS, null, $userid);
if ($preffilter !== null) {
$filters = json_decode($preffilter);
$descriptions = [];
foreach ($filters as $filter) {
list($category, $value) = explode(':', $filter);
$option = new stdClass();
switch($category) {
case tool_helper::FILTER_TYPE:
$option->category = get_string('requesttype', 'tool_dataprivacy');
$option->name = tool_helper::get_shortened_request_type_string($value);
break;
case tool_helper::FILTER_STATUS:
$option->category = get_string('requeststatus', 'tool_dataprivacy');
$option->name = tool_helper::get_request_status_string($value);
break;
case tool_helper::FILTER_CREATION:
$option->category = get_string('requestcreation', 'tool_dataprivacy');
$option->name = tool_helper::get_request_creation_method_string($value);
break;
}
$descriptions[] = get_string('filteroption', 'tool_dataprivacy', $option);
}
// Export the filter preference as comma-separated values and text descriptions.
$values = implode(', ', $filters);
$descriptionstext = implode(', ', $descriptions);
writer::export_user_preference('tool_dataprivacy', tool_helper::PREF_REQUEST_FILTERS, $values, $descriptionstext);
}
$prefperpage = get_user_preferences(tool_helper::PREF_REQUEST_PERPAGE, null, $userid);
if ($prefperpage !== null) {
writer::export_user_preference('tool_dataprivacy', tool_helper::PREF_REQUEST_PERPAGE, $prefperpage,
get_string('privacy:metadata:preference:tool_dataprivacy_request-perpage', 'tool_dataprivacy'));
}
}
}
+183
View File
@@ -0,0 +1,183 @@
<?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 for loading/storing data purposes from the DB.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use stdClass;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Class for loading/storing data purposes from the DB.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class purpose extends \core\persistent {
/**
* Database table.
*/
const TABLE = 'tool_dataprivacy_purpose';
/** Items under GDPR Article 6.1. */
const GDPR_ART_6_1_ITEMS = ['a', 'b', 'c', 'd', 'e', 'f'];
/** Items under GDPR Article 9.2. */
const GDPR_ART_9_2_ITEMS = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
/**
* Extended constructor to fetch from the cache if available.
*
* @param int $id If set, this is the id of an existing record, used to load the data.
* @param stdClass $record If set will be passed to {@link self::from_record()}.
*/
public function __construct($id = 0, stdClass $record = null) {
global $CFG;
if ($id) {
$cache = \cache::make('tool_dataprivacy', 'purpose');
if ($data = $cache->get($id)) {
// Replicate self::read.
$this->from_record($data);
// Validate the purpose record.
$this->validate();
// Now replicate the parent constructor.
if (!empty($record)) {
$this->from_record($record);
}
if ($CFG->debugdeveloper) {
$this->verify_protected_methods();
}
return;
}
}
parent::__construct($id, $record);
}
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return array(
'name' => array(
'type' => PARAM_TEXT,
'description' => 'The purpose name.',
),
'description' => array(
'type' => PARAM_RAW,
'description' => 'The purpose description.',
'null' => NULL_ALLOWED,
'default' => '',
),
'descriptionformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_HTML
),
'lawfulbases' => array(
'type' => PARAM_TEXT,
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_lawfulbasis.',
),
'sensitivedatareasons' => array(
'type' => PARAM_TEXT,
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_sensitive',
'null' => NULL_ALLOWED,
'default' => ''
),
'retentionperiod' => array(
'type' => PARAM_ALPHANUM,
'description' => 'Retention period. ISO_8601 durations format (as in DateInterval format).',
'default' => '',
),
'protected' => array(
'type' => PARAM_INT,
'description' => 'Data retention with higher precedent over user\'s request to be forgotten.',
'default' => '0',
),
);
}
/**
* Adds the new record to the cache.
*
* @return null
*/
protected function after_create() {
$cache = \cache::make('tool_dataprivacy', 'purpose');
$cache->set($this->get('id'), $this->to_record());
}
/**
* Updates the cache record.
*
* @param bool $result
* @return null
*/
protected function after_update($result) {
$cache = \cache::make('tool_dataprivacy', 'purpose');
$cache->set($this->get('id'), $this->to_record());
}
/**
* Removes unnecessary stuff from db.
*
* @return null
*/
protected function before_delete() {
$cache = \cache::make('tool_dataprivacy', 'purpose');
$cache->delete($this->get('id'));
}
/**
* Is this purpose used?.
*
* @return null
*/
public function is_used() {
if (\tool_dataprivacy\contextlevel::is_purpose_used($this->get('id')) ||
\tool_dataprivacy\context_instance::is_purpose_used($this->get('id'))) {
return true;
}
$pluginconfig = get_config('tool_dataprivacy');
$levels = \context_helper::get_all_levels();
foreach ($levels as $level => $classname) {
list($purposevar, $unused) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
if (!empty($pluginconfig->{$purposevar}) && $pluginconfig->{$purposevar} == $this->get('id')) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the request_contextlist persistent.
*
* @package tool_dataprivacy
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
use core\persistent;
/**
* The request_contextlist persistent.
*
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class request_contextlist extends persistent {
/** The table name this persistent object maps to. */
const TABLE = 'tool_dataprivacy_rqst_ctxlst';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties() {
return [
'requestid' => [
'type' => PARAM_INT
],
'contextlistid' => [
'type' => PARAM_INT
]
];
}
/**
* Creates a new relation, but does not persist it.
*
* @param int $requestid
* @param int $contextlistid
* @return $this
* @throws \coding_exception
*/
public static function create_relation($requestid, $contextlistid) {
$requestcontextlist = new request_contextlist();
return $requestcontextlist->set('requestid', $requestid)
->set('contextlistid', $contextlistid);
}
}
@@ -0,0 +1,91 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled task to create delete data request for pre-existing deleted users.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\task;
use core\task\scheduled_task;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Scheduled task to create delete data request for pre-existing deleted users.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_existing_deleted_users extends scheduled_task {
/**
* Returns the task name.
*
* @return string
*/
public function get_name() {
return get_string('deleteexistingdeleteduserstask', 'tool_dataprivacy');
}
/**
* Run the task to delete expired data request files and update request statuses.
*
*/
public function execute() {
global $DB;
// Automatic creation of deletion requests must be enabled.
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
// Select all deleted users that do not have any delete data requests created for them.
$sql = "SELECT DISTINCT(u.id)
FROM {user} u
LEFT JOIN {tool_dataprivacy_request} r
ON u.id = r.userid
WHERE u.deleted = ?
AND (r.id IS NULL
OR r.type != ?)";
$params = [
1,
api::DATAREQUEST_TYPE_DELETE
];
$deletedusers = $DB->get_records_sql($sql, $params);
$createdrequests = 0;
foreach ($deletedusers as $user) {
api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE,
get_string('datarequestcreatedfromscheduledtask', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO);
$createdrequests++;
}
if ($createdrequests > 0) {
mtrace($createdrequests . ' delete data request(s) created for existing deleted users');
}
}
}
}
@@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled task to delete expired context instances once they are approved for deletion.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\task;
use coding_exception;
use core\task\scheduled_task;
use tool_dataprivacy\api;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Scheduled task to delete expired context instances once they are approved for deletion.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_expired_contexts extends scheduled_task {
/**
* Returns the task name.
*
* @return string
*/
public function get_name() {
return get_string('deleteexpiredcontextstask', 'tool_dataprivacy');
}
/**
* Run the task to delete context instances based on their retention periods.
*/
public function execute() {
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($courses, $users) = $manager->process_approved_deletions();
mtrace("Processed deletions for {$courses} course contexts, and {$users} user contexts as expired");
}
}
@@ -0,0 +1,67 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled task to delete files and update statuses of expired data requests.
*
* @package tool_dataprivacy
* @copyright 2018 Michael Hawkins
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\task;
use coding_exception;
use core\task\scheduled_task;
use tool_dataprivacy\api;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Scheduled task to delete files and update request statuses once they expire.
*
* @package tool_dataprivacy
* @copyright 2018 Michael Hawkins
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_expired_requests extends scheduled_task {
/**
* Returns the task name.
*
* @return string
*/
public function get_name() {
return get_string('deleteexpireddatarequeststask', 'tool_dataprivacy');
}
/**
* Run the task to delete expired data request files and update request statuses.
*
*/
public function execute() {
$expiredrequests = \tool_dataprivacy\data_request::get_expired_requests();
$deletecount = count($expiredrequests);
if ($deletecount > 0) {
\tool_dataprivacy\data_request::expire($expiredrequests);
mtrace($deletecount . ' expired completed data requests have been deleted');
}
}
}
@@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled task to flag contexts as expired.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\task;
use coding_exception;
use core\task\scheduled_task;
use tool_dataprivacy\api;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Scheduled task to flag contexts as expired.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_retention_period extends scheduled_task {
/**
* Returns the task name.
*
* @return string
*/
public function get_name() {
return get_string('expiredretentionperiodtask', 'tool_dataprivacy');
}
/**
* Run the task to flag context instances as expired.
*/
public function execute() {
$manager = new \tool_dataprivacy\expired_contexts_manager();
list($courses, $users) = $manager->flag_expired_contexts();
mtrace("Flagged {$courses} course contexts, and {$users} user contexts as expired");
}
}
@@ -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/>.
/**
* Adhoc task that processes a data request and prepares the user's relevant contexts for review.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\task;
use coding_exception;
use core\task\adhoc_task;
use moodle_exception;
use tool_dataprivacy\api;
use tool_dataprivacy\contextlist_context;
use tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
/**
* Class that processes a data request and prepares the user's relevant contexts for review.
*
* Custom data accepted:
* - requestid -> The ID of the data request to be processed.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class initiate_data_request_task extends adhoc_task {
/**
* Run the task to initiate the data request process.
*
* @throws coding_exception
* @throws moodle_exception
*/
public function execute() {
global $CFG;
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
if (!isset($this->get_custom_data()->requestid)) {
throw new coding_exception('The custom data \'requestid\' is required.');
}
$requestid = $this->get_custom_data()->requestid;
$datarequest = new data_request($requestid);
// Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run.
$status = $datarequest->get('status');
if (!api::is_active($status)) {
mtrace('Request ' . $requestid . ' with status ' . $status . ' doesn\'t need to be processed. Skipping...');
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);
// When the preparation of the contexts finishes, update the request status to awaiting approval.
api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
mtrace('Context generation complete...');
// Get the list of the site Data Protection Officers.
$dpos = api::get_site_dpos();
// We should prevent DPO(s)/Admin(s) being flooded with notifications for each request when bulk delete
// data requests are being created.
// NOTE: This should be improved, we should not totally disable the notifications for automatically
// created requests. Possibly, we should create one notification for these such cases.
if ($datarequest->get('creationmethod') != data_request::DATAREQUEST_CREATION_AUTO) {
// Email the data request to the Data Protection Officer(s)/Admin(s).
foreach ($dpos as $dpo) {
$dponame = fullname($dpo);
if (api::notify_dpo($dpo, $datarequest)) {
mtrace('Message sent to DPO: ' . $dponame);
} else {
mtrace('A problem was encountered while sending the message to the DPO: ' . $dponame);
}
}
}
}
}
@@ -0,0 +1,272 @@
<?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/>.
/**
* Adhoc task that processes an approved data request and prepares/deletes the user's data.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\task;
use action_link;
use coding_exception;
use core\message\message;
use core\task\adhoc_task;
use core_user;
use moodle_exception;
use moodle_url;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
/**
* Class that processes an approved data request and prepares/deletes the user's data.
*
* Custom data accepted:
* - requestid -> The ID of the data request to be processed.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class process_data_request_task extends adhoc_task {
/**
* Run the task to initiate the data request process.
*
* @throws coding_exception
* @throws moodle_exception
*/
public function execute() {
global $CFG, $PAGE, $SITE;
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
if (!isset($this->get_custom_data()->requestid)) {
throw new coding_exception('The custom data \'requestid\' is required.');
}
$requestid = $this->get_custom_data()->requestid;
$requestpersistent = new data_request($requestid);
$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.
$status = $requestpersistent->get('status');
if (!api::is_active($status)) {
mtrace("Request {$requestid} with status {$status} doesn't need to be processed. Skipping...");
return;
}
// Get the user details now. We might not be able to retrieve it later if it's a deletion processing.
$foruser = core_user::get_user($request->userid);
// Update the status of this request as pre-processing.
mtrace('Processing request...');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING);
$completestatus = api::DATAREQUEST_STATUS_COMPLETE;
$deleteuser = false;
if ($request->type == api::DATAREQUEST_TYPE_EXPORT) {
// Run as the user performing the export.
cron_setup_user($foruser);
// Get the user context.
$usercontext = \context_user::instance($foruser->id, IGNORE_MISSING);
if (!$usercontext) {
mtrace("Request {$requestid} cannot be processed due to a missing user context instance for the user
with ID {$foruser->id}. Skipping...");
return;
}
// Get the collection of approved_contextlist objects needed for core_privacy data export.
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
// 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();
$filerecord = new \stdClass;
$filerecord->component = 'tool_dataprivacy';
$filerecord->contextid = $usercontext->id;
$filerecord->userid = $foruser->id;
$filerecord->filearea = 'export';
$filerecord->filename = 'export.zip';
$filerecord->filepath = '/';
$filerecord->itemid = $requestid;
$filerecord->license = $CFG->sitedefaultlicense;
$filerecord->author = fullname($foruser);
// Save somewhere.
$thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
$completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY;
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
// Get the collection of approved_contextlist objects needed for core_privacy data deletion.
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
// Delete the data.
$manager = new \core_privacy\manager();
$manager->set_observer(new \tool_dataprivacy\manager_observer());
$manager->delete_data_for_user($approvedclcollection);
$completestatus = api::DATAREQUEST_STATUS_DELETED;
$deleteuser = !$foruser->deleted;
}
// When the preparation of the metadata finishes, update the request status to awaiting approval.
api::update_request_status($requestid, $completestatus);
mtrace('The processing of the user data request has been completed...');
// Create message to notify the user regarding the processing results.
$dpo = core_user::get_user($request->dpo);
$message = new message();
$message->courseid = $SITE->id;
$message->component = 'tool_dataprivacy';
$message->name = 'datarequestprocessingresults';
$message->userfrom = $dpo;
$message->replyto = $dpo->email;
$message->replytoname = fullname($dpo->email);
$typetext = null;
// Prepare the context data for the email message body.
$messagetextdata = [
'username' => fullname($foruser)
];
$output = $PAGE->get_renderer('tool_dataprivacy');
$emailonly = false;
$notifyuser = true;
switch ($request->type) {
case api::DATAREQUEST_TYPE_EXPORT:
// Check if the user is allowed to download their own export. (This is for
// institutions which centrally co-ordinate subject access request across many
// systems, not just one Moodle instance, so we don't want every instance emailing
// the user.)
if (!api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->userid)) {
$notifyuser = false;
}
$typetext = get_string('requesttypeexport', 'tool_dataprivacy');
// We want to notify the user in Moodle about the processing results.
$message->notification = 1;
$datarequestsurl = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php');
$message->contexturl = $datarequestsurl;
$message->contexturlname = get_string('datarequests', 'tool_dataprivacy');
// Message to the recipient.
$messagetextdata['message'] = get_string('resultdownloadready', 'tool_dataprivacy', $SITE->fullname);
// Prepare download link.
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $thing->get_itemid(),
$thing->get_filepath(), $thing->get_filename(), true);
$downloadlink = new action_link($downloadurl, get_string('download', 'tool_dataprivacy'));
$messagetextdata['downloadlink'] = $downloadlink->export_for_template($output);
break;
case api::DATAREQUEST_TYPE_DELETE:
$typetext = get_string('requesttypedelete', 'tool_dataprivacy');
// No point notifying a deleted user in Moodle.
$message->notification = 0;
// Message to the recipient.
$messagetextdata['message'] = get_string('resultdeleted', 'tool_dataprivacy', $SITE->fullname);
// Message will be sent to the deleted user via email only.
$emailonly = true;
break;
default:
cron_setup_user();
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
}
$subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typetext);
$message->subject = $subject;
$message->fullmessageformat = FORMAT_HTML;
$message->userto = $foruser;
// 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 to the user involved.
if ($notifyuser) {
$messagesent = false;
if ($emailonly) {
// Do not sent an email if the user has been deleted. The user email has been previously deleted.
if (!$foruser->deleted) {
$messagesent = email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
}
} else {
$messagesent = message_send($message);
}
if ($messagesent) {
mtrace('Message sent to user: ' . $messagetextdata['username']);
}
}
// Send to requester as well in some circumstances.
if ($foruser->id != $request->requestedby) {
$sendtorequester = false;
switch ($request->type) {
case api::DATAREQUEST_TYPE_EXPORT:
// Send to the requester as well if they can download it, unless they are the
// DPO. If we didn't notify the user themselves (because they can't download)
// then send to requester even if it is the DPO, as in that case the requester
// needs to take some action.
if (api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->requestedby)) {
$sendtorequester = !$notifyuser || !api::is_site_dpo($request->requestedby);
}
break;
case api::DATAREQUEST_TYPE_DELETE:
// Send to the requester if they are not the DPO and if they are allowed to
// create data requests for the user (e.g. Parent).
$sendtorequester = !api::is_site_dpo($request->requestedby) &&
api::can_create_data_request_for_user($request->userid, $request->requestedby);
break;
default:
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
}
// Ensure the requester has the capability to make data requests for this user.
if ($sendtorequester) {
$requestedby = core_user::get_user($request->requestedby);
$message->userto = $requestedby;
$messagetextdata['username'] = fullname($requestedby);
// Render message email body.
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
$message->fullmessage = html_to_text($messagehtml);
$message->fullmessagehtml = $messagehtml;
// Send message.
if ($emailonly) {
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
} else {
message_send($message);
}
mtrace('Message sent to requester: ' . $messagetextdata['username']);
}
}
cron_setup_user();
if ($deleteuser) {
// Delete the user.
delete_user($foruser);
}
}
}
@@ -0,0 +1,100 @@
<?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/>.
/**
* Prints the contact form to the site's Data Protection Officer
*
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
require_once('../../../config.php');
require_once('lib.php');
require_once('createdatarequest_form.php');
$manage = optional_param('manage', 0, PARAM_INT);
$requesttype = optional_param('type', \tool_dataprivacy\api::DATAREQUEST_TYPE_EXPORT, PARAM_INT);
$url = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', ['manage' => $manage, 'type' => $requesttype]);
$PAGE->set_url($url);
require_login();
if (isguestuser()) {
print_error('noguest');
}
// Return URL and context.
if ($manage) {
// For the case where DPO creates data requests on behalf of another user.
$returnurl = new moodle_url($CFG->wwwroot . '/admin/tool/dataprivacy/datarequests.php');
$context = context_system::instance();
// Make sure the user has the proper capability.
require_capability('tool/dataprivacy:managedatarequests', $context);
} else {
// For the case where a user makes request for themselves (or for their children if they are the parent).
$returnurl = new moodle_url($CFG->wwwroot . '/admin/tool/dataprivacy/mydatarequests.php');
$context = context_user::instance($USER->id);
}
$PAGE->set_context($context);
// If contactdataprotectionofficer is disabled, send the user back to the profile page, or the privacy policy page.
// That is, unless you have sufficient capabilities to perform this on behalf of a user.
if (!$manage && !\tool_dataprivacy\api::can_contact_dpo()) {
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), 0, \core\output\notification::NOTIFY_ERROR);
}
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage)]);
$mform->set_data(['type' => $requesttype]);
// Data request cancelled.
if ($mform->is_cancelled()) {
redirect($returnurl);
}
// Data request submitted.
if ($data = $mform->get_data()) {
if ($data->userid != $USER->id) {
if (!\tool_dataprivacy\api::can_manage_data_requests($USER->id)) {
// 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).
\tool_dataprivacy\api::require_can_create_data_request_for_user($data->userid);
}
}
\tool_dataprivacy\api::create_data_request($data->userid, $data->type, $data->comments);
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('createnewdatarequest', 'tool_dataprivacy');
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title($title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
echo $OUTPUT->box_start();
$mform->display();
echo $OUTPUT->box_end();
echo $OUTPUT->footer();
@@ -0,0 +1,129 @@
<?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/>.
/**
* The contact form to the site's Data Protection Officer
*
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
use tool_dataprivacy\api;
use tool_dataprivacy\local\helper;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
\MoodleQuickForm::registerElementType('request_user_autocomplete',
$CFG->dirroot . '/admin/tool/dataprivacy/classes/form/request_user_autocomplete.php',
'\\tool_dataprivacy\\form\\request_user_autocomplete');
/**
* The contact form to the site's Data Protection Officer
*
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
class tool_dataprivacy_data_request_form extends moodleform {
/** @var bool Flag to indicate whether this form is being rendered for managing data requests or for regular requests. */
protected $manage = false;
/**
* Form definition.
*
* @throws coding_exception
*/
public function definition() {
global $USER;
$mform =& $this->_form;
$this->manage = $this->_customdata['manage'];
if ($this->manage) {
$options = [
'ajax' => 'tool_dataprivacy/form-user-selector',
];
$mform->addElement('request_user_autocomplete', 'userid', get_string('requestfor', 'tool_dataprivacy'), [], $options);
$mform->addRule('userid', null, 'required', null, 'client');
} else {
// Get users whom you are being a guardian to if your role has the capability to make data requests for children.
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');
} else {
// Requesting for self.
$mform->addElement('hidden', 'userid', $USER->id);
}
}
$mform->setType('userid', PARAM_INT);
// Subject access request type.
$options = [
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy')
];
$mform->addElement('select', 'type', get_string('requesttype', 'tool_dataprivacy'), $options);
$mform->setType('type', PARAM_INT);
$mform->addHelpButton('type', 'requesttype', 'tool_dataprivacy');
// Request comments text area.
$textareaoptions = ['cols' => 60, 'rows' => 10];
$mform->addElement('textarea', 'comments', get_string('requestcomments', 'tool_dataprivacy'), $textareaoptions);
$mform->setType('type', PARAM_ALPHANUM);
$mform->addHelpButton('comments', 'requestcomments', 'tool_dataprivacy');
// Action buttons.
$this->add_action_buttons();
}
/**
* Form validation.
*
* @param array $data
* @param array $files
* @return array
* @throws coding_exception
* @throws dml_exception
*/
public function validation($data, $files) {
$errors = [];
$validrequesttypes = [
api::DATAREQUEST_TYPE_EXPORT,
api::DATAREQUEST_TYPE_DELETE
];
if (!in_array($data['type'], $validrequesttypes)) {
$errors['type'] = get_string('errorinvalidrequesttype', 'tool_dataprivacy');
}
if (api::has_ongoing_request($data['userid'], $data['type'])) {
$errors['type'] = get_string('errorrequestalreadyexists', 'tool_dataprivacy');
}
return $errors;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?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/>.
/**
* Prints the data deletion main page.
*
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
require_login(null, false);
$filter = optional_param('filter', CONTEXT_COURSE, PARAM_INT);
$url = new moodle_url('/admin/tool/dataprivacy/datadeletion.php');
$title = get_string('datadeletion', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
$table = new \tool_dataprivacy\output\expired_contexts_table($filter);
$table->baseurl = $url;
$table->baseurl->param('filter', $filter);
$datadeletionpage = new \tool_dataprivacy\output\data_deletion_page($filter, $table);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->render($datadeletionpage);
} else {
$dponamestring = implode (',', tool_dataprivacy\api::get_dpo_role_names());
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
echo $OUTPUT->notification($message, 'error');
}
echo $OUTPUT->footer();
+50
View File
@@ -0,0 +1,50 @@
<?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/>.
/**
* Prints the data registry main page.
*
* @copyright 2018 onwards David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
require_login(null, false);
$contextlevel = optional_param('contextlevel', CONTEXT_SYSTEM, PARAM_INT);
$contextid = optional_param('contextid', 0, PARAM_INT);
$url = new moodle_url('/admin/tool/dataprivacy/dataregistry.php');
$title = get_string('dataregistry', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
echo $OUTPUT->heading($title);
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
$dataregistry = new tool_dataprivacy\output\data_registry_page($contextlevel, $contextid);
echo $output->render($dataregistry);
} else {
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
echo $OUTPUT->notification($message, 'error');
}
echo $OUTPUT->footer();
+93
View File
@@ -0,0 +1,93 @@
<?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/>.
/**
* Prints the contact form to the site's Data Protection Officer
*
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package tool_dataprivacy
*/
require_once("../../../config.php");
require_once('lib.php');
require_login(null, false);
$perpage = optional_param('perpage', 0, PARAM_INT);
$url = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
$title = get_string('datarequests', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title, '', 'tool/dataprivacy:managedatarequests');
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
$filtersapplied = optional_param_array('request-filters', [-1], PARAM_NOTAGS);
$filterscleared = optional_param('filters-cleared', 0, PARAM_INT);
if ($filtersapplied === [-1]) {
// If there are no filters submitted, check if there is a saved filters from the user preferences.
$filterprefs = get_user_preferences(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, null);
if ($filterprefs && empty($filterscleared)) {
$filtersapplied = json_decode($filterprefs);
} else {
$filtersapplied = [];
}
}
// Save the current applied filters to the user preferences.
set_user_preference(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, json_encode($filtersapplied));
$types = [];
$statuses = [];
$creationmethods = [];
foreach ($filtersapplied as $filter) {
list($category, $value) = explode(':', $filter);
switch($category) {
case \tool_dataprivacy\local\helper::FILTER_TYPE:
$types[] = $value;
break;
case \tool_dataprivacy\local\helper::FILTER_STATUS:
$statuses[] = $value;
break;
case \tool_dataprivacy\local\helper::FILTER_CREATION:
$creationmethods[] = $value;
break;
}
}
$table = new \tool_dataprivacy\output\data_requests_table(0, $statuses, $types, $creationmethods, true);
if (!empty($perpage)) {
set_user_preference(\tool_dataprivacy\local\helper::PREF_REQUEST_PERPAGE, $perpage);
} else {
$prefperpage = get_user_preferences(\tool_dataprivacy\local\helper::PREF_REQUEST_PERPAGE);
$perpage = ($prefperpage) ? $prefperpage : $table->get_requests_per_page_options()[0];
}
$table->set_requests_per_page($perpage);
$table->baseurl = $url;
$requestlist = new tool_dataprivacy\output\data_requests_page($table, $filtersapplied);
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
echo $requestlistoutput->render($requestlist);
} else {
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
echo $OUTPUT->notification($message, 'error');
}
echo $OUTPUT->footer();
+70
View File
@@ -0,0 +1,70 @@
<?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/>.
/**
* Capability definitions for this module.
*
* @package tool_dataprivacy
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = [
// Capability for managing data requests. Usually given to the site's Data Protection Officer.
'tool/dataprivacy:managedatarequests' => [
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_DATALOSS,
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => []
],
// Capability for managing the data registry. Usually given to the site's Data Protection Officer.
'tool/dataprivacy:managedataregistry' => [
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_DATALOSS,
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => []
],
// Capability for parents/guardians to make data requests on behalf of their children.
'tool/dataprivacy:makedatarequestsforchildren' => [
'riskbitmask' => RISK_SPAM | RISK_PERSONAL,
'captype' => 'write',
'contextlevel' => CONTEXT_USER,
'archetypes' => []
],
// Capability for users to download the results of their own data request.
'tool/dataprivacy:downloadownrequest' => [
'riskbitmask' => 0,
'captype' => 'read',
'contextlevel' => CONTEXT_USER,
'archetypes' => [
'user' => CAP_ALLOW
]
],
// Capability for administrators to download other people's data requests.
'tool/dataprivacy:downloadallrequests' => [
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_USER,
'archetypes' => []
],
];
+43
View File
@@ -0,0 +1,43 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* tool_dataprivacy cache definitions.
*
* @package tool_dataprivacy
* @category cache
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$definitions = array(
'purpose' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'staticacceleration' => true,
'staticaccelerationsize' => 30,
),
'contextlevel' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'staticacceleration' => true,
'staticaccelerationsize' => 10,
),
);
+32
View File
@@ -0,0 +1,32 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file defines observers needed by the plugin.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = [
[
'eventname' => '\core\event\user_deleted',
'callback' => '\tool_dataprivacy\event\user_deleted_observer::create_delete_data_request',
],
];
+151
View File
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20180821" COMMENT="XMLDB file for Moodle tool/dataprivacy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="tool_dataprivacy_request" COMMENT="Table for data requests">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="type" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Data request type"/>
<FIELD NAME="comments" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="More details about the request"/>
<FIELD NAME="commentsformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID the request is being made for"/>
<FIELD NAME="requestedby" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the one making the request"/>
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The current status of the data request"/>
<FIELD NAME="dpo" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the Data Protection Officer who is reviewing th request"/>
<FIELD NAME="dpocomment" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="DPO's comments (e.g. reason for rejecting the request, etc.)"/>
<FIELD NAME="dpocommentformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user who created/modified this request object"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time this data request was created"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The last time this data request was updated"/>
<FIELD NAME="creationmethod" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The type of the creation method of the data request"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="requestedby" TYPE="foreign" FIELDS="requestedby" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="dpo" TYPE="foreign" FIELDS="dpo" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_purpose" COMMENT="Data purposes">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="lawfulbases" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Comma-separated IDs matching records in tool_dataprivacy_lawfulbasis"/>
<FIELD NAME="sensitivedatareasons" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Comma-separated IDs matching records in tool_dataprivacy_sensitive"/>
<FIELD NAME="retentionperiod" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="protected" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_category" COMMENT="Data categories">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_ctxinstance" COMMENT="Default comment for the table, please edit me">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="purposeid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="contextid" TYPE="foreign-unique" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
<KEY NAME="purposeid" TYPE="foreign" FIELDS="purposeid" REFTABLE="tool_dataprivacy_purpose" REFFIELDS="id"/>
<KEY NAME="categoryid" TYPE="foreign" FIELDS="categoryid" REFTABLE="tool_dataprivacy_category" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_ctxlevel" COMMENT="Default comment for the table, please edit me">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="contextlevel" TYPE="int" LENGTH="3" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="purposeid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="contextlevel" TYPE="unique" FIELDS="contextlevel"/>
<KEY NAME="categoryid" TYPE="foreign" FIELDS="categoryid" REFTABLE="tool_dataprivacy_category" REFFIELDS="id"/>
<KEY NAME="purposeid" TYPE="foreign" FIELDS="purposeid" REFTABLE="tool_dataprivacy_purpose" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_ctxexpired" COMMENT="Default comment for the table, please edit me">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="contextid" TYPE="foreign-unique" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_contextlist" COMMENT="List of contexts for a component">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="component" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Frankenstyle component name"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_ctxlst_ctx" COMMENT="A contextlist context item">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Approval status of the context item"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist containing this context item"/>
</KEYS>
</TABLE>
<TABLE NAME="tool_dataprivacy_rqst_ctxlst" COMMENT="Association table joining requests and contextlists">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="requestid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="requestid" TYPE="foreign" FIELDS="requestid" REFTABLE="tool_dataprivacy_request" REFFIELDS="id" COMMENT="Reference to the request"/>
<KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist"/>
<KEY NAME="request_contextlist" TYPE="unique" FIELDS="requestid, contextlistid" COMMENT="Uniqueness constraint on request and contextlist"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
+52
View File
@@ -0,0 +1,52 @@
<?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/>.
/**
* Defines message providers (types of messages being sent)
*
* @package tool_dataprivacy
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$messageproviders = [
// Notify Data Protection Officer about incoming data requests.
'contactdataprotectionofficer' => [
'defaults' => [
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
],
'capability' => 'tool/dataprivacy:managedatarequests'
],
// Notify user about the processing results of their data request.
'datarequestprocessingresults' => [
'defaults' => [
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
'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'
],
];
+236
View File
@@ -0,0 +1,236 @@
<?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/>.
/**
* Chat external functions and service definitions.
*
* @package tool_dataprivacy
* @category external
* @copyright 2018 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$functions = [
'tool_dataprivacy_cancel_data_request' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'cancel_data_request',
'classpath' => '',
'description' => 'Cancel the data request made by the user',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_contact_dpo' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'contact_dpo',
'classpath' => '',
'description' => 'Contact the site Data Protection Officer(s)',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_mark_complete' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'mark_complete',
'classpath' => '',
'description' => 'Mark a user\'s general enquiry as complete',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_data_request' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_data_request',
'classpath' => '',
'description' => 'Fetch the details of a user\'s data request',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_approve_data_request' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'approve_data_request',
'classpath' => '',
'description' => 'Approve a data request',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_bulk_approve_data_requests' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'bulk_approve_data_requests',
'classpath' => '',
'description' => 'Bulk approve data requests',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_deny_data_request' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'deny_data_request',
'classpath' => '',
'description' => 'Deny a data request',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_bulk_deny_data_requests' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'bulk_deny_data_requests',
'classpath' => '',
'description' => 'Bulk deny data requests',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_users' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_users',
'classpath' => '',
'description' => 'Fetches a list of users',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedatarequests',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_create_purpose_form' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'create_purpose_form',
'classpath' => '',
'description' => 'Adds a data purpose',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_create_category_form' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'create_category_form',
'classpath' => '',
'description' => 'Adds a data category',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_delete_purpose' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'delete_purpose',
'classpath' => '',
'description' => 'Deletes an existing data purpose',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_delete_category' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'delete_category',
'classpath' => '',
'description' => 'Deletes an existing data category',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_set_contextlevel_form' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'set_contextlevel_form',
'classpath' => '',
'description' => 'Sets purpose and category across a context level',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_set_context_form' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'set_context_form',
'classpath' => '',
'description' => 'Sets purpose and category for a specific context',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_tree_extra_branches' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'tree_extra_branches',
'classpath' => '',
'description' => 'Return branches for the context tree',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_confirm_contexts_for_deletion' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'confirm_contexts_for_deletion',
'classpath' => '',
'description' => 'Mark the selected expired contexts as confirmed for deletion',
'type' => 'write',
'capabilities' => '',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_set_context_defaults' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'set_context_defaults',
'classpath' => '',
'description' => 'Updates the default category and purpose for a given context level (and optionally, a plugin)',
'type' => 'write',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_category_options' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_category_options',
'classpath' => '',
'description' => 'Fetches a list of data category options',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_purpose_options' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_purpose_options',
'classpath' => '',
'description' => 'Fetches a list of data storage purpose options',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
'tool_dataprivacy_get_activity_options' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_activity_options',
'classpath' => '',
'description' => 'Fetches a list of activity options',
'type' => 'read',
'capabilities' => 'tool/dataprivacy:managedataregistry',
'ajax' => true,
'loginrequired' => true,
],
];
+63
View File
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file defines tasks performed by the tool.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// List of tasks.
$tasks = array(
array(
'classname' => 'tool_dataprivacy\task\expired_retention_period',
'blocking' => 0,
'minute' => '0',
'hour' => 'R',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
), array(
'classname' => 'tool_dataprivacy\task\delete_expired_contexts',
'blocking' => 0,
'minute' => '0',
'hour' => 'R',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
), array(
'classname' => 'tool_dataprivacy\task\delete_expired_requests',
'blocking' => 0,
'minute' => 'R',
'hour' => 'R',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
), array(
'classname' => 'tool_dataprivacy\task\delete_existing_deleted_users',
'blocking' => 0,
'minute' => 'R',
'hour' => 'R',
'day' => '*',
'dayofweek' => '*',
'month' => '*',
'disabled' => true,
),
);
+233
View File
@@ -0,0 +1,233 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* tool_dataprivacy plugin upgrade code
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Function to upgrade auth_cas.
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_tool_dataprivacy_upgrade($oldversion) {
global $CFG, $DB;
$dbman = $DB->get_manager();
if ($oldversion < 2017051500) {
// Nothing to do here. Moodle Plugins site's just complaining about missing upgrade.php.
// Savepoint reached.
upgrade_plugin_savepoint(true, 2017051500, 'error', 'dataprivacy');
}
if ($oldversion < 2017051503) {
// Define table tool_dataprivacy_ctxexpired to be created.
$table = new xmldb_table('tool_dataprivacy_ctxexpired');
// Adding fields to table tool_dataprivacy_ctxexpired.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table tool_dataprivacy_ctxexpired.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('contextid', XMLDB_KEY_FOREIGN_UNIQUE, array('contextid'), 'context', array('id'));
// Conditionally launch create table for tool_dataprivacy_ctxexpired.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2017051503, 'tool', 'dataprivacy');
}
if ($oldversion < 2017051504) {
// Define table tool_dataprivacy_contextlist to be created.
$table = new xmldb_table('tool_dataprivacy_contextlist');
// Adding fields to table tool_dataprivacy_contextlist.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table tool_dataprivacy_contextlist.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
// Conditionally launch create table for tool_dataprivacy_contextlist.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table tool_dataprivacy_ctxlst_ctx to be created.
$table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
// Adding fields to table tool_dataprivacy_ctxlst_ctx.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table tool_dataprivacy_ctxlst_ctx.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
// Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table tool_dataprivacy_rqst_ctxlst to be created.
$table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
// Adding fields to table tool_dataprivacy_rqst_ctxlst.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table tool_dataprivacy_rqst_ctxlst.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('requestid', XMLDB_KEY_FOREIGN, array('requestid'), 'tool_dataprivacy_request', array('id'));
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
$table->add_key('request_contextlist', XMLDB_KEY_UNIQUE, array('requestid', 'contextlistid'));
// Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2017051504, 'tool', 'dataprivacy');
}
if ($oldversion < 2017051506) {
// Define field lawfulbases to be added to tool_dataprivacy_purpose.
$table = new xmldb_table('tool_dataprivacy_purpose');
// It is a required field. We initially define and add it as null and later update it to XMLDB_NOTNULL.
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null, 'descriptionformat');
// Conditionally launch add field lawfulbases.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
// Set a kind-of-random value to lawfulbasis field.
$DB->set_field('tool_dataprivacy_purpose', 'lawfulbases', 'gdpr_art_6_1_a');
// We redefine it now as not null.
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'descriptionformat');
// Launch change of nullability for field lawfulbases.
$dbman->change_field_notnull($table, $field);
}
// Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
$table = new xmldb_table('tool_dataprivacy_purpose');
$field = new xmldb_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null, 'lawfulbases');
// Conditionally launch add field sensitivedatareasons.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2017051506, 'tool', 'dataprivacy');
}
if ($oldversion < 2017051516) {
// Update completed delete requests to new delete status.
$query = "UPDATE {tool_dataprivacy_request}
SET status = :setstatus
WHERE type = :type
AND status = :wherestatus";
$params = array(
'setstatus' => 10, // Request deleted.
'type' => 2, // Delete type.
'wherestatus' => 5, // Request completed.
);
$DB->execute($query, $params);
// Update completed data export requests to new download ready status.
$params = array(
'setstatus' => 8, // Request download ready.
'type' => 1, // export type.
'wherestatus' => 5, // Request completed.
);
$DB->execute($query, $params);
upgrade_plugin_savepoint(true, 2017051516, 'tool', 'dataprivacy');
}
if ($oldversion < 2017051517) {
// Changing precision of field status on table tool_dataprivacy_request to (2).
$table = new xmldb_table('tool_dataprivacy_request');
$field = new xmldb_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'requestedby');
// Launch change of precision for field status.
$dbman->change_field_precision($table, $field);
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2017051517, 'tool', 'dataprivacy');
}
if ($oldversion < 2017051554) {
// Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
$table = new xmldb_table('tool_dataprivacy_request');
$field = new xmldb_field('creationmethod', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, 0, 'timemodified');
// Conditionally launch add field sensitivedatareasons.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2017051554, 'tool', 'dataprivacy');
}
if ($oldversion < 2017051556) {
// Delete orphaned data privacy requests.
$sql = "SELECT r.id
FROM {tool_dataprivacy_request} r LEFT JOIN {user} u ON r.userid = u.id
WHERE u.id IS NULL";
$orphaned = $DB->get_fieldset_sql($sql);
if ($orphaned) {
$DB->delete_records_list('tool_dataprivacy_request', 'id', $orphaned);
}
upgrade_plugin_savepoint(true, 2017051556, 'tool', 'dataprivacy');
}
return true;
}
+71
View File
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This page lets users manage default purposes and categories.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
require_login(null, false);
$url = new \moodle_url('/admin/tool/dataprivacy/defaults.php');
$title = get_string('setdefaults', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
$mode = optional_param('mode', CONTEXT_COURSECAT, PARAM_INT);
$classname = context_helper::get_class_for_level($mode);
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$purpose = get_config('tool_dataprivacy', $purposevar);
$category = get_config('tool_dataprivacy', $categoryvar);
$otherdefaults = [];
if ($mode == CONTEXT_MODULE) {
// Get activity module plugin info.
$pluginmanager = core_plugin_manager::instance();
$modplugins = $pluginmanager->get_enabled_plugins('mod');
foreach ($modplugins as $name) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname, $name);
$plugincategory = get_config('tool_dataprivacy', $categoryvar);
$pluginpurpose = get_config('tool_dataprivacy', $purposevar);
if ($plugincategory === false && $pluginpurpose === false) {
// If no purpose and category has been set for this plugin, then there's no need to show this on the list.
continue;
}
$displayname = $pluginmanager->plugin_name('mod_' . $name);
$otherdefaults[$name] = (object)[
'name' => $displayname,
'category' => $plugincategory,
'purpose' => $pluginpurpose,
];
}
}
$defaultspage = new \tool_dataprivacy\output\defaults_page($mode, $category, $purpose, $otherdefaults, true);
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
echo $output->heading($title);
echo $output->render_from_template('tool_dataprivacy/defaults_page', $defaultspage->export_for_template($output));
echo $output->footer();
+60
View File
@@ -0,0 +1,60 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This page lets users manage categories.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
$id = optional_param('id', 0, PARAM_INT);
$url = new \moodle_url('/admin/tool/dataprivacy/editcategory.php', array('id' => $id));
if ($id) {
$title = get_string('editcategory', 'tool_dataprivacy');
} else {
$title = get_string('addcategory', 'tool_dataprivacy');
}
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
$category = new \tool_dataprivacy\category($id);
$form = new \tool_dataprivacy\form\category($PAGE->url->out(false),
array('persistent' => $category, 'showbuttons' => true));
$returnurl = new \moodle_url('/admin/tool/dataprivacy/categories.php');
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
if (empty($data->id)) {
\tool_dataprivacy\api::create_category($data);
$messagesuccess = get_string('categorycreated', 'tool_dataprivacy');
} else {
\tool_dataprivacy\api::update_category($data);
$messagesuccess = get_string('categoryupdated', 'tool_dataprivacy');
}
redirect($returnurl, $messagesuccess, 0, \core\output\notification::NOTIFY_SUCCESS);
}
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
$form->display();
echo $output->footer();
+61
View File
@@ -0,0 +1,61 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This page lets users manage purposes.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_login(null, false);
$id = optional_param('id', 0, PARAM_INT);
$url = new \moodle_url('/admin/tool/dataprivacy/editpurpose.php', array('id' => $id));
if ($id) {
$title = get_string('editpurpose', 'tool_dataprivacy');
} else {
$title = get_string('addpurpose', 'tool_dataprivacy');
}
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
$purpose = new \tool_dataprivacy\purpose($id);
$form = new \tool_dataprivacy\form\purpose($PAGE->url->out(false),
array('persistent' => $purpose, 'showbuttons' => true));
$returnurl = new \moodle_url('/admin/tool/dataprivacy/purposes.php');
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
if (empty($data->id)) {
\tool_dataprivacy\api::create_purpose($data);
$messagesuccess = get_string('purposecreated', 'tool_dataprivacy');
} else {
\tool_dataprivacy\api::update_purpose($data);
$messagesuccess = get_string('purposeupdated', 'tool_dataprivacy');
}
redirect($returnurl, $messagesuccess, 0, \core\output\notification::NOTIFY_SUCCESS);
}
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
$form->display();
echo $output->footer();

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